Volver al esquema de datos

Modelo de datos

Cómo se deriva el stock

La idea más importante de todo el sistema, así que merece su propia página: el stock nunca se guarda como un número. Se calcula, cada vez que se pide, sumando el historial de movimientos.

No existe una columna variante.stock. Buscarla en el esquema no tiene sentido: el número que ves en Inventario o en el catálogo es siempre el resultado de una suma en el momento de la consulta.

El modelo: movimiento_inventario

Cada entrada o salida de una variante del almacén queda anotada como una fila en esta tabla — nunca se edita ni se borra un movimiento ya creado, solo se añaden nuevos:

variante_id, almacen_id, tipo, cantidad, referencia, fecha -- cantidad es SIEMPRE con signo: positiva si entra, negativa si sale

El signo de cantidad depende del tipo, y la base de datos lo obliga con una restricción (CHECK), no solo la aplicación:

entrada compra → siempre positiva (suma).
venta merma → siempre negativa (resta).
ajuste → puede ser cualquiera de los dos signos (corrección manual, p. ej. tras un conteo físico).

La fórmula

El stock de una variante es la suma de todos sus movimientos. Por almacén, se filtra además por almacen_id:

stock(variante) = SUM(cantidad) WHERE variante_id = X stock(variante, almacén) = SUM(cantidad) WHERE variante_id = X AND almacen_id = Y

En el backend esto vive en apps/principal/services.py (stock_variante / stock_por_almacen), y en la base de datos hay una vista equivalente, v_stock_variante, que hace exactamente el mismo cálculo con SQL — se usa desde v_stock_bajo para saber qué reponer.

Ejemplo paso a paso

La variante CUAD-100H (Cuaderno universitario, 100 hojas) tiene estos movimientos, en orden:

FechaTipoReferenciaCantidad
02/03compraRecepción orden #14+200
05/03ventaPedido #881−12
06/03ventaPedido #883−45
10/03mermaCajas dañadas en almacén−3
18/03ajusteConteo físico (sobraban 2)+2
Stock actual (suma de todo lo anterior)142

Nadie escribió "142" en ningún sitio: es 200 − 12 − 45 − 3 + 2. Si mañana entra una nueva compra, el número cambia solo, sin tocar ninguna fila anterior.

Por qué no se guarda como un contador

Concurrencia: si dos personas confirman un pedido de la misma variante al mismo tiempo, un contador con UPDATE stock = stock - N puede perder una de las dos restas. Sumar filas nuevas no tiene ese problema.
Auditoría: con un contador, si el número "no cuadra" no hay forma de saber por qué. Con movimientos, se puede reconstruir exactamente de dónde salió o entró cada unidad.
Consistencia: el mismo cálculo sirve para el stock total, el stock por almacén, el valor de inventario (stock × costo) y la rotación — todo deriva de la misma tabla, no hay varias fuentes que se puedan desincronizar entre sí.

Dónde se usa en la práctica

Confirmar un pedido (services.confirmar_pedido): antes de nada, verifica que haya stock suficiente en todas las líneas; si falta, no confirma nada (409, todo o nada) — no descuenta unas líneas sí y otras no.
Recibir una orden de compra (services.recibir_orden): genera un movimiento de entrada por cada línea recibida, en una sola transacción.
Reponer stock (Inventario y Reportes): la vista v_stock_bajo compara el stock calculado contra el mínimo de cada variante.
Valor de inventario y rotación (Reportes): suman stock × costo variante por variante, y buscan la última venta (Max(fecha) con tipo=venta) para saber qué lleva tiempo sin moverse.

Rendimiento

movimiento_inventario crece sin límite (nunca se borra nada), así que sumar sobre ella podría volverse lento con el tiempo. Por eso tiene índices dedicados: idx_mov_variante y idx_mov_fecha para las sumas y filtros por periodo, y un índice parcial idx_mov_variante_fecha_venta (solo filas de tipo venta) pensado específicamente para "última venta de esta variante" — la consulta más pesada de rotación/stock parado, porque sin él implica revisar todo el historial de cada SKU.

El modelo completo de la tabla está en Esquema de datos; el contrato de la API que lo expone, en API del backend; y la explicación en lenguaje de negocio, sin código, en Almacén / Inventario.