Skip to content

layout-gastos — estado vivo

2026-09-05 — HITO: 16/17, conciliación XML por origen sin pasar por config, 2 bugs + 1 gotcha nuevos

Extiende CONT-4/CONT-5 de ayer (solo config 0450) a un panorama completo por origen — trabaja directo contra Gasto_Registro/Gasto_Registro_ Documento/Comprobante_Digital, para GASTO_DIRECTO/VIAJE/ORDEN_ COMPRA/CONTROL_COMBUSTIBLE (los 4 que sí deberían llevar XML).

Tres bugs reales corregidos (detalle completo en Calidad de datos → Comprobante_Digital): 1. Cd_Documento tiene 2 formatos de longitud (14 y 18) — filtrar solo LEN=18 perdía ~23% de los XML ligados. 2. Aceptar ambos formatos duplica el mismo XML si no se deduplica por (FOLIO, GRD_ID, XML_UUID). 3. Cd_Monto viene en la moneda ORIGINAL del CFDI, nunca convertido a MXN — comparar el importe ya convertido daba diferencias de hasta 18x en documentos USD/EUR.

Con las 3 correcciones: VIAJE/ORDEN_COMPRA/CONTROL_COMBUSTIBLE en 100%/100% (cobertura y cuadre de monto, enero 2026). GASTO_DIRECTO sigue con el hueco real: 85.94% cobertura (35.04% del importe), 89.30% de eso cuadra en monto.

Gotcha nuevo, investigando el residual de GASTO_DIRECTO: 31/58 XML_UUID sin cuadrar son CFDI tipo RETENCIONES (monto real en fila hermana bajo Cd_Tabla='CONSTANCIA_RETENCION', ver Calidad de datos) — 16 cuadran exacto con factor 90% (ISR de honorarios). Aplicado en 17: 27.56% → 31.40% de importe que cuadra en GASTO_DIRECTO.

Probado también el grano de componente conexa (union-find, idea de ~/proyectos/conciliacion-master/adjuntar-xml/, otra sesión) — efecto marginal (+0.17pp), el residual de este proyecto no era mayormente por facturas repartidas en varios documentos. Reimplementado en versión ligera (sin Cd_XML) por límite de RAM del entorno (~5GB, confirmado con free -h que la versión completa agota memoria sin traceback).

Probado en 3 rangos de fecha (enero, enero-marzo, enero-mayo 2026): el % total no varía mucho (~40-41%), pero CONTROL_COMBUSTIBLE cae a 73.49% en enero-mayo (era 100% en los rangos cortos) — nuevo, sin investigar.

Ideas de adjuntar-xml/layout-contabilidad/layout_gastos (parseo de Cd_XML para complementos, Cuenta_X_Pagar para combustible, CFDI intercompañía) se evaluaron por razonamiento, no se probaron empíricamente contra esta base — quedan como hipótesis documentadas, no como hallazgo.

Código: 16_reconciliacion_xml_por_origen.py, 17_reconciliacion_xml_grano_grupo.py. Detalle completo en index.md de este mismo repo y en PROGRESS.md de conciliacion-master/layout-gastos/ (fuera de este repo).

2026-09-04 — CONT-4/CONT-5 (exploratorio, solo local): conciliación 1:1 contra XML

Extiende la reconstrucción vía Poliza_Configuracion de ayer (14) a una conciliación real contra XML (CFDI) — dos reportes Streamlit nuevos, solo corriendo en local (streamlit_venv/, puerto 8506, 127.0.0.1, no expuesto a red), no promovidos a streamlit-reportes.

15_reconciliacion_xml_via_poliza_configuracion.py — grano (FOLIO, GRD_ID), el sugerido al comparar contra (FOLIO, CUENTA_ CONTABLE) (ver entrada de ayer): el XML liga a Comprobante_Digital. Cd_Documento, decodificado Gr_Folio (10) + Grd_ID con padding (4) + sufijo casi siempre fijo '0001' (4) — confirmado contra datos reales (74,347/74,352 XML con ese sufijo). Es el documento, no la cuenta.

