Skip to content

conciliacion-cfdi

Objetivo

Conciliación automatizada entre los CFDI timbrados ante el SAT (bodega raw_sat en Postgres, ver warehouse) y los registros contables de Management Pro / mpro (SQL Server), para responder, periodo a periodo: ¿qué documentos de mpro corresponden a cada CFDI, y el importe contabilizado (cargo/abono en póliza) cuadra con el importe fiscal del CFDI? Cubre las tres direcciones de CFDI que maneja Trivasa: recibidos, emitidos, y retención (constancias de retención/intereses a prestamista).

Dónde vive el código

Repo separado, no en este monorepo: github.com/ehalso/conciliacion-cfdi (público) — toda la extracción, el parseo de XML, la lógica de conciliación y los reportes .xlsx viven ahí.

Consolidación 2026-09-10 — antes eran varios proyectos hermanos respondiendo la misma pregunta por separado, ahora es un solo repo:

~/proyectos/conciliacion-master/ ya no existe — se migró por completo y se borró (respaldo final en ~/backups/conciliacion-master-final-2026-09-10.zip; el respaldo de la primera pasada, antes de completar layout-gastos, en ~/backups/consolidacion-conciliacion-2026-09-10.zip):

  • adjuntar-xml (recibidos, nivel documento) se portó a recibidos/nivel_documento/.
  • conciliacion-emitidos (emitidos + retención, nivel documento) se portó a emitidos/nivel_documento/ y retencion/cruce_sat/.
  • layout-gastos (reconciliación CECO/Gasto_Registro, scripts 01-17) se portó completo a layout_gastos_poliza/ — incluido lo exploratorio (14-17, reconstrucción vía Poliza_Configuracion) que en la primera pasada se había dejado atrás. Su UI (CONT-1/2/4/5) vive en reportes_streamlit/layout_gastos_poliza/; CONT-6 (promoción de recibidos/nivel_documento/04_conciliacion_mpro_vs_xml.py) se separó a reportes_streamlit/recibidos_nivel_documento/ — ver layout-gastos/index.md.
  • funcionales-auditoria (reportes nativos RPTRV79, dominio de auditoría de compras, no de conciliación) pasó a su propio repo, github.com/ehalso/reportes-mpro — incluidas las 3 páginas Streamlit que lo envolvían, antes mezcladas en el hub de layout-gastos.

Además, ~/proyectos/layout-contabilidad/layout-gastos-streamlit-claude se retiró — reemplazado por streamlit-reportes (el deployment real de producción, ver streamlit-reportes).

Acceso a datos: conexión directa por SQLAlchemy (psycopg2/pymssql, src/bridge_client.py) a las tres bases — Postgres raw_sat y los dos SQL Server de mpro. El modo anterior (API puente HTTP vía query-api, reportesweb.frento.com.mx) queda documentado como fallback histórico, ya no es el camino primario desde 2026-09-09.

Estructura del repo (2026-09-10 — por pregunta de negocio, no por sesión)

Cada dominio se concilia desde dos ángulos independientes y complementarios, no un método único:

  • nivel_documento — ¿el documento que capturó/originó el CFDI en mpro trae el mismo importe? (compara contra la tabla propia del módulo, nunca contra la póliza).
  • nivel_poliza — ¿lo que se contabilizó en Poliza_Detalle (Cargo/Abono real) cuadra contra el CFDI? Un nivel más profundo: el documento puede estar perfecto y la póliza no aislarlo bien.
  • cruce_sat — cruce contra una fuente independiente de mpro (los XML que el PAC deja en el share del SAT, cargados directo a Postgres sin pasar por el ERP) — el único ángulo que puede detectar un comprobante que se timbró y el ERP nunca registró en ningún módulo.
recibidos/{nivel_documento, nivel_poliza, cruce_sat}
emitidos/{nivel_documento, nivel_poliza, cruce_sat}
retencion/cruce_sat/
cobranza_rep/
layout_gastos_poliza/
reportes_streamlit/

cruce_sat/ de recibidos y emitidos son placeholders documentados (pendientes de construir); el de retención (retencion_reconciliation.py) ya está corrido y validado — ver el hallazgo abajo.

