# Cuándo confiar en la existencia — propuesta

Estado: **propuesta, sin implementar**. Escrita el 2026-08-01.

## El problema

El sugerido de compra descuenta la existencia. Si la existencia no refleja la bodega, el sugerido
sale mal, y hoy no refleja la bodega en buena parte del catálogo. Medido sobre 300 productos con
venta habitual de La Chimalteca (tienda 2, últimos 90 días):

- **54% tuvo existencia negativa** algún día. Un saldo negativo no es un dato bajo: es un dato roto.
- De los 136 coherentes, sólo 3 tuvieron algún día en cero, y los tres con el saldo clavado en 0
  mientras vendían — otro síntoma de descuadre, no un quiebre con reposición.

Por eso el módulo ya tiene la opción de **ignorar las existencias**. Funciona, pero es un
interruptor global: o se confía en todo o en nada.

## Por qué el interruptor global se queda corto

La confianza no llega toda junta: llega **producto por producto**, conforme avanzan los conteos.
La Chimalteca empezó con físicos en julio-2026 (912 líneas, 907 productos; no hay actividad de
físicos antes) y el efecto ya se mide:

| | productos | con saldo negativo hoy |
|---|---|---|
| con físico reciente | 23 | **0 (0 %)** |
| sin físico reciente | 577 | 220 (**38 %**) |

La muestra de contados es chica todavía, pero apunta claro: donde cuentan, la existencia queda
coherente. Con un interruptor global, esos 23 productos ya saneados siguen sin usarse porque el
resto del catálogo no lo está. Y cuando el catálogo mejore, alguien tiene que acordarse de ir a
activar la opción.

## Qué se propone

### 1. Un estado de confianza por producto

**La confianza no expira por calendario, expira por anomalía.** Un producto es confiable si fue
contado y **desde ese conteo** no hizo nada imposible:

- `sin contar` — no tiene físico ni selectivo registrado.
- `confiable` — contado, y sin anomalías posteriores al conteo.
- `descuadrado` — tuvo una anomalía después del último conteo.

Anomalía es cualquiera de estas dos, con la misma definición que ya usa el dashboard del ML
(ver más abajo):

- existencia negativa;
- vendió teniendo existencia ≤ 0 (`Ventas_Dia > 0 AND Stock_Actual <= 0`).

Se prefiere esto a un umbral de días porque no hay número que defender: un producto contado hace
cinco meses que se ha portado bien es más confiable que uno contado hace dos semanas que ya vendió
sin stock. Como respaldo queda un solo corte por calendario: si el último conteo es anterior al
último **físico total** —que por la SAT toca cada 6 meses—, el producto vuelve a `sin contar`.

Los datos salen de `detmov`, que es transaccional y permite reconstruir el saldo de cualquier
fecha. Está verificado: el saldo reconstruido coincide **al decimal con `existencias_tienda` en 80
de 80 productos**. Los conteos son los `tipodoc` cuyo movimiento tiene `esfisico = 'S'` (acá, 1 y 6).

`maeexi` **no** sirve como fuente: no se actualiza sola, se llena a mano o por el proceso de
mínimos y máximos que todavía está pendiente.

### 2. Qué hace el módulo con eso

- `confiable` → descuenta la existencia, como hoy.
- `descuadrado` / `sin contar` → la ignora, que es exactamente lo que hoy hace el interruptor, y lo
  dice en el detalle con el motivo ("vendió sin existencia el 14-jul").
- El **interruptor global se queda** como override en ambos sentidos, para forzar una cosa u otra
  sin discutir con el sistema.

### 3. El conteo selectivo antes del pedido

Es el flujo que ya se le sugirió al cliente —hacer un selectivo del proveedor los días previos a
pedirle— y el módulo es justo donde ocurre ese momento: la cotización se arma por proveedor y el
selectivo se hace por proveedor.

Al abrir la cotización de un proveedor, el módulo puede decir cuántos de sus productos no tienen
existencia confiable y **generar la lista de conteo** para ese selectivo. Se cuenta, se registra el
físico, se recalcula, y el pedido sale con existencias que valen. Convierte el indicador en una
acción, en el único momento en que a alguien le importa contar.

### 4. La visión global no se duplica

El proyecto de ML (repo `Demo-AI`) ya tiene el dashboard de sanidad del inventario y su evolución:
`/api/dashboard/inventory_health` y `/api/dashboard/data_quality_inventory`, con `rows_negative_stock`,
`rows_sales_without_stock`, `rows_stock_without_sales_30d` y demás, cada uno con conteo y porcentaje.

El reparto es: **el ML reporta, el módulo decide**. El módulo no construye otro dashboard; enlaza al
del ML donde esté disponible. Pero sí calcula el estado por su cuenta desde `detmov`, porque el ML
no va a estar al alcance de todos los clientes.

Las definiciones se toman del ML para que, donde estén los dos, den el mismo número. Ya coinciden
sin haberlo coordinado: el ML suma el stock con `GREATEST(Stock_Actual, 0)` y el módulo con
`Math.max(0, existencia)`.

## Lo que falta decidir

- Dónde se muestra el estado en el listado: una columna propia o junto a la existencia.
- Si la lista de conteo se genera desde el módulo o se delega al proceso de físicos.
- Volver a medir la tabla de arriba en unas semanas: 23 productos contados son pocos para dar la
  correlación por probada.