Dos correcciones reales encontradas armando el script (ninguna cambia la metodología de 14, sí la de esta conciliación nueva):

  1. Contra qué comparar el monto: el primer intento comparó CARGO (reconstruido vía Poliza_Configuracion — el importe neto de gasto, sin IVA, que es el que cuadra contra Poliza_Detalle) contra Cd_Monto (el total bruto del CFDI, con IVA) — 2.75% de cuadre, casi todo desviado por el impuesto, que vive en otro renglón de la póliza. Corregido: comparar Grd_Precio_Neto_Importe (campo nativo de Gasto_Registro_Documento, ya incluye impuesto — misma lógica que IMPORTE_FOLIO_SQL de layout_gastos_lib.py pero a nivel documento) contra Cd_Monto.
  2. Un XML puede repetirse en varios Grd_ID: 1.83% de los XML_UUID aparecen en más de un documento — una factura partida en N líneas idénticas (ej. cargo recurrente de electricidad dividido en 16 líneas de $237 cada una) queda capturada como N documentos que apuntan al mismo Comprobante_Digital — Cd_Monto en esos casos es el total del CFDI completo, no de un solo documento. Hay que sumar el importe de todos los documentos que comparten un XML_UUID antes de comparar.

Con las dos correcciones: 97.76% (1,922/1,966 XML_UUID) cuadra exacto contra Cd_Monto. Residual no investigado a fondo — parece concentrado en documentos en USD (valores repetidos idénticos entre folios distintos, compatible con doble aplicación de tipo de cambio).

Hallazgo adicional, no buscado: una sola Poliza (config 0450) puede consolidar varios folios Gasto_Registro distintos bajo el mismo Pl_Folio (confirmado con datos reales, 3 folios reales bajo 0000472999) — el Abono (pago a proveedor) vive a nivel de esa póliza consolidada, con Pd_Referencia = referencia de la factura del proveedor, no Gr_Folio. Por eso el universo de config 0450 siempre sale con Abono = 0 en CONT-1/CONT-2 también (no es nuevo, ya pasaba ahí, solo que nadie lo había explicado hasta ahora) — el Abono simplemente no es atribuible por folio con la información disponible para esta config.

Streamlit local — CONT-4 y CONT-5, misma pestaña de Reconciliación que CONT-1/CONT-2 (reusa resumen_por_folio()/resumen_qa() de layout_gastos_lib.py tal cual, sin cambios — opera a nivel folio, no depende del grano de la tabla de Datos):

  • CONT-4 — grano documento (FOLIO, GRD_ID, CUENTA), XML ligado 1:1.
  • CONT-5 — grano cuenta contable (FOLIO, CUENTA), construido a propósito junto a CONT-4 para comparar: coincide con el documento en 86.28% de los folios (ver entrada de ayer), pero el 13.72% que diverge tiene más XML ligado (81.77% vs 61.92%) — falla justo donde más importa. Cada fila trae N_DOCUMENTOS/N_XML/AMBIGUO para que se vea explícito cuándo el grano cuenta no corresponde a un solo XML.

Ambos conservan CARGO/ABONO como columnas separadas (mismo formato que reporte_completo()) — ABONO sale en 0 para toda esta config, ver hallazgo arriba, no es un bug de estos reportes nuevos.

Código: poliza_configuracion_lib.py (reconstruir_config() movido aquí desde 14, más abono_via_referencia()/xml_gasto_registro() nuevas), layout_gastos_config_lib.py (reporte_cont4()/reporte_cont5()), reporte_ui_config.py, pages/3_CONT-4_...py/4_CONT-5_...py. Validado con AppTest (sin excepciones, filtros probados) antes de correr el servidor real.

Cobertura: solo config 0450, igual que 14 — no reemplaza CONT-1/CONT-2, es un complemento exploratorio. Pendiente: generalizar a más configs, investigar el residual de USD, decidir si esto se promueve a producción o se queda como herramienta de diagnóstico local.

2026-09-03 (continuación 6) — Rank-pairing vs. reconstrucción real vía Poliza_Configuracion (14, exploratorio)

Pregunta que motivó esto: el rank-pairing de 07/11/12 (ordenar por valor dentro de (FOLIO, CECO) y emparejar por posición) empareja "por casualidad" cuando hay más de una línea de Grc_ID — ¿se puede arreglar con las tablas de Poliza_Configuracion (configuracion-polizas.md, poliza-explor)?

