consumo-interno-fifo
Reporte de movimientos de inventario tipo Kardex: entradas y salidas "reales" de negocio en un periodo (compra, venta, consumo interno, producción, merma, ajustes...), reconciliado al 100% contra existencia a una fecha. Incluye una segunda capa que liga cada salida al UUID del CFDI de compra que la respalda, vía FIFO.
Origen: exploración local (~/por_ordenar/exploracion/existencia_a_una_fecha/ y
~/por_ordenar/exploracion/consumo_interno_trazabilidad/fifo_lib.py), validada al 100%
contra enero 2026 y promovida a este repo el 2026-08-24. La metodología completa
(clasificación de Tm_Cve_Tipo_Movimiento, Método C de existencia a una fecha,
resultados de validación) ya vivía condensada en
Inventario — este proyecto es el código que la implementa.
Qué hace hoy
V1 — Movimientos en un periodo (scripts/movimientos_lib.py +
scripts/streamlit_app_movimientos.py): lista los movimientos efectivos de inventario
del periodo, clasificados en Entrada/Salida + categoría de negocio, reconciliados contra
existencia_inicial + neto_movimientos == existencia_final (Método C). Enero 2026:
22,090 filas efectivas, 7,179/7,179 productos cuadran exacto (100%).
Deliberadamente excluye traspasos/transferencias internas (transferencia entre
sucursales, entrada/salida de tránsito, traspaso entre almacenes, traspaso a
producción — tipos 100-113/500-513) porque netean exactamente a 0 por producto
en el periodo: son reacomodos, no movimiento de negocio. Ver el detalle de tipos en
Inventario.
V2 — UUID de compra vía FIFO (scripts/movimientos_uuid_lib.py +
scripts/streamlit_app_uuid_fifo.py): extiende V1, asignando cada salida a la capa de
compra más antigua disponible (FIFO on the fly: semilla reconstruida hacia atrás +
capas nuevas del periodo + cola línea a línea) y resolviendo su UUID de CFDI. Enero
2026: 18,953 salidas → 19,646 filas de asignación, 0 descuadres (tolerancia 0.5);
52.2% de las unidades salidas tienen respaldo de compra real (el resto es
mayoritariamente producto fabricado — Trivasa produce, no solo revende).
V3 — Baseline nativo + reconciliación contable (scripts/07_baseline_rpmv008co.py
+ 08_baseline_existencias.py, 2026-08-24): reconstruye el reporte nativo
RPMV008_CO.asp de "CONSUMO INTERNO" línea a línea (fuente independiente de
Movimiento) y liga cada folio hasta su póliza contable real. Ver
"Round 2" más abajo para el detalle completo.
Por qué se está retomando (2026-08-24)
El reporte V1 excluye traspasos/transferencias porque para el propósito con el que se construyó (reconciliar contra existencia) es correcto ignorarlos — netean a 0. Pero eso significa que hoy no hay ninguna vista de los movimientos internos en sí (a qué almacén/sucursal se movió cada traspaso, cuánto tiempo quedó "en tránsito", etc.) — quedaron fuera del alcance original, no analizados. Ese es el punto de partida de este proyecto: usar V1/V2 como base y decidir cómo incorporar los movimientos internos como su propia vista (probablemente una tabla aparte, ya que no tiene sentido mezclarlos con el neto de negocio de V1 sin romper la reconciliación que hoy cuadra al 100%).
Cómo correr
cd docs/proyectos/consumo-interno-fifo/scripts
streamlit run streamlit_app_movimientos.py --server.port 8504 # V1 - kardex
streamlit run streamlit_app_uuid_fifo.py --server.port 8505 # V2 - UUID FIFO
O vía CLI (genera CSV en output/, relativo al cwd):
python3 05_movimientos_periodo.py 2026-01-01 2026-01-31
python3 06_movimientos_uuid_fifo.py 2026-01-01 2026-01-31
V3 — Baseline del reporte nativo + existencias (07_baseline_rpmv008co.py
+ 08_baseline_existencias.py, promovidos de consumo_interno_round2
2026-08-24): reconstruye el reporte nativo RPMV008_CO.asp (CONSUMO INTERNO
por producto, resuelto vía skill mpro-reporte-asp) línea a línea, sin pasar
por Movimiento — es la fuente de verdad independiente contra la que se
valida V1. Le agrega existencia inicial/final del periodo (mismo Método C).
Marzo 2026: 4,295 líneas, 920 productos, $7,422,687.48 total.
python3 07_baseline_rpmv008co.py --fecha-ini 2026-03-01 --fecha-fin 2026-03-31
python3 08_baseline_existencias.py --fecha-ini 2026-03-01 --fecha-fin 2026-03-31 \
--baseline output/baseline_rpmv008co_2026-03-01_2026-03-31.csv
Dependencias: pandas, streamlit, sqlalchemy, pymssql, openpyxl (Excel export).
Sin requirements.txt propio todavía — no existía en el origen tampoco.
Conexión — gotcha de IP
El código original importaba connection_200_trivasadb3, que ya no existe: TRIVASADB3
se movió de .200 a .205 el 2026-08-13 (ver
Servidores y bases). Se corrigió al
promover: los tres módulos (movimientos_lib.py, fifo_lib.py,
movimientos_uuid_lib.py indirectamente) ahora importan connection_205_trivasadb3,
copiado a scripts/ desde trivasa-bi-core/connections/ (sin symlink, por convención
de este repo — ver CLAUDE.md).
Estructura del código
| Archivo | Qué es |
|---|---|
connection_205_trivasadb3.py |
Conexión (engine, q(), show()), copiada de trivasa-bi-core |
movimientos_lib.py |
Núcleo V1: extracción, clasificación, reconciliación |
fifo_lib.py |
Primitivas FIFO compartidas (capas de compra, cola, resolución de UUID) |
movimientos_uuid_lib.py |
Núcleo V2: orquesta movimientos_lib + fifo_lib línea a línea |
05_movimientos_periodo.py |
CLI V1 → CSV |
06_movimientos_uuid_fifo.py |
CLI V2 → CSV |
streamlit_app_movimientos.py |
Reporte interactivo V1 |
streamlit_app_uuid_fifo.py |
Reporte interactivo V2 |
07_baseline_rpmv008co.py |
V3: baseline independiente desde el reporte nativo RPMV008_CO.asp |
08_baseline_existencias.py |
V3: le agrega existencia inicial/final (Método C) al baseline anterior |
Nota de refactor al promover: en el origen, streamlit_app.py (V1) importaba sus
libs desde una carpeta scripts/ hermana, y movimientos_uuid_lib.py importaba
fifo_lib.py desde el directorio del proyecto vecino consumo_interno_trazabilidad/
vía sys.path manual. Aquí todo vive plano en un solo scripts/, así que ese
sys.path cruzado se eliminó — no cambió ninguna lógica de negocio.
No promovido (se queda en ~/por_ordenar/)
- Los CSV de salida ya generados para enero 2026 (
output/*.csv). - Las variantes exploratorias previas en
consumo_interno_trazabilidad/(streamlit_app_normalizado.py,_lineas.py,_caso_simple.py) — caminos previos a la versión validada al 100%,fifo_lib.pyya extrae lo reusable de ellas. scripts/01-04deexistencia_a_una_fecha/(exploración y validación del Método A/B/C contra ground truth de MPRO) — el resultado ya está sintetizado en Inventario; si hace falta reproducir la validación desde cero, están en~/por_ordenar/.- Los 3 ADR originales (
docs/decisions/2026-07-*.mdenpor_ordenar) — el contenido relevante ya está fusionado en esteindex.mdy endocs/schema/inventario.md.
Round 2 (2026-08-24) — primera corrida en vivo + reconciliación contable
Primera vez que V1/V2 se corren en vivo desde esta ubicación contra .205
(PROGRESS.md traía "no probado en vivo" desde la promoción). Corrido para
marzo 2026 y para el semestre completo (2026-01-01 a 2026-06-30), en ambos
casos 100% reconciliado: 7,306 productos (marzo) y 8,568 productos
(semestre), 0 sin cuadrar en los dos, tolerancia 0.5. V2 (FIFO/UUID) también
sin descuadres: 105,857 salidas del semestre, cobertura FIFO exacta en las
105,857 (0 con magnitud distinta a la original).
Fix: 6 tipos de movimiento que colaban como "SIN CLASIFICAR"
Al correr el semestre completo aparecieron 053, 201, 903, 905,
981, 983 sin clasificar (36-49 filas por corte, según el periodo). Los
seis son anulaciones de tipos ya mapeados, mismo patrón que las demás
(invierten el signo del tipo que anulan):
| Tipo | Descripción | Anula a | Signo resultante |
|---|---|---|---|
201 |
ANULACION ENTRADA POR RECHAZOS | 200 Rechazo (Entrada) |
Salida |
903 |
ANULACION ENTRADA AJUSTE INV. FISICO | 902 Ajuste inv. físico (Entrada) |
Salida |
981 |
ANULACION SALIDA POR AJUSTE DE MP BLOQUERA | 980 Ajuste MP bloquera (Salida) |
Entrada |
983 |
ANULACION SALIDA POR AJUSTE DE MP VIGUERA | 982 Ajuste MP viguera (Salida) |
Entrada |
053 |
ANULACION ENTRADA POR CONSIGNACION | 052 Consignación (Entrada) |
Salida |
905 |
ANULACION SALIDA AJUSTE INV. FISICO | 904 Ajuste inv. físico (Salida) |
Entrada |
Agregados a TIPOS_ANULACION_REALES y a CLASIFICACION en
scripts/movimientos_lib.py. No afectaban la reconciliación en ningún caso
(el neto de Movimiento ya los traía sumados, clasificados o no) — el
cambio es de higiene del listado, verificado con reconciliar_y_ajustar
dando 0 sin cuadrar antes y después, en ambos periodos.
Ejemplo trabajado: TARIMA DE PINO (0000000812) — no es combustible, cruza varias capas
Para explicarle a alguien sin contexto técnico cómo funciona el FIFO on the fly (semilla + capas + un consumo repartido en más de un UUID), este producto es más ilustrativo que el combustible porque las cantidades son enteras y las compras están muy espaciadas en el tiempo:
- Semilla al 2026-03-01: existencia real al 28-feb (6,830.516 unidades para gas LP en el ejemplo original; para tarima, 34,665 unidades al 2026-01-01) se reconstruye tomando las compras más recientes primero, hacia atrás, hasta completar esa cantidad — lo que sobrevive hoy son las compras más nuevas, porque las viejas ya se consumieron (FIFO).
- Un consumo que cruza 4 capas: movimiento
05-0927732(20-mar-2026), 1,000 tarimas consumidas de un jalón, repartidas en 4 compras de hace un año (mar-abr 2025) porque cada una se fue agotando:
| Cantidad | Compra origen | Fecha compra | UUID |
|---|---|---|---|
| 87 | 05-0026524 | 19-mar-2025 | 88BBECFA-... |
| 300 | 23-0005943 | 26-mar-2025 | 871B7E9A-... |
| 500 | 05-0026646 | 04-abr-2025 | D1BD53A8-... |
| 113 | 05-0026647 | 04-abr-2025 | 0C022EBE-... |
Y 10 días después (05-0931468, 30-mar), otro consumo de 1,000 retoma
la cola exactamente donde la dejó el anterior (empieza con los 387
restantes del folio 05-0026647) — evidencia de que el FIFO lleva
memoria entre movimientos, no vuelve a empezar.
- Validado para el producto completo, semestre 2026-01 a 2026-06: 50
filas de asignación FIFO de origen CONSUMO_INTERNO, 100% con capa de
compra y 100% con UUID resuelto, 16 de esas 50 cruzaron 2-4 capas.
- Cruce contra .207 (producción real, TRIVASADB): existencia
Método C al 2026-01-01 y al 2026-06-30 idéntica byte a byte entre .207
y .205 (34,665 → 34,529) — confirma que .205 no divergió de
producción para este producto en el semestre.
Cómo se liga Consumo_Interno con su póliza contable (round2 avanza el pendiente heredado)
Camino de join confirmado (no estaba documentado en ningún lado, ni en
calidad-de-datos.md ni aquí):
Consumo_Interno.Ci_Folio
→ Gasto_Registro.Gr_Documento (Gr_Tabla = 'CONSUMO_INTERNO')
→ Gasto_Registro.Gr_Folio
→ Poliza_Control.Pc_Documento (Pc_Tabla = 'GASTO_REGISTRO')
→ Poliza_Control.Pl_Folio
→ Poliza_Detalle
Cada folio de Gasto_Registro de origen CONSUMO_INTERNO genera dos
pólizas separadas (confirma y detalla lo ya apuntado en
Calidad de datos
y en Layout de gastos):
- Póliza de inventario/memo — partida doble completa, ambas líneas
tagueadas con
Pd_Referencia = Gr_Folio: - Cargo
10500.012.003"Salida Por Consumo Interno" - Abono
10600.012.003"Salida Por Consumo Interno Contra" -
Se cancelan entre sí, no mueven valor — es control/trazabilidad de unidades, no de dinero real.
-
Póliza de gasto real — Cargo a la cuenta de gasto (root
6xxxo1140.020.xxx, ej.6300.001.004.017"Herramientas Y Productos Deteriorados", la más usada para tarima: 138/195 folios), pero el Abono va contra una cuenta de Activo de almacén,1140.010.XXX.007"Salida Consumo Interno" (una por sucursal/almacén, ej.1140.010.012.007) — no contraGasto_Registroni contra CxP.
Por qué layout-gastos documentaba "el
Abono siempre sale en 0": la línea de Abono de esta póliza no lleva
Pd_Referencia — está agregada a nivel de almacén para toda la
póliza del día, no por folio individual. Cualquier query que filtre
Pd_Referencia = Gr_Folio (como la usada ahí) solo va a ver la línea
de Cargo — el Abono real existe, pero hay que traer la póliza completa
(Pl_Folio, sin filtrar por referencia) para verlo.
Confirma que contablemente el consumo interno se registra igual que
cualquier salida de almacén: se da de baja el Activo (inventario) y se
reconoce el Gasto — la póliza 10500/10600 es aparte, solo de
control.
Hueco confirmado: 35% de Consumo_Interno no genera movimiento 060
Cruce folio a folio, marzo 2026: de 3,063 folios de Consumo_Interno (no
cancelados), solo 1,976 (65%) tienen un Movimiento de tipo 060
("Consumo interno") con Mv_Documento = Ci_Folio. Los 1,087 restantes
(35%, $3.32M de $7.42M del mes) no tienen NINGÚN movimiento tipo 060 —
confirmado contra un folio real (05-0143433, Es_Cve_Estado = 'AC',
importe real): cero filas en Movimiento para ese documento.
El hueco está muy concentrado, no repartido: 90% de esas filas
(2,006 de 2,319) caen en almacén 0030 "REFACCIONES". Causa encontrada:
ese consumo sí genera movimiento, pero de tipo 800 "Refacciones /
consumo" en vez de 060 — y 800 no es exclusivo de
CONSUMO_INTERNO, también lo genera ORDEN_SERVICIO (confirmado: de 10
movimientos tipo 800 de muestra, 9 traían Mv_Tabla = 'ORDEN_SERVICIO'
y 1 Mv_Tabla = 'CONSUMO_INTERNO'). Consecuencia práctica: para ligar
Consumo_Interno a Movimiento hay que filtrar por
Mv_Tabla = 'CONSUMO_INTERNO' AND Mv_Documento = Ci_Folio, nunca solo por
Tm_Cve_Tipo_Movimiento = '060' — el tipo de movimiento no identifica el
origen de forma confiable para este caso.
Consistente con esto, al mirar la firma de categorías de movimiento de los
920 productos con consumo interno en marzo 2026: solo 3 tienen "Consumo
interno" (060) como categoría de salida; 882 de 920 (95.9%) tienen
"Refacciones / consumo" (800) como su única categoría de salida. La
clasificación de negocio por Tm_Cve_Tipo_Movimiento es correcta para
saber si algo es entrada o salida, pero no basta para saber "esto vino de
Consumo_Interno" — para eso hace falta Mv_Tabla/Mv_Documento.
Sigue pendiente (no resuelto en esta ronda): confirmar si ese 35% sin
Movimiento propio (aparte del caso ORDEN_SERVICIO de almacén
REFACCIONES) tiene alguna otra vía de rastreo, o si de verdad es un hueco
de datos para esos folios específicos.
Pendiente heredado — reconciliación Consumo_Interno → Contabilidad
Trabajo aparte, sin código propio promovido todavía (vivía como hipótesis en
por_ordenar/reports/consumo_interno_reconciliation.md): ligar cada registro de
Consumo_Interno con sus movimientos de sistema para que contabilidad rastree de
dónde salió cada producto consumido internamente. Hipótesis de whitelist (firma de
movimientos ⊆ {Compra, Traspaso, Transferencia, Consumo_Interno}) sin validar contra
los 47 Tm_Cve_Tipo_Movimiento distintos que tocan los 950 productos con consumo
interno activo. Casos complejos (consumo interno + fabricación, + merma) sin empezar.
Puede converger con el punto de "movimientos internos" de arriba — mismo dominio,
retomar juntos si se avanza en uno.
Fuente
Código original: ~/por_ordenar/exploracion/existencia_a_una_fecha/ (V1 + V2 + Método
C) y ~/por_ordenar/exploracion/consumo_interno_trazabilidad/fifo_lib.py (primitivas
FIFO). Validado 100% para enero 2026, empresa 0001, contra TRIVASADB3.