@mafesoftware/stock
v0.2.0
Published
Costo promedio ponderado de stock, diferencia de inventario fisico y alertas de reposicion. Nucleo puro: sin DB, sin framework.
Readme
@mafesoftware/stock
Costo promedio ponderado de stock, diferencia de inventario físico y alertas de reposición.
Parte de la familia de paquetes de MAFE Software: sin dependencias de
framework, sin ORM, y puro — sin DB, sin process.env, sin ningún
efecto de lado. Los saldos, cantidades y costos entran siempre por
parámetro; quien persiste (y resuelve la concurrencia, ej. con FOR UPDATE
sobre la fila del saldo) vive en la app que consume este paquete.
bun add @mafesoftware/stockLa documentación de cada función está en src/, con el motivo de cada
decisión al lado. Los tests (tests/) son la otra mitad de la
documentación: cada uno dice qué caso cubre.
Probar
bun testTipos y unidades
- Plata: siempre
biginten centavos — un monto nunca pasa pornumber, ni como paso intermedio. - Cantidades físicas (
Cantidad = string): decimal con hasta 4 decimales ("120","833.3333") — tampoco pasan pornumber, para no perder precisión en un promedio con muchos decimales. - El redondeo (comercial, medio hacia arriba, al centavo) usa
redondearComercialde@mafesoftware/plata-ar— nunca reimplementado acá.
API
Costo promedio ponderado (CPP)
import { costoPromedio, ingresoAValorFijo, egresoAPromedio, egresoAValorFijo, type SaldoStock } from "@mafesoftware/stock";
let saldo: SaldoStock = { cantidad: "0", valor: 0n };
// Ingreso 100 u a $ 10.000 (1.000.000 centavos)
saldo = costoPromedio(saldo, { cantidad: "100", costoUnitario: 1_000_000n });
// { cantidad: "100.0000", valor: 100_000_000n, costoUnitario: 1_000_000n }
// Ingreso 50 u a $ 13.000 → se mezcla con el saldo existente
saldo = costoPromedio(saldo, { cantidad: "50", costoUnitario: 1_300_000n });
// { cantidad: "150.0000", valor: 165_000_000n, costoUnitario: 1_100_000n } ($ 11.000 promedio)
// Egreso al costo promedio VIGENTE (no se recalcula)
const egreso = egresoAPromedio(saldo, "30");
// egreso.ok === true → { valorEgreso: 33_000_000n, resto: { cantidad: "120.0000", valor: 132_000_000n } }
// Cantidad insuficiente
egresoAPromedio(saldo, "999");
// { ok: false, error: "stock_insuficiente", disponible: "120.0000" }Egresar el saldo entero nunca deja valor residual (ni $ 0,02 de más ni
de menos): se lleva s.valor exacto en vez de recalcular
cantidad × costoUnitario y arrastrar el redondeo.
egresoAValorFijo(s, cantidad, valorFijo) es la variante para revertir una
operación anterior al costo ORIGINAL de esa operación (ej.: anular una
recepción al precio pactado en la orden de compra), no al costo promedio
vigente del saldo — mismo chequeo de disponibilidad, y el valor egresado se
acota a s.valor para no dejarlo negativo. Si retira toda la cantidad, se
lleva todo el valor disponible para no dejar centavos residuales con saldo
físico cero.
ingresoAValorFijo(s, cantidad, valorFijo) hace la inversa: repone una salida
sumando su valor total historico exacto, sin reconstruirlo desde un unitario
redondeado. Esto evita perder el centavo residual cuando la salida original
habia vaciado un saldo cuyo valor no era divisible exactamente por la cantidad.
ingresoAValorFijo({ cantidad: "4", valor: 40_000n }, "3", 100_001n);
// { cantidad: "7.0000", valor: 140_001n, costoUnitario: 20_000n }costoUnitarioDivision(valor, cantidad) expone el cociente
valor / cantidad con el mismo redondeo — útil para valorizar un ajuste al
costo promedio vigente sin pasar por un costoPromedio/egreso*.
Inventario físico y reposición
import { diferenciaInventario, bajoMinimo } from "@mafesoftware/stock";
// Sistema 120, contado 115, costo promedio $ 11.000 → faltante
diferenciaInventario("120", "115", 1_100_000n);
// { cantidad: "-5.0000", valor: -5_500_000n }
// Punto de reposición 50, disponible 45 → alerta
bajoMinimo("45", "50"); // true
bajoMinimo("55", "50"); // false
bajoMinimo("10", null); // false (sin punto de reposición configurado)Qué queda afuera a propósito
Las tablas de almacenes y movimientos de stock (qué se guarda, qué FKs
tiene, a qué proyecto o rubro de presupuesto se asocia, el FOR UPDATE que
evita que dos egresos concurrentes sobregiren el mismo saldo) son
específicas de cada producto — no hay una parte de DB genérica para
extraer a este paquete. Lo que sí es común entre productos es la
aritmética: el CPP, la diferencia de inventario y la alerta de
reposición de arriba.