Sí, en su mayoría — y con un límite duro medido, no supuesto. Poliza_Configuracion_Detalle.Pcd_Relacion ya hace el JOIN real a Gasto_Registro_Control por (Gr_Folio, Grd_ID) que usa el motor de MPRO para generar cada renglón de póliza. Reconstruyendo esa query tal cual (14_reconstruccion_via_poliza_configuracion.py, función reconstruir_config()) para la config 0450 (la dominante — GASTO_ DIRECTO/CONTROL_COMBUSTIBLE/VIAJE/ORDEN_COMPRA, 3,403 de ~3,600 folios de enero-marzo 2026):

  1. Validación: la reconstrucción, sumada por (FOLIO,CECO), cuadra 100.00% contra Poliza_Detalle real (5,171/5,171) — confirma que es el mismo JOIN que usó el motor, no una aproximación.
  2. Cuantificación de la ambigüedad: de las 436 combinaciones (FOLIO,CECO) con más de una línea de Grc_ID (las únicas donde rank-pairing podría fallar), 17 se resuelven en renglones distintos de la config (distinción real, recuperable) y 419 caen en el mismo renglón (el motor las suma con SUM(...) GROUP BY Centro_Costo, sin agrupar también por Tipo_Gasto). De esas 419, solo 20 tienen además más de un Tipo_Gasto real — esa es la ambigüedad irreducible: ni el propio motor de MPRO puede recuperarla, la descartó al generar el dato.

Neto: 5,151/5,171 (99.61%) de las combinaciones quedan resueltas con certeza real, no una suposición de orden — el límite duro de rank-pairing para esta config es 0.39%, no un "residual desconocido".

Alcance: solo la config 0450 (1 de ~67 usadas en el universo del proyecto). No reemplaza 11/12 todavía — es la prueba de que la técnica funciona y cuánto resuelve, antes de invertir en generalizarla a las ~66 configs restantes. Ver index.md para el detalle completo y Pendiente para el siguiente paso.

2026-09-03 (continuación 5) — Dos hallazgos de poliza-explor aplicados: filtro de CONSUMO_INTERNO y causa raíz de COMPROBACION_GASTO

trivasa-context/docs/proyectos/poliza-explor/configuracion-polizas.md (sesión 2026-09-03, documenta cómo CT001/Poliza_Configuracion generan las pólizas) resolvió dos cosas que este proyecto tenía como "pendiente" o "sin explicar":

1. Filtro de doble póliza de CONSUMO_INTERNO, movido de Pl_Comentario a Poliza_Configuracion. poliza-explor explicó la causa raíz del patrón de doble póliza: no es una anomalía, son dos configuraciones activas distintas sobre la misma Pc_Tabla, una marcada (CUENTAS DE ORDEN) en Pc_Descripcion — y recomendó filtrar ahí, no en Pl_Comentario (texto derivado, más frágil). Cambiado en 12 (query POLIZA_SQL_CI): join a Poliza_Configuracion por Pl_Configuracion, filtro sobre Pc_Descripcion en vez de Pl_Comentario.

Antes de aplicarlo se validó contra datos reales (enero-marzo 2026, 811 pólizas de CONSUMO_INTERNO) que ambos criterios dan el mismo resultado exacto — cero discrepancias — pero solo usando las 4 variantes de abreviatura que ya traía el filtro viejo (CUENTAS DE ORDEN / CTS ORDEN / CTS DE ORDEN / CUENTA ORDEN); la primera prueba con solo CUENTAS DE ORDEN sobre Pc_Descripcion dio 42 discrepancias porque las configs 0296/0481 usan la abreviatura (CTS ORDEN)/(CTS DE ORDEN) en su descripción — el filtro viejo ya la cubría, el nuevo tuvo que igualarla explícitamente. Corrido 12 completo después del cambio: mismo resultado exacto, 3,072/3,045 (99.12%) enero 2026 — idéntico al ya documentado.