Estado (2026-09-10)

  • Recibidos:
  • nivel_poliza (baseline_universal.py, el método vigente): H1 2026 (ene-jun) en 99.36% (9,511/9,572). El 0.64% restante son familias con mecanismo identificado, no misterios — ver calidad de datos, sección Comprobante_Digital (CFDI).
  • nivel_documento (ex adjuntar-xml, 03/04/05): 95.76% de importe conciliado, 99.77% de IVA acreditable con CFDI ligado (enero 2026) — responde una pregunta que nivel_poliza no cubre (los impuestos a nivel documento; baseline_universal.py deja el IVA fuera del chequeo automatizado a propósito, se postea consolidado por día/póliza).
  • Emitidos:
  • nivel_documento (conciliacion_emitidos_documento.py): 100% en todo H1 2026 (14,554/14,554 CFDI conciliables — factura, nota de crédito, retenciones). Por construcción: el CFDI se genera DESDE el documento de mpro, no puede diferir.
  • nivel_poliza (baseline_universal_emitido.py, nuevo 2026-09-10): NOTA_CREDITO 99.5% (separar el Cargo por cuenta contable — devolución de mercancía vs. reversión de costo de venta — resolvió lo que parecía ruido). FACTURA sigue sin método funcional (0.6%, el 92% del universo monetario) — la póliza de ingreso de VENTA no aísla el documento por Pd_Referencia como sí hacen recibidos y NOTA_CREDITO. Candidato: preguntar a Trivasa cómo se referencia el documento ahí, o un chequeo agregado por sucursal/día.
  • Retención (retencion/cruce_sat/retencion_reconciliation.py): nivel 1 corrido para H1 2026 — dos conceptos con mecánica distinta bajo el mismo cve_retenc (arrendamiento/honorarios 10% vía CONSTANCIA_RETENCION vs. intereses a prestamista 20% vía GASTO_REGISTRO, nunca CONSTANCIA_RETENCION). Hallazgo real de negocio: 29 constancias de retención por $536,597.04 ($107,319.45 de ISR) que el SAT tiene timbradas y el ERP nunca registró — 14 en enero, 15 en febrero, ninguna en marzo-junio. Confirmado por dos implementaciones independientes (dos sesiones distintas llegaron al mismo número por caminos separados) — ver sección dedicada en calidad de datos.
  • Cobranza (REP): pendiente, sin construir — metodología ya medida en el proyecto original conciliacion-emitidos (96.18% de facturas con cobranza conciliada, enero 2026), falta portarla.

Decisión de arquitectura: los ajustes del CFDI (Descuento/IEPS/impuestos locales) ahora viven en raw_sat, no se re-parsean del XML de mpro

Hasta 2026-09-09, baseline_universal.py necesitaba, en cada corrida, volver a bajar el Cd_XML de cada CFDI desde Comprobante_Digital (mpro) y reparsearlo con lxml para sacar Descuento, IEPS trasladado, e impuestos locales (raw_sat.cfdi_recibidos solo trae el subtotal bruto del SAT, sin estos ajustes) — el paso más caro del pipeline, sin caché entre entornos.

2026-09-10: se agregaron 15 columnas nuevas, nullable, a raw_sat.cfdi_recibidos/cfdi_emitidos — pobladas por el mismo loader de raw_sat_xml (ver consulta-xmls) al parsear el XML durante la ingesta, en dos tandas el mismo día:

  • Primera tanda (misma lógica ya validada en conciliacion-cfdi/src/cfdi_parser.py): descuento, ieps_trasladado, impuestos_locales_trasladados, impuestos_locales_retenidos, total_impuestos_retenidos.
  • Segunda tanda (pedido de negocio nuevo, no viene de cfdi_parser.py): desglose ret_iva/ret_isr (Impuestos/Retenciones), complemento Pagos (REP) — pagos_monto_total, pagos_iva_total, pagos_dr_uuids (;-separado, únicos, orden alfabético), pagos_dr_pagado —, complemento ValesDeDespensa (vales_despensa_total, vales_despensa_n_trab), complementos (inventario de qué complementos trae el CFDI, ;-separado, sin el Timbre) y base_exenta (Traslados con TipoFactor='Exento', por @Base, no @Importe).

baseline_universal.py ahora lee la primera tanda directo de raw_sat (ajustes_desde_sat(), en vez de xml_ajustes()) — validado exacto contra los 6 meses de H1 2026 (mismo 99.36%/61 pendientes de siempre, sin re-bajar ni un solo XML de mpro). Ambas tablas quedaron backfillado completo para todo 2026 disponible en el mount (cfdi_recibidos enero-septiembre, cfdi_emitidos enero-septiembre también — ya no solo enero, corregido tras un backfill completo posterior el mismo día).

raw_sat.cfdi_retencion llegó ya parseado desde su ingesta original (monto_total_operacion, monto_total_retenido, cve_retenc, con cobertura histórica completa 2016-2026) — por eso retencion_ reconciliation.py y el cruce contra SAT de emitidos tampoco necesitaron parsear XML nunca.

Para cualquier otro proyecto que necesite estos mismos ajustes: ya están en raw_sat.cfdi_recibidos/cfdi_emitidos/cfdi_retencion — no hace falta reimplementar el parseo.

Gotchas de esquema/calidad de dato que salieron de aquí

Documentados en detalle en schema/calidad-de-datos.md (secciones Comprobante_Digital y "Retención"): el catálogo real de Cd_Tabla, los dos formatos de longitud de Cd_Documento para GASTO_REGISTRO, el tipo de cambio por origen (y que Cheque no tiene columna de moneda propia), el complemento implocal:ImpuestosLocales, el reciclaje de folio entre Venta_Encabezado/Factura_Encabezado (nunca confiar en un match de Pc_Documento sin verificar la fecha), y los dos mecanismos de "CFDI de retención huérfano" (omisión de etiquetado vs. sustitución de CFDI sin re-ligar, vía CfdiRetenRelacionados).