layout-gastos
Consolidación completa 2026-09-10:
~/proyectos/conciliacion-master/ya no existe — todo su contenido se migró y la carpeta se borró (backup completo en~/backups/conciliacion-master-final-2026-09-10.zip). Los scripts01-13(la reconciliación Cargo/Abono por CECO, secciones "Piezas resueltas" en adelante) y14-17(reconstrucción víaPoliza_Configuracion, conciliación XML por origen) viven enlayout_gastos_poliza/dentro del repogithub.com/ehalso/conciliacion-cfdi. Su UI (CONT-1/2/4/5) vive enreportes_streamlit/layout_gastos_poliza/del mismo repo; CONT-6 (promoción derecibidos/nivel_documento/ 04_conciliacion_mpro_vs_xml.py, exadjuntar-xml) se separó a su propio hub,reportes_streamlit/recibidos_nivel_documento/. Las 3 páginas RPTRV79 (auditoría de compras, dominio distinto) pasaron al repogithub.com/ehalso/reportes-mpro. El visor de CSV genérico (viewer-*.html) y los logs de sesión ya curados en este documento no se migraron — quedaron solo en el backup. Todo lo demás de este documento sigue siendo la referencia vigente para el código ya migrado — no se reescribió el historial, solo cambió dónde vive.
Reconstruir vía SQL el layout de gastos que usa el área de contabilidad, y
conciliar cada folio de Gasto_Registro contra su póliza contable
(Cargo/Abono de Poliza_Detalle) para validar que el importe nativo del
gasto cuadra con lo que se contabilizó. Trabajo histórico en
~/trivasa-bi-dev/exploracion/layout_gastos y
layout-gastos-ctunlinux (v0.1 → v0.5.1); esta entrada documenta el estado
consolidado, no el detalle de cada iteración.
Piezas resueltas
- v0.1 — reporte nativo:
Gasto_Registro+Gasto_Registro_Documento+Comprobante_Digital(UUID). Validado 1:1 contra export real de MPRO (enero 2026: $39,469,269.50 vs $39,469,269.59, diferencia $0.09). - v0.5 — Cargo/Abono vs póliza, orígenes "normales" (general/sin
Gr_Tabla,VIAJE,ORDEN_COMPRA,CONTROL_COMBUSTIBLE,GASTO_RECLASIFICACION): 100% dentro de $1 de tolerancia (1,425/1,425 folios, enero 2026; 99.90% en 2025 completo). 4 casos especiales — ver Casos especiales de conciliación abajo.
CONSUMO_INTERNO — antes excluido, ahora conciliado (2026-08-18)
La documentación histórica de v0.5 excluía CONSUMO_INTERNO del universo
reconciliable asumiendo "sin póliza por diseño" (se registra por otro
subsistema). Falso: sí tiene póliza, solo que estructurada distinto a
los demás orígenes. Detalle completo del join ambiguo y la corrección en
Calidad de datos → joins que parecen obvios pero son falsos.
Resumen: cada folio de CONSUMO_INTERNO genera dos pólizas
paralelas bajo el mismo Pc_Documento — una de movimiento de inventario
(cuenta 10500.012.003, raíz de grupo H = Cuentas de Orden/memo) y otra
de reconocimiento de gasto real (cuenta variable, raíz F = Gastos, o sin
grupo con descripción "gastos a cuenta de costo estandar"). Filtrando el
Cargo a la póliza de gasto real (dos formas equivalentes, mismo resultado
exacto: por cuenta contable, o por Poliza.Pl_Comentario excluyendo
"CUENTAS DE ORDEN"/"CTS ORDEN"/"CUENTA ORDEN"):
| Folios (enero 2026) | 2,970 |
| Concilian (±$1) | 2,944 (99.12%) |
| Sin póliza de gasto real | 26 (0.88%) — causa real, no de filtro |
| Importe total | $6,594,522.55 |
| Cargo (gasto real) total | $6,388,627.47 |
El Abono siempre sale en 0 al filtrar por Pd_Referencia = Gr_Folio
(como hace este query): la contrapartida sí existe, va a una cuenta de
enlace/almacén (1140.010.XXX.007 "Salida Consumo Interno", una por
sucursal/almacén), pero esa línea de Abono no lleva Pd_Referencia — está
agregada a nivel de almacén para toda la póliza consolidada del día, no
por folio individual. Sigue sin ser la partida doble Cargo=Gasto/Abono=CxP
de los demás orígenes (aquí es Cargo=Gasto/Abono=Activo de almacén), pero
si el Abono llega a hacer falta, hay que traer el Pl_Folio completo sin
filtrar por referencia — detalle completo en
consumo-interno-fifo
y en Calidad de datos.
Validado también a grano más fino:
- Grd_ID (línea de documento): mismo 99.12%, coincide 1:1 con folio —
los 2,970 folios de CONSUMO_INTERNO de enero 2026 tienen exactamente 1
línea cada uno.
- Grc_ID (Gasto_Registro_Control, reparto por centro de costo vía
Grc_Factor): 2,942/2,970 folios tienen 1 sola línea Grc, el resto se
reparte hasta en 14 centros de costo. La póliza de gasto real también
se reparte por Pd_Centro_Costo cuando aplica — emparejando por
(folio + centro de costo): 3,044/3,071 líneas concilian (99.1%,
consistente con el resultado a nivel folio).
Los 26 folios sin póliza de gasto real: corregido 2026-09-02 — no es
un hueco de datos como se documentaba aquí. Drill-down folio a folio
(proyecto conciliacion-master/layout-gastos/, código en
~/trivasa/proyectos-bi/conciliacion-master/layout-gastos/, scripts
01-09) encontró que 21 de 23 folios revisados (rango enero-marzo) sí
tienen su gasto contabilizado, solo que capitalizado como activo fijo
en vez de reconocido como gasto del periodo: Grd_Comentario empieza con
un código de proyecto (U-NNN/UNNN), y la póliza real usa
Pd_Referencia = 'NNN' (el código corto, no el folio completo) en cuenta
1210.xxx (Activo Fijo) — por eso un query que filtra
Pd_Referencia = Gr_Folio nunca la encuentra. Solo quedan 2 folios
genuinamente sin ninguna póliza (05-0175286, 05-0176482).
Corrección 2026-09-11: 05-0174748 NO es un caso de "sin póliza" —
tiene 4 pólizas reales (configs 0274/0277/0234/0396, no 2 como
asume el diseño memo/gasto-real). El filtro de doble póliza excluye
correctamente las 2 de memo (10500.012.003, comentario con "orden"),
pero una tercera póliza tipo "Provisión" (2120.010.004.005.002,
config 0274, comentario "CONSUMO INTERNO LLANTAS DEL..." sin la
palabra "orden") se escapa del filtro y duplica el Cargo real
($64.51 × 2 = $129.02 en vez de $64.51). Cuantificado a escala completa:
1 combinación (FOLIO,CECO) de 9,090 en todo enero-marzo 2026 — bug
real, extremadamente raro, no vale la pena una regla nueva para
cerrarlo. Detalle completo, incluida la prueba de que filtrar por
Poliza_Configuracion.Pc_Descripcion en vez de Pl_Comentario tampoco
lo resuelve (la config 0274 tampoco menciona "orden" en su
descripción), en github.com/ehalso/conciliacion-cfdi,
layout_gastos_poliza/PROGRESS.md (entrada 2026-09-11).
GASTO_REGISTRO_NOMINA — investigado, sí liga con póliza real (2026-09-03)
Igual que pasó con CONSUMO_INTERNO, este origen quedaba excluido desde
v0.5 asumiendo "se contabiliza por otro mecanismo, ajeno a este reporte"
(comentario textual en queries/v03_detalle_cuenta_centro_costo.sql).
Falso — se liga por el mismo mecanismo Poliza_Control
(Pc_Tabla='GASTO_REGISTRO') → Poliza → Poliza_Detalle
(Pd_Referencia=Gr_Folio) que los orígenes normales, sin estructura de
doble póliza como CONSUMO_INTERNO.
Diferencia de grano: cada folio de nómina desagrega el Cargo en muchas más
cuentas 6xxx por concepto (Sueldos, Bono Por Productividad/Puntualidad/
Asistencia, Fondo de ahorro, Comisiones, Vacaciones, Prima Vacacional,
Pagos Por Retiro) de las que distingue Grc_ID/Tg_Cve_Tipo_Gasto — la
comparación se hace a nivel (FOLIO, Centro) en vez de por cuenta. Cada
póliza también trae líneas de Cargo/Abono sin centro de costo
(reclasificaciones de pasivo: ISR retenido, Cuotas IMSS, Sueldos y
salarios por pagar, Fondo de ahorro por pagar, Vales de despensa por
pagar) que cierran el balance Cargo=Abono de la póliza pero no son gasto
por centro — se excluyen filtrando Pd_Centro_Costo <> ''.
Resultado: 100.00% (2,549/2,549 filas FOLIO×CENTRO, 270 folios activos de 294 totales, enero-marzo 2026, empresa 0001) — cero folios sin póliza ligada, mejor resultado de cualquier origen de este proyecto.
Validado también a grano más fino (FOLIO, Centro, Concepto) — no solo
que la suma agregada por centro cuadre, sino que el reparto entre tipos
de gasto dentro del mismo centro también sea correcto (1,155 de 2,549
combinaciones folio×centro traen 2+ tipos de gasto distintos en el mismo
centro). No hay FK real Tipo_Gasto→Cuenta_Contable para nómina
(Tg_Cuenta_Contable vacío en los 249 tipos — mismo hueco ya conocido de
la exploración general de layout-gastos); se unió por texto normalizado
(Tg_Descripcion=Cc_Descripcion exacto). También 100.00%
(6,591/6,591 filas, cero conceptos huérfanos). Código:
~/trivasa/proyectos-bi/conciliacion-master/layout-gastos/13_reconciliacion_nomina_ceco.py,
detalle en el PROGRESS.md de ese repo (entrada 2026-09-03).
Pendiente: decidir si se incorpora como séptimo origen al universo del
reporte final — el 100% no deja obstáculo técnico, falta decidir a nivel
negocio si nómina va en el mismo layout que los demás gastos o se reporta
aparte (dato sensible, ver dominios.md: dominio
NÓMINA marcado "Baja — definir acceso antes"). Actualización 2026-09-11:
sí se incorporó (junto con CONSUMO_INTERNO) al layout de 60 columnas —
ver sección nueva más abajo.
Nómina: sin pago vía CXP ni CFDI desde 2022 (2026-09-11)
Tres hallazgos nuevos sobre GASTO_REGISTRO_NOMINA, investigados al
incorporarlo al layout de 60 columnas:
Gr_Genera_Cxp = 'NO'en el 100% de los folios de nómina — bandera nativa del sistema: nómina no pasa por Cuenta_X_Pagar por diseño (se paga por dispersión bancaria directa, proceso separado).- Los "proveedores" que trae nómina (
Gasto_Registro_Documento. Pv_Cve_Proveedor) no son proveedores reales — son 4 cuentas de pasivo placeholder (SYP - SUELDOS Y SALARIOS POR PAGAR,VACACIONES POR PAGAR,VALES DE DESPENSA POR PAGAR,AGUINALDOS POR PAGAR), las 4 con el RFC genéricoXAXX010101000("público en general"). Comprobante_Digital.Cd_Tabla='NOMINA'(69,766 filas totales) dejó de alimentarse en 2022 (MAX(Cd_Timbre_Fecha) = 2022-12-31) — confirmado buscando sin restringirCd_Tablaen absoluto contra folios de nómina de 2026: los únicos "hits" son coincidencias falsas de número de folio con documentos deCHEQUE/FACTURA/TRASLADOde otros módulos/años (numeración de folio independiente por módulo). Conclusión: hoy (2026) la nómina se timbra en un sistema externo no integrado conTRIVASADB3.
Detalle completo en github.com/ehalso/conciliacion-cfdi,
layout_gastos_poliza/PROGRESS.md (entrada 2026-09-11).
Casos especiales de conciliación Cargo/Abono
CONSUMO_INTERNO/GASTO_REGISTRO_NOMINA: ver arriba — ninguno de los dos se excluye ya; ambos se reconcilian (CONSUMO_INTERNOcon el filtro de cuenta/comentario,GASTO_REGISTRO_NOMINAsin filtro especial, solo grano(FOLIO, Centro)).GASTO_RECLASIFICACION: no tiene póliza real de Cargo/Abono — se deriva del signo deGrd_Precio_Descontado_Importe(positivo=Cargo, negativo=Abono), misma cuenta víaTipo_Gasto.Tg_Cuenta_Contable.- Abono (
Pd_Tipo=2) solo cuenta si la cuenta contable es raízF(Gastos), o sin grupo asignado y código que empieza en6, o sin grupo con descripción "gastos a cuenta de costo estandar" — sin este filtro, partidas dobles legítimas contra cuentas de Activo (ej. depreciación) rompen la conciliación. - Reversiones (
Importede folio ≤ -$1): usar solo el Abono, forzar Cargo a 0.
Reconciliación a nivel CECO (centro de costo) — 2026-09-02
Extiende lo de arriba (que reconciliaba Cargo/Abono a nivel folio) a
nivel centro de costo: ¿el reparto por CECO que trae
Gasto_Registro_Control (Grc_ID, el lado operativo) coincide con lo que
quedó posteado en Poliza_Detalle? Es diagnóstico de calidad de dato, no
la fuente del layout final (Poliza_Detalle sola ya reconcilia 100% a
nivel folio×cuenta×centro, sin pasar por Grc_ID).
Código: ~/trivasa/proyectos-bi/conciliacion-master/layout-gastos/,
scripts 01 a 09 (progresión documentada en el PROGRESS.md de ese
repo — cada intento y por qué se descartó o mejoró el siguiente).
08_notebook_hito_ceco.py trae la narrativa completa con SQL comentado,
pensada para retomar el razonamiento sin releer la sesión que lo generó.
Resultado final (09_reconciliacion_completa_con_consumo_interno.py,
enero 2026, 6 orígenes):
| Origen | % |
|---|---|
CONTROL_COMBUSTIBLE, ORDEN_COMPRA, VIAJE, GASTO_DIRECTO, GASTO_RECLASIFICACION |
100% |
CONSUMO_INTERNO |
99.12% (ver corrección de los "26 folios" arriba) |
| Total (6 orígenes, 10,762 filas) | 99.75% |
Hallazgo central: la llave real de consolidación que usa la póliza al
postear es Tg_Cve_Tipo_Gasto (tipo de gasto) × Centro de Costo — no
el documento (Grd_ID). Cuando varios documentos del mismo folio
comparten tipo de gasto y centro, la póliza los junta en una sola línea;
agrupar por documento (intento intermedio, scripts 04/05) los deja
separados y da peor resultado (93-94%) que agrupar por tipo de gasto
(06/07, 99%+). Caso real que lo confirmó: folio de TELEFONIA con 7
recibos — por documento da 53 filas, por tipo de gasto da 27, exacto el
número de líneas reales de la póliza.
Otros 2 hallazgos nuevos, sin documentar en ningún proyecto anterior:
- Reversión (IMPORTE ≤ -$1): el Cargo cae en cuenta de
Provisión/Pasivo sin centro de costo (una sola línea); el Abono sí
trae el detalle real por centro. Regla: comparar contra Abono, no
Cargo — mismo principio que el caso especial 4 de arriba, pero aquí
confirmado también a nivel CECO.
- GASTO_RECLASIFICACION: el signo de Grc_Importe por línea (no
si el folio completo es negativo) decide si esa línea es Cargo
(positivo) o Abono (negativo) — el caso especial 2 de arriba solo
llegaba a nivel folio.
Residual conocido (enero-marzo, <0.06% del universo): 3 folios donde el
conteo de líneas no coincide exacto entre Grc_ID y Poliza_Detalle en
un centro puntual — límite de la técnica de emparejamiento (por posición,
sin llave real entre las dos tablas), no un patrón sistémico nuevo.
También se confirmó, cruzando contra Comprobante_Digital (XML/CFDI):
el problema de folios con "Varios" comprobantes es casi siempre un
artefacto de contar por folio en vez de por documento (16 de 17 casos de
enero 2026 desaparecen al bajar a nivel documento) — no ambigüedad real
de a qué factura corresponde un gasto.
Rediseño sin máscara — grano base establecido (2026-09-03)
La reconciliación de arriba (01-09) decidía Cargo vs. Abono con una
regla de reversión por folio ("si IMPORTE del folio ≤ -$1, usar
Abono") calculada del lado operativo (Gasto_Registro_Documento).
Existía solo porque la query de póliza (v03_detalle_cuenta_centro_
costo.sql, heredada de FlexMonster) colapsa Pd_Tipo — el dato real,
1=Cargo/2=Abono — en dos columnas sumadas antes de que el script pudiera
ver cuál era la línea real; sin ese dato, había que adivinar con una
señal indirecta.
11_reporte_base_ceco.py/12_reporte_base_ceco_consumo_interno.py
resuelven esto agrupando por Pd_Tipo en vez de colapsarlo —
TIPO_MOVIMIENTO sale directo del dato, cero regla, cero máscara.
Validado no solo en agregado sino posición por posición dentro de
(FOLIO, CECO), contra el CSV real del diseño viejo: cero
diferencias en las 6,891 combinaciones (FOLIO, CENTRO) de enero y las
15,647 de enero-marzo. Mismo resultado exacto, mecanismo honesto.
Grano base del reporte, establecido aquí en adelante:
(FOLIO, CECO, TIPO_GASTO) — 1 fila por combinación, con
CUENTA/CUENTA_DESCRIPCION y CARGO/ABONO (pivotado de
TIPO_MOVIMIENTO+IMPORTE) de la línea real de póliza que le tocó por
rank-pairing. Confirmado que CARGO y ABONO nunca coexisten !=0 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 huérfano sin match), nunca
compiten por la misma fila.
Dos bugs reales encontrados al dejar de colapsar (ninguno cambió el % final, sí la honestidad del mecanismo):
Gr_Tablaviene''(noNULL) paraGASTO_DIRECTO— se tapaba con un.fillna()de pandas después del hecho; ahora se normaliza en el SQL (CASE WHEN ISNULL(Gr_Tabla,'')='' THEN 'GASTO_DIRECTO' ...).- 210 líneas de póliza (enero 2026) sin
Pd_Centro_Costoreal aparecían al no colapsar — la contrapartida normal de doble entrada (Abono aProveedor Nacional/Provisión de costo estándar/Préstamos a Terceros, cuentas de balance sin centro). El diseño viejo las descartaba por casualidad; ahora se excluyen a propósito (AND pd.Pd_Centro_Costo <> '', documentado en el SQL).
CONSUMO_INTERNO incorporado con la misma filosofía en 12 — mismo
99.12% ya documentado arriba, pero con una confirmación nueva:
TIPO_MOVIMIENTO sale 100% CARGO, 0% ABONO en la póliza de gasto
real. 09 nunca miraba Pd_Tipo=2 (su query solo sumaba Pd_Tipo=1),
así que "son puros cargos" era un supuesto no verificado — ahora es un
hecho leído del dato.
Primer reporte con interfaz: streamlit_app.py +
layout_gastos_lib.py (patrón del skill trivasa-streamlit-reportes),
Cargo/Abono como columnas separadas, tab de reconciliación con semáforo
por origen, descarga a Excel. Validado con AppTest antes de correr el
servidor real.
Código: conciliacion-master/layout-gastos/, scripts 11/12 +
layout_gastos_lib.py/streamlit_app.py. Detalle completo en el
PROGRESS.md de ese repo (entrada 2026-09-03).
Rank-pairing vs. reconstrucción real vía Poliza_Configuracion — exploratorio (2026-09-03)
El rank-pairing de arriba (ordenar por valor dentro de (FOLIO, CECO) y
emparejar por posición) existe porque no hay llave real
Grc_ID↔Poliza_Detalle visible desde esas dos tablas. Pero sí
existe una llave real un nivel más abajo: trivasa-context/docs/
proyectos/poliza-explor/configuracion-polizas.md (sesión 2026-09-03)
documentó que Poliza_Configuracion_Detalle.Pcd_Relacion — el JOIN que
el motor de MPRO realmente usa para generar cada renglón de póliza — se
liga a Gasto_Registro_Control directo por (Gr_Folio, Grd_ID). Se
puede reconstruir esa query tal cual y etiquetar cada línea de Grc_ID
con el renglón exacto que le corresponde, sin ordenar ni adivinar nada.
Probado contra datos reales para la config 0450 ("REGISTRO DE GASTOS
NACIONAL", la que domina GASTO_DIRECTO/CONTROL_COMBUSTIBLE/VIAJE/
ORDEN_COMPRA — 3,403 de ~3,600 folios del rango enero-marzo 2026):
- La reconstrucción cuadra 100.00% contra
Poliza_Detallereal (5,171/5,171 combinacionesFOLIO,CECO) — confirma que es el mismo JOIN que usó el motor, no una aproximación. - De las 436 combinaciones
(FOLIO,CECO)con más de una línea deGrc_ID(las únicas donde rank-pairing podría emparejar mal): - 17 se resuelven en renglones distintos de la configuración —
distinción real (el motor las separa por
Tipo_Gasto/Proveedor), no coincidencia de orden. - 419 caen en el mismo renglón — el motor las suma
(
SUM(ABS(Grc_Importe)) GROUP BY Centro_Costo, sin agrupar también porTipo_Gasto). De esas, solo 20 tienen además más de unTipo_Gastoreal — esa es la ambigüedad irreducible: ni reconstruyendo la query exacta del motor se recupera esa distinción, porque el propio motor ya la descartó al generarPoliza_Detalle. No es un límite de la técnica, es un límite del dato fuente.
Neto: 5,151/5,171 (99.61%) de las combinaciones quedan resueltas con certeza real vía la configuración, no una suposición de orden. El límite duro de rank-pairing, medido (no supuesto), es 0.39% — para esta config.
Alcance parcial, no reemplaza 11/12 todavía: solo cubre la config
0450, 1 de ~67 configs distintas que aparecen en el universo de
layout-gastos (enero-marzo 2026). Generalizar a las ~66 restantes (y a
CONSUMO_INTERNO/GASTO_RECLASIFICACION, que tienen mecanismos propios
ya resueltos por otra vía) queda pendiente. Código:
14_reconstruccion_via_poliza_configuracion.py, función
reconstruir_config(cve, empresa, fecha_ini, fecha_fin) — reusable para
cualquier config con renglones de tipo Cargo+Centro_Costo.
CONT-4/CONT-5 — conciliación 1:1 contra XML (exploratorio, solo local, 2026-09-04)
Extiende la reconstrucción de arriba a una conciliación real contra XML
(CFDI). 15_reconciliacion_xml_via_poliza_configuracion.py: grano
(FOLIO, GRD_ID) — el documento, no la cuenta contable (Comprobante_
Digital.Cd_Documento decodificado es Gr_Folio + Grd_ID). Comparando
Grd_Precio_Neto_Importe (con impuesto, agrupado por XML_UUID — un
mismo XML puede repartirse en varios documentos, 1.83% de los casos) contra
Cd_Monto: 97.76% (1,922/1,966 XML_UUID) cuadra exacto. Detalle
completo (incluida la corrección de comparar contra el importe con
impuesto, no contra CARGO neto de póliza) en PROGRESS.md.
Dos reportes Streamlit nuevos, corriendo solo en local
(streamlit_venv/, puerto 8506, 127.0.0.1) — no promovidos a
streamlit-reportes:
- CONT-4 — grano documento, XML 1:1.
- CONT-5 — grano cuenta contable, XML agregado (
N_DOCUMENTOS/N_XML/AMBIGUOexplícitos) — construido junto a CONT-4 a propósito para comparar ambos granos con datos reales.
Misma pestaña de Reconciliación que CONT-1/CONT-2 (reusa resumen_por_
folio()/resumen_qa() sin cambios). Cobertura: solo config 0450, igual
que arriba — complemento exploratorio, no reemplazo. Código:
poliza_configuracion_lib.py, layout_gastos_config_lib.py,
reporte_ui_config.py, pages/3_CONT-4_...py/4_CONT-5_...py.
Conciliación XML por origen (16/17, 2026-09-05)
Extiende CONT-4/CONT-5 (arriba, solo config 0450) a un panorama completo
sin pasar por Poliza_Configuracion — trabaja directo contra
Gasto_Registro/Gasto_Registro_Documento/Comprobante_Digital, para los
4 orígenes que sí deberían llevar XML por lógica de negocio:
GASTO_DIRECTO, VIAJE, ORDEN_COMPRA, CONTROL_COMBUSTIBLE
(CONSUMO_INTERNO/GASTO_REGISTRO_NOMINA/GASTO_RECLASIFICACION quedan
fuera a propósito — 0% de cobertura confirmado por diseño de negocio, no
hueco).
Tres bugs reales encontrados y corregidos en el camino (documentados a
detalle en Calidad de datos → Comprobante_Digital,
resumen aquí):
Comprobante_Digital.Cd_Documento(paraCd_Tabla='GASTO_REGISTRO') tiene dos formatos de longitud (14 y 18 caracteres) — filtrar soloLEN=18perdía ~23% de los XML ligados. Confirmado con drill-down real deORDEN_COMPRA: 17 folios "sin XML" en realidad sí tenían, en el formato de 14.- Aceptar ambos formatos puede duplicar el mismo XML en el cálculo
(mismo comprobante capturado dos veces, uno por formato) — corregido
deduplicando por
(FOLIO, GRD_ID, XML_UUID). Comprobante_Digital.Cd_Montoviene en la moneda original del CFDI, nunca convertido a MXN — comparar el importe ya convertido daba diferencias de hasta 18x en documentos USD/EUR. Corregido comparando sin aplicarGrd_Tipo_Cambio.
Con las 3 correcciones: VIAJE, ORDEN_COMPRA y CONTROL_COMBUSTIBLE
llegan a 100%/100% (cobertura y cuadre de monto, enero 2026).
GASTO_DIRECTO queda con el hueco real del proyecto: 85.94% de
documentos con XML (35.04% del importe), 89.30% de eso cuadra en monto.
Cuarto hallazgo, investigando el residual de GASTO_DIRECTO: 31 de
58 XML_UUID sin cuadrar son CFDI tipo RETENCIONES, cuyo monto real vive
en una fila hermana bajo Cd_Tabla='CONSTANCIA_RETENCION' (ver detalle
en Calidad de datos) — 16 de esos 31 cuadran exacto aplicando el factor
del 90% (ISR de honorarios). Aplicado en 17: 27.56% → 31.40% de
importe que cuadra en GASTO_DIRECTO (+3.67pp).
17_reconciliacion_xml_grano_grupo.py también prueba el grano de
componente conexa (union-find) — idea tomada de un proyecto paralelo
de otra sesión (~/proyectos/conciliacion-master/adjuntar-xml/, ver
Calidad de datos) que resuelve el mismo problema con metodología más
madura (parseo de Cd_XML, Cuenta_X_Pagar como cadena alterna). Efecto
medido en GASTO_DIRECTO: solo +0.17pp — mucho menor de lo esperado,
porque el residual de este proyecto no era mayormente por facturas
repartidas en varios documentos (a diferencia de adjuntar-xml, donde
el mismo cambio resolvía ~9x más descuadres). Reimplementado en versión
ligera (sin Cd_XML, el texto completo del CFDI) porque el entorno
de exploración solo tiene ~5GB de RAM — confirmado que la versión con
Cd_XML completo agota memoria y el proceso muere sin traceback.
Efecto total por rango de fecha (probado en 3 rangos para elegir con evidencia — el % no varía mucho entre rangos, ningún periodo es claramente "mejor"):
| Rango | GASTO_DIRECTO | CONTROL_COMBUSTIBLE | TOTAL (4 orígenes) |
|---|---|---|---|
| Enero 2026 | 31.40% | 100.00% | 40.28% |
| Enero-Marzo 2026 | 33.13% | 100.00% | 41.15% |
| Enero-Mayo 2026 | 32.03% | 73.49% | 39.94% |
CONTROL_COMBUSTIBLE cae a 73.49% en enero-mayo (era 100% en los rangos
más cortos) — hallazgo nuevo, sin investigar en profundidad, pero
relacionado con dos propiedades ya confirmadas de este origen (ver
Calidad de datos → CONTROL_COMBUSTIBLE):
la factura consolidada del proveedor puede repartirse entre folios de
distintas Poliza_Configuracion (caso real: un solo CFDI de
$10,486.62 cubre 14 folios repartidos entre las configs 0450 y 0427),
y el CFDI casi nunca timbra en el mismo mes que el folio operativo
que cubre — por eso la conciliación de este origen debe expandirse por
folio real ligado al UUID (sin restringir a una sola config ni al
periodo de fecha), no por fecha de timbrado del comprobante. El residual
de enero-mayo probablemente es la misma causa a mayor escala (más
facturas consolidadas que cruzan fuera del universo expandido con
max_iter=3), sin confirmar todavía.
Ideas evaluadas por razonamiento, NO probadas empíricamente (ver
Calidad de datos para el detalle de cada una): parsear Cd_XML para
complementos de Pago/Vales de Despensa; Cuenta_X_Pagar como cadena
alterna para CONTROL_COMBUSTIBLE (validada por otra sesión en
layout-contabilidad/layout_gastos/); marcar CFDI intercompañía.
Código: conciliacion-master/layout-gastos/ (16_reconciliacion_xml_por_
origen.py, 17_reconciliacion_xml_grano_grupo.py). Detalle completo,
incluida la corrección de que estas ideas se evaluaron por razonamiento
y no por prueba, en PROGRESS.md de ese repo (entradas 2026-09-05).
HITO: layout de 60 columnas del Word, construido (2026-09-11)
Este proyecto nació para reconstruir "el layout de gastos que usa el área
de contabilidad" (ver intro de este documento) — hasta esta fecha, todo
el trabajo de arriba (v0.1-17, CONT-1/2/4/5) resolvía la
reconciliación Cargo/Abono por CECO, no el entregable original. Esta
sesión (github.com/ehalso/conciliacion-cfdi, layout_gastos_poliza/,
scripts 18-25) retomó el requerimiento real: el Word transcrito
completo en layout_gastos_60col/legado/layout-gastos-streamlit-claude/
docs/00_requerimiento.md (rescatado de ~/backups/, nunca antes leído
por ninguna sesión de este repo) — auditoría externa trimestral, Bates y
Asociados, 60 columnas en 6 bloques (pago, proveedor, factura/CFDI,
impuestos, XML, póliza), criterio de aceptación: suma de Cargo/Abono ==
reporte nativo de gastos.
Hallazgo metodológico central: la reconstrucción vía
Poliza_Configuracion (arriba, sección "Rank-pairing vs. reconstrucción
real") resuelve un problema que este entregable no necesita —
atribuir cada línea de póliza a su Tipo_Gasto de origen, algo que sí
necesita CONT-1/2 pero que el Word nunca pide. El join directo
(Poliza_Detalle.Pd_Referencia = Gasto_Registro.Gr_Folio, lectura de
Pd_Tipo sin inferir nada) da Cargo/Abono exactos, sin rank-pairing, sin
regla de reversión, para los 5 orígenes normales y para
GASTO_RECLASIFICACION completo (que reconstruir_config() no podía
cubrir — solo traía el lado Cargo, nunca el Abono del par espejo).
Alcance final: 7 orígenes (los 5 del Word + GASTO_REGISTRO_NOMINA +
CONSUMO_INTERNO, ampliado después de cerrar el alcance original).
Validado contra el reporte nativo de MPro (no algo derivado): 7
orígenes, enero 2026, 4,494 folios — SUBTOTAL_NETO/IMPUESTO/TOTAL
coinciden exacto a $0.00; Cargo−Abono $39,469,326.09 vs. baseline
$39,469,269.50 (diferencia $56.59, 0.00014%, redondeo a centavos entre
Poliza_Detalle y los campos nativos, no un problema de datos).
Impuestos atribuidos correctamente por centro de costo (no solo por
folio) usando Gasto_Registro_Control.Grc_Factor — campo nativo de
peso (confirmado que suma 1.0 exacto por documento, de 1 hasta 79
centros), evita inflar la suma cuando un documento se reparte en varios
centros (19.3% de los casos).
Pendiente sin cerrar en esta sesión: MONTO_COBRADO infla ~41.6% cuando
un Cxp_Folio se comparte entre varios folios (factura consolidada, no
corregido); FACTURA_REF/Descuento sin fuente (placeholder); vista
agrupada por cuenta contable (segunda vista del Word, solo se construyó
la de detalle). Detalle completo en layout_gastos_poliza/PROGRESS.md
de ese repo (entrada 2026-09-11) y layout_gastos_60col/README.md.
Pendiente
- Investigar por qué
CONTROL_COMBUSTIBLEcae a 73.49% en enero-mayo (nuevo, sin investigar) — candidato: el mecanismo deCuenta_X_Pagardelayout-contabilidad/layout_gastos. - Los 27
XML_UUIDdeGASTO_DIRECTOtipo CFDI Ingreso normal sin patrón identificado, y los 15 CFDI de RETENCIONES sin constancia ligada (de los 31 encontrados) — ver sección arriba. - Probar contra esta base las ideas de
adjuntar-xml/layout- contabilidadque solo se evaluaron por razonamiento (parseo deCd_XML,Cuenta_X_Pagar, CFDI intercompañía) — pendiente de una máquina con más memoria para el parseo deCd_XML. - ~~Decidir si CONT-4/CONT-5... investigar el residual de cuadre XML (~2.2%, parece concentrado en documentos en USD)~~ — resuelto 2026-09-05: confirmado que sí era doble aplicación de tipo de cambio en documentos USD, no un residual sin explicar. Ver sección arriba.
- ~~Generalizar la reconstrucción vía
Poliza_Configuracionde la config0450a las ~66 restantes~~ — hecho 2026-09-11 (las 44 configs reales usadas por los 5 orígenes del Word, no exactamente 66): 99.74%/99.39% de cobertura (folios/Cargo $), 100% cruzado contra rank-pairing donde ambas técnicas tienen dato. Pero ver el hallazgo más grande de esa misma sesión: el layout de 60 columnas no necesita esta técnica en absoluto — el join directo (Pd_Referencia=Gr_Folio, sin rank-pairing) ya resuelve Cargo/Abono sin ambigüedad para los 5 orígenes, porque el Word nunca pideTipo_Gasto(lo único quereconstruir_config()aporta sobre el join directo). Ver sección nueva más abajo.reconstruir_config()generalizada queda como cross-check, no como fuente del reporte final. - Investigar los 2 folios de
CONSUMO_INTERNOgenuinamente sin póliza (05-0175286,05-0176482) — sigue abierto.05-0174748ya no aplica aquí — ver corrección arriba, resultó ser un caso de póliza duplicada, no de póliza faltante. - ~~Construir la rama de capitalización de activo fijo para
CONSUMO_INTERNO~~ — resuelto 2026-09-08 en12_reporte_base_ceco_ consumo_interno.py, pero nunca portado a producción (ni alayout_gastos_lib.py/CONT-1/2). Reincorporado 2026-09-11 al construir el layout de 60 columnas (24_bloque_poliza_directo.py,github.com/ehalso/conciliacion-cfdi) — 45/47 folios recuperados, mismo resultado que en agosto (2 residuales sin resolver, folios que reparten en más de 1 centro de costo). - Nuevo 2026-09-11: se probó la alternativa de filtrar la doble
póliza de
CONSUMO_INTERNOpor raíz de cuenta contable (6xxx) en vez de porPl_Comentario/Pc_Descripcion— descartada, excluye 1,319 de 2,970 folios legítimos de enero 2026 (muchas cuentas de gasto real de este origen NO son raíz6xxx). El filtro de comentario, pese a su bug conocido (05-0174748arriba), sigue siendo la mejor opción disponible. - ~~
GASTO_REGISTRO_NOMINA: nunca se investigó...~~ — resuelto 2026-09-03, ver sección arriba. Falta la decisión de negocio sobre incorporarlo al reporte final. - ~~Mejorar el filtro de doble póliza de
CONSUMO_INTERNO...~~ — resuelto 2026-09-03: filtro movido dePl_Comentario(texto libre) aPoliza_ Configuracion.Pc_Descripcion(la fuente real de la regla), verPROGRESS.md. Validado 0 discrepancias contra el filtro viejo en enero-marzo 2026. - ~~Investigar
COMPROBACION_GASTO...~~ — causa raíz confirmada 2026-09-03 víapoliza-explor/configuracion-polizas.md: config0394no cubre el CeCo000501PLANTA CONCRETERA en ninguna condición de cargo — hueco estructural en la regla contable, no ruido de nuestra técnica de match. VerPROGRESS.md. - Decidir qué hacer con los ~9 folios residuales del rank-pairing
(<0.06%, ver "Rediseño sin máscara" arriba): hoy el reporte los deja
visibles como filas
TIPO_GASTO/CUENTA=(residual)en vez de ocultarlos — falta decidir si se excluyen del reporte final o se quedan marcados así. - ~~Construir el reporte de producción~~ — resuelto 2026-09-03: promovido
a
streamlit-reportes(repo Docker,reportes.frento.com.mx/contabilidad), como CONT-1 (sin nómina) y CONT-2 (conGASTO_REGISTRO_NOMINA). Detalle en streamlit-reportes/reportes/layout-gastos-ceco-cont-1.md y layout-gastos-ceco-cont-2.md. El reporte Streamlit local de exploración (streamlit_app.pyenconciliacion-master/layout-gastos/) se queda como estaba, para seguir iterando antes de re-promover.