2. Causa raíz confirmada del residual COMPROBACION_GASTO (folio 01-0035339, CeCo 000501, $4.9489 — visto como fila huérfana left_only en las corridas de 07/enero-marzo). poliza-explor lo rastreó hasta la config 0394 ("COMPROBACION DE GASTOS NACIONAL"): sus renglones de cargo no cubren el CeCo 000501 PLANTA CONCRETERA en ninguna condición — es un hueco estructural real en la regla contable, no ruido de la técnica de rank-pairing de este proyecto. La póliza real (0000478288) queda descuadrada $5.36 en el sistema fuente: $4.95 por este hueco + $0.41 de redondeo del prorrateo entre 80 renglones. No es un caso a "arreglar" en este reporte — es exactamente el tipo de hueco que el reporte está diseñado para exponer.

2026-09-03 (continuación 4) — Python 3.14 en el venv de exploración: no es un límite, era el pin de paquete

Después de pinnear el venv a las versiones exactas de producción (entrada anterior) surgió la duda de si el dolor de compilar desde source era culpa de Python 3.14 en sí. No lo es — lo que determina si hay wheel precompilado es la combinación versión-de-paquete × versión-de-Python, no Python solo. Confirmado contra PyPI directo (no supuesto): pandas== 2.2.3/pymssql==2.3.2 (las versiones pinneadas, de antes de que 3.14 existiera) nunca van a tener wheel para cp314 — quedaron congeladas en el pasado —, pero las versiones más nuevas de esos mismos paquetes (pandas==3.0.5, pymssql==2.4.0, psycopg2-binary==2.9.12) sí publican wheel manylinux para cp314 desde hace tiempo. streamlit, streamlit-aggrid, altair y sqlalchemy son puro Python (py3-none- any) — nunca les importa la versión de Python.

Bump hecho y revalidado en el venv de exploración (streamlit_venv/, Python 3.14.4 del host, sin cambio): pandas 2.2.3 → 3.0.5, pymssql 2.3.2 → 2.4.0, psycopg2-binary 2.9.10 → 2.9.12. Instalación: segundos, cero compilación, cero paquetes de sistema nuevos — contraste directo contra los ~20 minutos + Cython==3.0.10 + freetds-dev/libkrb5-dev/libssl-dev/libpq-dev que costó la entrada anterior con las versiones viejas.

pandas 2.x → 3.x es un salto de major version con cambios de comportamiento reales (copy-on-write permanente, dtype de string por default) — no un bump trivial como los otros dos. Se revalidó contra 07 (el script de validación canónico, rank-pairing + agregación) para los dos rangos ya documentados como baseline, resultado idéntico, cero diferencias:

  • Enero 2026: 7,691/7,691 (100.00%) — igual.
  • Enero-marzo 2026: 17,935/17,944 (99.95%), mismos 9 filas / 3 folios residuales de siempre (05-0182622, 23-0007297, 01-0035339) — igual.

Pendiente antes de considerar el bump listo para producción: esto solo revalida el camino de 07 (los 5 orígenes "normales"). 09/11/12/13 (CONSUMO_INTERNO, GASTO_REGISTRO_NOMINA, el reporte final) no se revalidaron todavía con pandas 3.x. streamlit-reportes (producción) no se tocó — sigue en python:3.12-slim + las versiones viejas pinneadas; este bump vive solo en el venv de exploración de este repo por ahora.

2026-09-03 (continuación 3) — Bump de Streamlit en producción + venv de exploración pinneado

Dos temas de infraestructura, ninguno cambia lógica de negocio del reporte:

1. streamlit==1.39.0 en producción → 1.63.0. Al promover CONT-1/ CONT-2 (entrada anterior) apareció TypeError: 'str' object cannot be interpreted as an integer en ambas páginas — width="stretch" (API nueva de Streamlit) no existe en 1.39.0, versión pinneada en streamlit-reportes sin que ningún commit ni doc explicara por qué se había bajado de una más nueva. Se decidió subir la versión (branch bump-streamlit-1.63) en vez de bajar el código nuevo a la API vieja. Bump reveló un segundo bug real, independiente: ModuleNotFoundError: openpyxl — faltaba en requirements.txt, enmascarado hasta entonces porque el bug de width fallaba antes en la ejecución del script. Ninguno de los dos se detectó con curl/chequeos HTTP (200 solo confirma que el shell estático responde, no que el script corre — Streamlit ejecuta el script real solo por WebSocket); se encontraron corriendo AppTest vía docker exec dentro del contenedor real, no en un venv local aparte. streamlit-aggrid también subió (1.0.5 → 1.2.1.post2): la vieja fija altair<5, choca con streamlit>=1.63 (pide altair>=5). Detalle de infraestructura completo en streamlit-reportes/index.md. Commits en streamlit-reportes: 3396a89 (fix width), 88da80e (bump streamlit), 4ec16b6 (bump aggrid), caa5f21 (fix openpyxl), 46d5309 (merge a main).

