Skip to content

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.py ya extrae lo reusable de ellas.
  • scripts/01-04 de existencia_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-*.md en por_ordenar) — el contenido relevante ya está fusionado en este index.md y en docs/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):

  1. Póliza de inventario/memo — partida doble completa, ambas líneas tagueadas con Pd_Referencia = Gr_Folio:
  2. Cargo 10500.012.003 "Salida Por Consumo Interno"
  3. Abono 10600.012.003 "Salida Por Consumo Interno Contra"
  4. Se cancelan entre sí, no mueven valor — es control/trazabilidad de unidades, no de dinero real.

  5. Póliza de gasto real — Cargo a la cuenta de gasto (root 6xxx o 1140.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 contra Gasto_Registro ni 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.