2. streamlit_venv/ de este repo de exploración, pinneado a las mismas versiones exactas que producción (streamlit==1.63.0, pandas==2.2.3, sqlalchemy==2.0.36, pymssql==2.3.2, streamlit-aggrid==1.2.1.post2, psycopg2-binary==2.9.10, openpyxl==3.1.5) — para que un AppTest local aquí sea representativo de lo que corre en Docker, no solo "casi igual". Este host (ctunlinux, donde vive tanto la exploración como producción) no tiene Python 3.12 disponible en repos (solo 3.14, sin deadsnakes/uv/pyenv) — se mantuvo Python 3.14 y se pinneó solo el lado de librerías, aceptando que pandas y pymssql compilen desde source (sin wheel prebuilt para una versión de Python tan nueva). Dos ajustes necesarios para que compilaran: Cython==3.0.10 pinneado (la 3.3.0, que se instala por default, rompe el parseo del .pyx de pymssql) y paquetes de sistema freetds-dev/ libkrb5-dev/libssl-dev/libpq-dev (vía apt, ninguno estaba instalado). Detalle de buenas prácticas dev/producción (WSL vs. ctunlinux, venv vs. Docker, cuándo pinnear) en el skill trivasa-streamlit-reportes (actualizado en la misma sesión con los gotchas de curl/docker run --env-file y el patrón de bump de dependencia compartida vía rama).

2026-09-03 (continuación) — Promovido a producción: CONT-1/CONT-2

El reporte Streamlit de exploración (entrada anterior) se promovió a streamlit-reportes (repo Docker, reportes.frento.com.mx/contabilidad) como dos páginas nuevas, reemplazando el reporte "Layout de Gastos — Reconciliación CECO" original (mismo path, mecanismo viejo de máscara de reversión):

  • CONT-1 — 6 orígenes, igual que el reporte de exploración sin nómina.
  • CONT-2 — igual + GASTO_REGISTRO_NOMINA (7mo origen), emparejado por join de texto (FOLIO, CECO, concepto normalizado) — no rank-pairing, no existe FK real Tipo_Gasto → Cuenta_Contable ni correspondencia posicional confiable para nómina. 6,591/6,591 exacto, cero huérfanos.

De paso, corregido un bug real heredado de queries/v03_detalle_cuenta_ centro_costo.sql en los 13 scripts del proyecto de origen Y en el código de producción: Gr_Folio ya es el folio completo de display ("SS-NNNNNNN", sucursal 2 dígitos) — se reconstruía sin necesidad concatenando Sc_Cve_Sucursal (4 dígitos, catálogo distinto). Corregido en los 13 scripts, 11/12/layout_gastos_lib.py, y el código promovido en streamlit-reportes.

También se separaron las conexiones de streamlit-reportes/contabilidad (antes compartían get_engine_test()/TRIVASADB_TEST_* aunque Layout de Gastos y Cruce XML usan bases distintas por diseño) — get_engine_205()/get_engine_207(), prefijos TRIVASADB_205_*/ TRIVASADB_207_* explícitos. Cruce XML (ahora CONT-3) pasó de .205 temporal a .207 producción real de paso.

Detalle completo: docs/proyectos/streamlit-reportes/reportes/ layout-gastos-ceco-cont-1.md y -cont-2.md en este mismo repo. Código: github.com/ehalso/streamlit-reportes, commit 64ceda2.

2026-09-03 — Rediseño sin máscara (11/12) + CONSUMO_INTERNO + reporte Streamlit

Reemplaza la mecánica de 01-09 (regla de reversión por folio: "si IMPORTE del folio ≤ -$1, usar Abono") por lectura directa de Pd_Tipo (1=Cargo, 2=Abono) en Poliza_Detalle — la regla de reversión existía solo porque la query vieja (v03_detalle_cuenta_centro_costo.sql, heredada de FlexMonster) colapsaba Pd_Tipo en dos columnas sumadas (CARGO/ABONO) antes de que Python pudiera decidir cuál era la real; al no colapsar, TIPO_MOVIMIENTO sale del dato, cero inferencia.

Mismo resultado exacto (validado no solo en agregado, sino fila por fila, por posición dentro de (FOLIO, CECO), contra el CSV real del diseño viejo): enero 2026 100.00% (7,691/7,691), enero-marzo 99.95% (17,935/17,944) — cero diferencias en 6,891 y 15,647 combinaciones (FOLIO, CENTRO) respectivamente.

Grano base establecido: (FOLIO, CECO, TIPO_GASTO) — 1 fila por combinación, con CUENTA/CUENTA_DESCRIPCION/TIPO_MOVIMIENTO/IMPORTE de la línea real de póliza que le tocó por rank-pairing. Confirmado que nunca hay CARGO y ABONO a la vez en la misma fila, ni siquiera en el residual de GASTO_RECLASIFICACION que mezcla signos en un mismo centro (05-0182622, centro 103) — ahí caen en TIPO_GASTO distintos (uno real, otro NaN por ser la línea sobrante sin match), nunca compiten por la misma fila. Esto habilita pivotar a columnas CARGO/ABONO separadas sin riesgo.

Dos bugs reales encontrados y corregidos en el camino (ninguno cambió el % final, pero sí la honestidad del mecanismo):

  1. Gr_Tabla viene '' (no NULL) para GASTO_DIRECTO — confirmado en BD (610 folios/6,531 filas CECO, enero 2026). Antes se tapaba con un .fillna() de pandas después del hecho; ahora se normaliza en el SQL mismo (CASE WHEN ISNULL(Gr_Tabla,'')='' THEN 'GASTO_DIRECTO' ELSE Gr_Tabla END).
  2. Al no colapsar Pd_Tipo, aparecían 210 líneas de póliza (enero 2026) sin Pd_Centro_Costo real — la contrapartida normal de doble entrada (Abono a Proveedor Nacional/Provisión de costo estándar/Préstamos a Terceros, cuentas de balance sin centro). El diseño viejo las descartaba por casualidad (VALOR=Cargo por defecto, Cargo=0 en esa fila); el diseño nuevo las descartaba por otro accidente (groupby con NaN en una llave de seguridad). Ahora se excluyen a propósito: AND pd.Pd_Centro_Costo <> '' en el SQL, documentado.

CONSUMO_INTERNO incorporado (12_reporte_base_ceco_consumo_interno.py) con la misma filosofía (Pd_Tipo real, filtro de doble póliza rescatado de 09). Resultado idéntico al ya documentado: 99.12% (3,045/3,072, enero 2026), 27 filas sin cuadrar (23 folios) = hueco de datos ya conocido. Confirmación nueva: TIPO_MOVIMIENTO sale 100% CARGO, 0 ABONO — antes 09 ni siquiera miraba Pd_Tipo=2 (su query solo traía SUM(CASE WHEN Pd_Tipo=1...)), así que "son cargos" era un supuesto no verificado; ahora es un hecho leído del dato real.

Reporte Streamlit (streamlit_app.py + layout_gastos_lib.py, patrón del skill trivasa-streamlit-reportes): pivota TIPO_MOVIMIENTO+ IMPORTE a columnas CARGO/ABONO separadas, sidebar Periodo+Filtros, tab de Reconciliación con semáforo por origen, descarga a Excel. Validado con AppTest (sin excepciones, filtros interactivos probados) antes de correr el servidor real. Corriendo en 127.0.0.1:8506 (streamlit_venv/ local, streamlit no estaba instalado en el sistema).

Código: conciliacion-master/layout-gastos/ (11_reporte_base_ceco.py, 12_reporte_base_ceco_consumo_interno.py, layout_gastos_lib.py, streamlit_app.py) — sesión de trabajo en la misma máquina que 13_reconciliacion_nomina_ceco.py (ver sección GASTO_REGISTRO_NOMINA de index.md, trabajo de otra sesión de Claude en paralelo).

Pendiente (sin cambios respecto a lo ya documentado, ver index.md): filtro de doble póliza de CONSUMO_INTERNO sigue siendo LIKE sobre texto libre; rama de capitalización de activo fijo no construida; los ~9 folios residuales (<0.06%) sin resolver, ahora visibles en el reporte como filas con TIPO_GASTO/CUENTA = (residual) en vez de ocultarse.

2026-09-02 — Reconciliación a nivel CECO (centro de costo)

Nuevo sub-proyecto, código en ~/trivasa/proyectos-bi/conciliacion-master/layout-gastos/ (scripts 01-09, progresión completa documentada en el PROGRESS.md de ese repo). Extiende la reconciliación Cargo/Abono de folio (ya al 100%, ver 2026-08-18 abajo) a nivel centro de costo: ¿el reparto por CECO de Gasto_Registro_Control coincide con Poliza_Detalle? Diagnóstico de calidad de dato, no la fuente del layout final.

  • Resultado: 99.75% (10,762 filas, enero 2026, 6 orígenes) — 100% en CONTROL_COMBUSTIBLE/ORDEN_COMPRA/VIAJE/GASTO_DIRECTO/ GASTO_RECLASIFICACION, 99.12% en CONSUMO_INTERNO.
  • Llave real de consolidación de la póliza: Tg_Cve_Tipo_Gasto × Centro de Costo, no el documento (Grd_ID) — hallazgo central de la sesión. Colapsar por documento da 93-94%; por tipo de gasto da 99%+. Confirmado con folio real de TELEFONIA (7 recibos → 53 filas por documento, 27 por tipo de gasto, exacto el número de líneas reales de la póliza).
  • Reversión (IMPORTE ≤ -$1): confirmado también a nivel CECO — el Cargo cae en cuenta de Provisión/Pasivo sin centro de costo (una sola línea), el Abono trae el detalle real por centro.
  • GASTO_RECLASIFICACION: el signo de Grc_Importe por línea (no si el folio completo es negativo) decide Cargo vs. Abono — extiende el caso especial 2 (que solo llegaba a nivel folio) a CECO.
  • Corrige la nota de "26 folios sin póliza de gasto real" de CONSUMO_INTERNO (ver 2026-08-18 abajo): 21 de 23 revisados (rango enero-marzo) sí tienen su gasto contabilizado — capitalizado como activo fijo vía código de proyecto (U-NNN en Grd_Comentario → Pd_Referencia = ese código corto, no el folio → cuenta 1210.xxx). Solo 2-3 folios genuinamente sin ninguna póliza.
  • Cruce con XML (Comprobante_Digital): el problema de "Varios" comprobantes por folio es casi siempre artefacto de grano — 16 de 17 casos de enero 2026 desaparecen al bajar de folio a documento.
  • Detalle completo (incluye el residual conocido de <0.06%, y el origen nuevo de bajo volumen COMPROBACION_GASTO investigado) en el PROGRESS.md del sub-proyecto.

2026-09-11 — HITO: layout de 60 columnas del Word construido, en conciliacion-cfdi

Retomado el objetivo original del proyecto (ver index.md) — github.com/ehalso/conciliacion-cfdi, layout_gastos_poliza/ scripts 18-25. Requerimiento completo (nunca antes leído) rescatado de ~/backups/ y transcrito en layout_gastos_60col/legado/layout-gastos- streamlit-claude/docs/00_requerimiento.md.

  • Hallazgo metodológico: la reconstrucción vía Poliza_Configuracion (sección "Rank-pairing vs. reconstrucción real", 2026-09-03) resuelve un problema (atribuir Tipo_Gasto por línea) que este entregable no necesita. El join directo Pd_Referencia=Gr_Folio (sin rank-pairing, sin regla de reversión) da Cargo/Abono exactos para los 5 orígenes + GASTO_RECLASIFICACION completo (que la reconstrucción vía config no cubría — solo el lado Cargo).
  • Antes de llegar a esa conclusión, sí se generalizó reconstruir_ config() de la config 0450 a las 44 configs reales del universo: 99.74%/99.39% de cobertura (folios/Cargo$), 100% cruzado contra rank-pairing. Bug real corregido en el camino: poliza_configuracion_lib.reconstruir_config() — Pcd_Condicion vacío se envolvía como (), SQL inválido (nunca visto porque solo se había probado con 0450, que no tiene ningún renglón así).
  • Corrección importante: 05-0174748 (CONSUMO_INTERNO), documentado en este proyecto como "sin póliza", en realidad tiene 4 pólizas — el filtro de doble póliza (memo/gasto real) se le escapa una tercera póliza tipo "Provisión" que no menciona "orden" en su comentario ni en la descripción de su config. Bug real, 1 de 9,090 combinaciones (FOLIO,CECO) en todo el trimestre — ver detalle en index.md.
  • La rama de capitalización de activo fijo de CONSUMO_INTERNO (resuelta 2026-08-08, nunca portada a producción) se reincorporó al layout: 45/47 folios recuperados, mismo resultado de agosto.
  • Probada y descartada la alternativa "filtrar por cuenta contable en vez de comentario" para el doble-póliza de CONSUMO_INTERNO — excluye 1,319 de 2,970 folios legítimos a escala completa (muchas cuentas de gasto real de este origen no son raíz 6xxx).
  • GASTO_REGISTRO_NOMINA investigado a fondo: Gr_Genera_Cxp='NO' en el 100% de sus folios (no pasa por CXP por diseño), sus "proveedores" son 4 cuentas de pasivo placeholder (RFC genérico XAXX010101000, no proveedores reales), y Comprobante_Digital.Cd_Tabla='NOMINA' dejó de alimentarse desde 2022 — la nómina 2026 se timbra en un sistema externo no integrado con TRIVASADB3.
  • Validado contra el reporte nativo de MPro (Excel real, no derivado): 7 orígenes, enero 2026, coincide exacto en folios/SUBTOTAL_NETO/ IMPUESTO/TOTAL; Cargo−Abono con diferencia de $56.59 sobre $39.47M (0.00014%, redondeo a centavos).
  • Pendiente sin cerrar: MONTO_COBRADO infla ~41.6% por Cxp_Folio compartido entre folios (factura consolidada); FACTURA_REF/ Descuento sin fuente; vista agrupada por cuenta (segunda vista del Word). Detalle completo en layout_gastos_poliza/PROGRESS.md de conciliacion-cfdi (entrada 2026-09-11).

2026-08-24

  • Corrección: la nota de "el Abono siempre sale en 0" para CONSUMO_INTERNO (línea de abajo, 2026-08-18) estaba incompleta — el Abono sí existe (1140.010.XXX.007 "Salida Consumo Interno" por almacén), solo que esa línea no lleva Pd_Referencia, así que un query filtrado por Pd_Referencia = Gr_Folio nunca la ve. Encontrado desde consumo-interno-fifo round2 siguiendo el join completo Consumo_Interno → Gasto_Registro → Poliza_Control → Poliza_Detalle. Detalle en consumo-interno-fifo/index.md.

2026-08-18

  • CONSUMO_INTERNO reconciliado: la documentación previa lo excluía del universo Cargo/Abono asumiendo "sin póliza por diseño" — falso, confirmado con datos reales. Descubierto: cada folio genera dos pólizas paralelas (movimiento de inventario vs. reconocimiento de gasto real), el join ingenuo sumaba el Cargo de ambas (~2x el importe nativo, Abono siempre 0). Filtrando a la póliza de gasto real: 99.12% de conciliación (2,944/2,970 folios, enero 2026), validado también a nivel Grd_ID y Grc_ID (reparto por centro de costo). Detalle completo en index.md.
  • Dos formas de aislar la póliza de gasto real dan el mismo resultado exacto: filtro por cuenta contable (raíz F / empieza 6 / descripción "costo estandar") vs. filtro por Poliza.Pl_Comentario (excluir "CUENTAS DE ORDEN"/"CTS ORDEN"/"CUENTA ORDEN") — el segundo, ya usado y validado antes en el proyecto consumo_interno_trazabilidad para el mismo problema, es más robusto (no depende de adivinar por código de cuenta).
  • Nuevo hallazgo de calidad de datos documentado en docs/schema/calidad-de-datos.md — Poliza_Detalle.Pd_Referencia es ambiguo para CONSUMO_INTERNO (dos pólizas comparten la misma referencia).
  • Re-validada la conciliación v0.5 (casos especiales 1-4) para orígenes "normales" contra datos en vivo de enero 2026: 1,425/1,425 folios (100% dentro de $1), consistente con lo documentado históricamente.