Configuración de pólizas en mpro — el motor detrás de Poliza
Exploración 2026-09-03, contra TRIVASADB3 en 192.168.117.205 (backup con
datos buenos hasta junio 2026). Complementa
poliza-explor, que documentó el lado de
salida (Poliza → documento origen). Este documento cubre el lado de
entrada: cómo se define lo que la póliza va a contener.
Fuente primaria: conversación con Marcelino Canche (soporte TIC, 2026-09-03) + capturas de la pantalla + verificación en BD de todo lo que afirmó.
La pantalla
CT001 — Catálogos / Contabilidad / Configurar conexión contable
Es un editor de reglas contables. Cada "póliza" ahí no es un asiento: es una
plantilla que, al ejecutarse contra un día y una empresa, produce N
asientos reales en Poliza/Poliza_Detalle.
El botón Generar script materializa la plantilla como SQL plano — eso es lo que Marcelino copia a Navicat para diagnosticar. El script generado no consulta la configuración en tiempo de ejecución; la traduce.
Las 5 tablas y su mapeo exacto a la pantalla
| Tabla | Qué es | Dónde se ve en la pantalla |
|---|---|---|
Poliza_Configuracion |
Cabecera de la plantilla | Bloque superior izquierdo (Póliza, Descripción, Módulo, Tabla, Tipo, Referencia, Comentario) + panel Relaciones |
Poliza_Configuracion_Condicion |
Filtros globales (WHERE de la cabecera) | Grid central (el listado de líneas Tabla.Campo = valor) |
Poliza_Configuracion_Detalle |
Un renglón contable de la póliza | Grid inferior (Id, Descripción, Condición, Cta. contable, Ce.Co., Concepto, Referencia, Cargo, Abono) |
Poliza_Configuracion_Detalle_Electronico |
Anexo de contabilidad electrónica SAT por renglón | Columna Detalle electrónico del grid inferior |
Poliza_Configuracion_Tabla |
Catálogo: qué tablas del ERP son contabilizables | Dropdowns Módulo y Tabla |
Poliza_Configuracion — la cabecera
Campos que importan:
Pc_Cve_Poliza_Configuracion— la clave de 4 dígitos (0428,0527).Pc_Tabla— tabla origen del ERP (Gasto_Registro,Cheque, …).Pc_Tipo— tipo de póliza contable; se copia tal cual aPoliza.Pl_Tipo.1= Ingresos,2= Egresos,3= Diario (la pantalla muestra "3 - Diario"). Distribución enPoliza: tipo 2 = 176,776, tipo 3 = 133,186, tipo 1 = 27,906.Pc_Relacion(ntext) — el bloqueFROM ... INNER JOIN ...completo, escrito a mano. Es el panel "Relaciones" de la pantalla. Texto libre: no hay validación, un JOIN mal escrito solo falla al generar el script.Pc_Comentario— expresión SQL (con comillas incluidas) que producePoliza.Pl_Comentario. Aquí vive el placeholder<FECHA>.Pc_Aplicacion_Automatica—SI/NO. Si esSI, el proceso nocturno la ejecuta sin intervención.Pc_Permitir_Importe_Cero— siNO, los renglones en cero se descartan.Es_Cve_Estado—AC(347) /BA(26). No hayCAen esta tabla.
Censo: cuántas configuraciones hay por tabla origen
347 activas (AC) + 26 dadas de baja (BA). Distribución de las activas
por Pc_Tabla y Pc_Tipo (1 Ingresos / 2 Egresos / 3 Diario):
Pc_Tabla |
Tipo | Configs |
|---|---|---|
| Gasto_Registro | 3 | 176 |
| Cheque | 2 | 46 |
| Movimiento | 3 | 31 |
| Recibo_Pago | 1 | 28 |
| Nota_Credito | 3 | 14 |
| Nota_Credito_Proveedor | 3 | 14 |
| Pago_Cxc | 3 / 1 | 9 / 2 |
| Cuenta_x_Cobrar | 3 / 1 | 8 / 1 |
| Cuenta_x_Pagar | 3 | 8 |
| Compra_Indirecto | 3 | 6 |
| Pago_Cxp | 3 | 5 |
| Compra | 3 | 5 |
| Venta | 1 / 3 | 5 / 1 |
| Banco_Movimiento | 1 / 2 | 3 / 1 |
| Deposito_Pago | 1 | 3 |
| Anticipo_Cxc, Aplicacion_Cxp_v, Depuracion_Cxc, Movimiento_Produccion, Movimiento_Reproceso, Movimiento_VTA_REM, Ventas_TRIV | 1 o 3 | 1 c/u |
Dos lecturas útiles:
Gasto_Registroconcentra la mitad del catálogo (176 de 347). Es donde vive la complejidad y donde apuntan los descuadres.- Casi toda
Pc_Tablatiene más de una configuración activa. Ese es el indicador correcto para anticipar el patrón de doble póliza en un origen nuevo — no la columnaXx_Tabladel documento, que fue lo que despistó al análisis del lado de salida:
SELECT Pc_Tabla, COUNT(*) FROM Poliza_Configuracion
WHERE Es_Cve_Estado = 'AC' GROUP BY Pc_Tabla HAVING COUNT(*) > 1
De las 29 tablas contabilizables del catálogo, 7 no tienen ninguna
configuración activa — están habilitadas en el catálogo pero nunca se
contabilizan en esta instalación: Anticipo_Cxp, Cheque_Rebotado,
Conversion_Producto, Devolucion_Cliente, Devolucion_Proveedor,
Merma y Transferencia.
Vale la pena notar Devolucion_Cliente y Devolucion_Proveedor ahí: las
devoluciones no generan póliza propia, así que su efecto contable —si lo
hay— entra por otra vía (nota de crédito). No se investigó.
Placeholders — solo existen dos
Verificado sobre las 373 configuraciones:
| Placeholder | Dónde vive | Configs que lo usan |
|---|---|---|
<FECHA> |
Pc_Comentario (cabecera) |
324 |
<EMPRESA> |
Pcc_Valor (condiciones) |
362 |
No existen <SUCURSAL>, <PERIODO> ni <EJERCICIO> — 0 ocurrencias. Esto
confirma exactamente los dos reemplazos que Marcelino describe hacer a mano
en Navicat ("remplazo la fecha … de igual forma el filtro de empresa").
<FECHA> se sustituye en dos lugares distintos con formatos distintos: como
Gasto_Registro.Gr_Fecha = '2026-09-03' en el WHERE (ISO) y como texto
legible en el comentario (DEL 03/09/2026). Ver captura del script generado.
Poliza_Configuracion_Condicion — el WHERE global
Una fila por condición, con Pcc_ID ordenando. Pcc_Valor es SQL crudo que
se concatena con AND. Ejemplo real (config 0428):
Gasto_Registro.Gr_Tabla = ''
Gasto_Registro.Es_Cve_Estado <> 'CA'
Grupo_Tipo_Gasto.Gtg_Cve_Grupo_Tipo_Gasto = '0002'
Gasto_Registro.Gr_Fecha >= '2021-09-01'
Tipo_Proveedor.Tp_Cve_Tipo_Proveedor <> '00'
Tipo_Gasto.Tg_Cve_Tipo_Gasto NOT IN ('0029','0027','0199','0203')
Gasto_Registro_Documento.Grd_Precio_Neto_Importe >= 0
Sucursal.Em_Cve_Empresa = '<EMPRESA>'
Dos cosas a notar:
Gr_Fecha >= 'YYYY-MM-01'es cómo se versiona una configuración. No hay campo de vigencia. Cuando cambia una regla contable se crea una config nueva con esa condición de piso y se da de baja (o se acota) la anterior. De ahí las descripciones tipo... MAYO 2026 EN ADEL— la vigencia vive en el texto de la descripción y en esa condición, nada más.Grd_Precio_Neto_Importe >= 0filtra los importes negativos, que es justo el caso que Marcelino describió como causa de desbalance ("ayer revise uno y es por un importe en negativo de un gasto"). Un gasto negativo cae fuera de las condiciones de la póliza de cargo pero puede seguir entrando por otra partida → descuadre.
Poliza_Configuracion_Detalle — los renglones
Un renglón = una partida contable candidata. Todas las columnas de fórmula
(Pcd_Cuenta_Contable, Pcd_Centro_Costo, Pcd_Concepto,
Pcd_Referencia, Pcd_Cargo, Pcd_Abono, Pcd_Condicion) son ntext con
expresiones SQL, no valores. Se inyectan literalmente en el SELECT /
WHERE del script generado.
Un renglón es de cargo o de abono según cuál de Pcd_Cargo / Pcd_Abono
está lleno — no hay bandera de tipo. Eso se convierte en
Poliza_Detalle.Pd_Tipo (1 = cargo, 2 = abono).
Pcd_Relacion guarda los JOINs adicionales que ese renglón necesita y
que no están en la cabecera. Es la pieza que faltaba para reconstruir el
query: la cabecera trae el tronco (Gasto_Registro + documento + proveedor),
y cada renglón añade lo suyo — los de CeCo añaden
Gasto_Registro_Control + Centro_Costo + Grupo_Centro_Costo, los de
impuesto añaden Gasto_Registro_Impuesto + Impuesto.
Pcd_Agrupa, Pcd_Tipo_Detalle y Pcd_Comando_Detalle están vacíos en
las 5,201 filas de la tabla. Son campos muertos en esta instalación.
El query completo, reconstruido
Con las piezas anteriores se puede reproducir lo que hace "Generar script" sin usar la pantalla:
FROM Pc_Relacion -- joins de cabecera
Pcd_Relacion -- joins propios del renglón
WHERE Pcc_Valor AND Pcc_Valor AND ... -- condiciones globales
AND Pcd_Condicion -- condición del renglón
SELECT Pcd_Cuenta_Contable, Pcd_Centro_Costo, Pcd_Concepto,
Pcd_Referencia, Pcd_Cargo | Pcd_Abono
Esto es lo que hace 05_huecos_ceco_configuracion.py, y es reutilizable
para cualquier auditoría sobre configuraciones sin depender de CT001.
Cómo se elige la cuenta contable — el patrón Cuenta_ContableN
Este es el hallazgo más útil del lado de gastos. En la config 0428
(previsión social) hay 15 renglones de cargo idénticos salvo por dos cosas:
la condición de centro de costo y el sufijo de la cuenta.
| Pcd_ID | Condición | Cuenta |
|---|---|---|
| 0010 | Gcc_Cve_Grupo_Centro_Costo = '000001' (Venta) |
opc_Tipo_Gasto.Cuenta_Contable1 |
| 0020 | = '000002' (Distribución) |
Cuenta_Contable2 |
| 0030 | = '000003' (Logística) |
Cuenta_Contable3 |
| 0040 | = '000004' (Mantenimiento) |
Cuenta_Contable4 |
| 0050 | IN ('000005','000007','000008','000009') (Administración) |
Cuenta_Contable5 |
| 0060 | = '000006' + lista explícita de 12 CeCos (Mina) |
Cuenta_Contable6 |
| 0070 | = '000006' + 17 CeCos (Agregado) |
Cuenta_Contable7 |
| 0080 / 0081 | = '000006' + CeCos (Vibroprensado X-Nohbotún / Xcitán) |
Cuenta_Contable8 / 13 |
| 0090 / 0091 / 0092 | Pretensado / Embolsado / Concretera | 9 / 10 / 14 |
| 0093 / 0094 | Consultoría FLX / Desarrollo FLX | 11 / 12 |
La cuenta contable no se escribe en la configuración: se resuelve contra
opc_Tipo_Gasto, que tiene 14+ columnas Cuenta_ContableN, una por línea de
negocio. La configuración solo decide cuál columna leer, en función del
grupo de centro de costo.
Consecuencia operativa directa: un CeCo nuevo del grupo 000006 que nadie
agregue a la lista IN de alguno de los renglones 0060–0094 no genera cargo,
pero sí genera su abono a acreedores — descuadre garantizado. Es
exactamente el "algún ceco que no se contempló" que menciona Marcelino. Los
grupos 000001–000005 usan igualdad de grupo y son inmunes; el grupo
000006 (producción) es el frágil, porque se desglosa por lista explícita
de CeCos.
Los renglones de abono usan otro eje — la moneda y el proveedor:
| Pcd_ID | Condición | Cuenta de abono |
|---|---|---|
| 0120 | Mn_Cve_Moneda='MXN' y proveedor NOT IN (7 claves) |
Tipo_Proveedor.Tp_Cuenta_Contable |
| 0130 | Mn_Cve_Moneda='MXN' y proveedor IN (esas 7) |
Proveedor.Pv_Cuenta_Contable |
| 0140 | Mn_Cve_Moneda='USD' |
Tipo_Proveedor.Tp_Cuenta_Contable_1 |
| 0150 | Mn_Cve_Moneda='USD' (complementaria por tipo de cambio) |
Tipo_Proveedor.Tp_UserDef_1 |
Los renglones 0100/0110 manejan impuestos partiendo por signo de tasa:
Impuesto.Im_Tasa >= 0 → cargo a IVA acreditable; Im_Tasa < 0 → abono a
impuesto por retener. La cuenta sale de Impuesto.Im_Cuenta_Contable.
Nótese que 0140 y 0150 tienen la misma condición: una compra en USD
genera dos abonos, el nominal y el diferencial cambiario
(importe * tipo_cambio - importe).
Poliza_Configuracion_Detalle_Electronico — anexo SAT
1,064 filas. Pcde_Comando es SQL construido por concatenación de
strings (... + CHAR(39) + 'GASTO_REGISTRO' + CHAR(39) + ...), porque el
resultado se ejecuta dinámicamente. CHAR(39) es la comilla simple.
Alimenta las tablas Poliza_Detalle_Comprobante,
Poliza_Detalle_Comprobante_Extranjero, Poliza_Detalle_Cheque,
Poliza_Detalle_Transferencia.
Pcde_Tipo |
Filas | Destino |
|---|---|---|
'COMPROBANTE' |
825 | UUID + RFC del CFDI, desde Comprobante_Digital |
'COMPROBANTE EXTRANJERO' |
119 | equivalente para extranjero |
'TRANSFERENCIA' |
67 | Poliza_Detalle_Transferencia |
'CHEQUE' |
53 | Poliza_Detalle_Cheque |
Poliza_Configuracion_Tabla — qué se puede contabilizar
29 tablas en 8 módulos. Define, por tabla origen, el campo de fecha
(Pc_Campo_Dia) y el campo de folio (Pc_Campo_Movimiento) que el
generador usa para filtrar por día y para poblar Poliza_Control.
Contenido completo de la tabla (Pc_Modo = Pc_Modo_Contabiliza). Es el
dato que necesita cualquier script que quiera reconstruir el query de una
configuración sin asumir el módulo de gastos:
| Módulo | Pc_Tabla |
Pc_Campo_Dia |
Pc_Campo_Movimiento |
Pc_Modo |
|---|---|---|---|---|
| BANCOS | Banco_Movimiento | Banco_Movimiento.Bm_Fecha |
Banco_Movimiento.Bm_Folio |
1 |
| BANCOS | Cheque | Cheque.Ch_Fecha |
Cheque.Ch_Folio |
2 |
| BANCOS | Cheque_Rebotado | Cheque_Rebotado.Cr_Fecha |
Cheque_Rebotado.Cr_Folio |
0 |
| BANCOS | Deposito_Pago | Deposito_Pago.Dp_Fecha |
Deposito_Pago.Dp_Folio |
1 |
| CAJA | Recibo_Pago | Recibo_Pago.Rp_Fecha |
Recibo_Pago.Rp_Folio |
0 |
| COMPRAS | Compra | Compra.Co_Fecha |
Compra.Co_Folio |
0 |
| COMPRAS | Compra_Indirecto | Compra_Indirecto.Ci_Fecha |
Compra_Indirecto.Ci_Folio |
0 |
| COMPRAS | Devolucion_Proveedor | Devolucion_Proveedor.Dp_Fecha |
Devolucion_Proveedor.Dp_Folio |
0 |
| CXC | Anticipo_Cxc | Anticipo_Cxc.An_Fecha |
Anticipo_Cxc.An_Folio |
0 |
| CXC | Aplicacion_Cxp_v | Aplicacion_Cxp_v.Ac_Fecha |
Aplicacion_Cxp_v.Ac_Folio |
0 |
| CXC | Cuenta_x_Cobrar | Cuenta_x_Cobrar.Cxc_Fecha |
Cuenta_x_Cobrar.Cxc_Folio |
0 |
| CXC | Depuracion_Cxc | Depuracion_Cxc.Dcc_Fecha |
Depuracion_Cxc.Dcc_Folio |
0 |
| CXC | Nota_Credito | Nota_Credito.Nc_Fecha |
Nc_Folio ⚠️ |
0 |
| CXC | Pago_Cxc | Pago_Cxc.Pc_Fecha |
Pago_Cxc.Cxc_Folio + Pago_Cxc.Pc_ID ⚠️ |
0 |
| CXP | Anticipo_Cxp | Anticipo_Cxp.An_Fecha |
Anticipo_Cxp.An_Folio |
0 |
| CXP | Cuenta_x_Pagar | Cuenta_x_Pagar.Cxp_Fecha |
Cuenta_x_Pagar.Cxp_Folio |
0 |
| CXP | Nota_Credito_Proveedor | Nota_Credito_Proveedor.Nc_Fecha |
Nota_Credito_Proveedor.Nc_Folio |
0 |
| CXP | Pago_Cxp | Pago_Cxp.Pc_Fecha |
Pago_Cxp.Cxp_Folio + Pago_Cxp.Pc_ID ⚠️ |
0 |
| GASTOS | Gasto_Registro | Gasto_Registro.Gr_Fecha |
Gasto_Registro.Gr_Folio |
0 |
| INVENTARIOS | Conversion_Producto | Conversion_Producto.Cp_Fecha |
Conversion_Producto.Cp_Folio |
0 |
| INVENTARIOS | Merma | Merma.Mr_Fecha |
Merma.Mr_Folio |
0 |
| INVENTARIOS | Movimiento | Movimiento.Mv_Fecha |
Movimiento.Mv_Folio |
1 |
| INVENTARIOS | Movimiento_Produccion | Movimiento_Produccion.Mv_Fecha |
Movimiento_Produccion.Mv_Folio |
1 |
| INVENTARIOS | Movimiento_Reproceso | Movimiento_Reproceso.Mv_Fecha |
Movimiento_Reproceso.Mv_Folio |
1 |
| INVENTARIOS | Movimiento_VTA_REM | Movimiento_VTA_REM.Mv_Fecha |
Movimiento_VTA_REM.Mv_Folio |
1 |
| INVENTARIOS | Transferencia | Transferencia.Tr_Fecha |
Transferencia.Tr_Folio |
0 |
| VENTAS | Devolucion_Cliente | Devolucion_Cliente.Dc_Fecha |
Devolucion_Cliente.Dc_Folio |
0 |
| VENTAS | Venta | Venta.Vn_Fecha |
Venta.Vn_Folio |
0 |
| VENTAS | Ventas_TRIV | Ventas_TRIV.Vn_Fecha |
Ventas_TRIV.Vn_Folio |
0 |
Tres irregularidades marcadas ⚠️, que hay que manejar como casos especiales al generalizar cualquier script:
Nota_Creditoes la única fila cuyoPc_Campo_Movimientoviene sin calificar con el nombre de la tabla (Nc_Folio, noNota_Credito.Nc_Folio). Concatenarlo a ciegas en unSELECTcon varios JOINs puede dar ambigüedad — hay que calificarlo a mano.Pago_CxcyPago_Cxpusan folio compuesto (Cxc_Folio + Pc_ID). Por esoPoliza_Control.Pc_Documentode esos orígenes no liga contra un folio simple: hay que partirlo. Es una de las razones por las que el censo del lado de salida clasificóPago_CXCcomo 🔴 muy complejo.
Pc_Campo_Dia es el campo con el que el generador filtra el día. Es
exactamente el valor que 05_huecos_ceco_configuracion.py tiene hardcodeado
como Gasto_Registro.Gr_Fecha; sustituirlo por una lectura de esta tabla es
todo lo que falta para que el script sirva para los 29 orígenes.
Pc_Modo_Contabiliza (0/1/2) y Pc_Contabiliza_Linea (siempre NO) son
banderas del motor cuya semántica exacta no se pudo determinar desde datos;
Cheque es el único con modo 2, y los de INVENTARIOS + Banco_Movimiento
+ Deposito_Pago usan 1. El resto, 0.
Poliza.Pl_Configuracion — el enlace que faltaba
Poliza.Pl_Configuracion guarda la clave de la configuración que generó
cada póliza. Está poblado en 334,374 de 337,868 pólizas (98.97%); los
3,494 vacíos son pólizas capturadas fuera del motor (manuales) y se
concentran al inicio del histórico.
Esto corrige un supuesto del proyecto anterior: no hace falta inferir el
origen solo por Poliza_Control.Pc_Tabla. Pl_Configuracion da la regla
exacta — no solo el módulo, sino qué variante de la regla se aplicó.
SELECT p.Pl_Folio, p.Pl_Fecha, c.Pc_Descripcion, c.Pc_Tabla
FROM Poliza p
JOIN Poliza_Configuracion c
ON c.Pc_Cve_Poliza_Configuracion = p.Pl_Configuracion
WHERE p.Es_Cve_Estado <> 'CA'
El patrón de "doble póliza" queda explicado
poliza-explor documentó como anomalía que CONSUMO_INTERNO y
COMPRA_INDIRECTO generan dos pólizas activas paralelas bajo el mismo
Pc_Documento, y lo dejó como sospecha de patrón oculto.
No es una anomalía: son dos configuraciones distintas sobre la misma
Pc_Tabla, una de ellas marcada (CUENTAS DE ORDEN) en su descripción, y
ambas con Pc_Aplicacion_Automatica = 'SI'. Ambas corren, ambas producen
póliza. Pares confirmados, por conteo de uso 2026:
| Real | Cuentas de orden | Tabla | Pólizas 2026 c/u |
|---|---|---|---|
| 0348 INDIRECTOS EN COMPRAS NACIONAL 2020 EN ADELANTE | 0349 INDIRECTOS EN COMPRAS 2020 EN A (CUENTAS DE ORDEN) | Compra_Indirecto | — |
| 0396 CONSUMO INTERNO SEP 2021 EN ADELANTE | 0234 CONSUMO INTERNO 2018 EN ADELANT (CUENTAS DE ORDEN) | Gasto_Registro | 219 / 220 |
| 0273 CONSUMO INTERNO MANTTO PREVENTIVO 2018 | 0275 CONSUMO INTERNO MTTO PREVE 2018 (CUENTAS DE ORDEN) | Gasto_Registro | 191 / 191 |
| 0300 CONSUMO INTERNO EQUIPO SEGURIDAD 2018 | 0301 CONSUMO INTERNO EQ SEGURID 2018 (CUENTAS DE ORDEN) | Gasto_Registro | 164 / 164 |
| 0346 COMPRAS NACIONAL 2019 EN ADELANTE | 0347 COMPRAS 2019 EN ADELANTE (CUENTAS DE ORDEN) | Compra | 216 / 217 |
| 0368 ENTREGA DE INVENTARIOS A CLIENTES 2020 | 0233 ENTREGA INVENTARIO 2018 EN ADE (CUENTAS DE ORDEN) | Movimiento | 233 / 233 |
| 0454 CONVERSION DE PRODUCTO SEP 2023 | 0455 + 0456 (SALIDA / ENTRADA, CTAS. ORDEN) | Movimiento | 194 / 194 / 194 |
Los conteos pareados casi perfectos (219/220, 216/217, 164/164) son la firma
del patrón. 0454 es un caso de triple póliza, no doble.
Regla general para aislar la póliza "real": filtrar por
Pl_Configuracion explícito, o descartar
Pc_Descripcion LIKE '%CUENTAS DE ORDEN%'. El filtro por Pl_Comentario
que usaba el proyecto anterior funciona pero es más frágil.
Y Deposito_Pago no tiene el patrón — pendiente cerrado
poliza-explor dejó abierta la sospecha de que Deposito_Pago escondiera
doble póliza (consolidación mediana de 163 documentos/póliza sin
sub-orígenes visibles). No la tiene: sus 3 configuraciones (0173,
0444, 0493) son versiones sucesivas por vigencia (2018–ene 2023 /
feb–dic 2023 / feb 2024 en adelante), ninguna es de cuentas de orden, y solo
0493 está en uso en 2026 (233 pólizas). Su consolidación alta es legítima:
un depósito bancario agrupa muchos pagos.
Desbalance — el proceso de Marcelino, y qué tanto hace falta
Su proceso, tal cual
- Contabilidad reporta número de póliza + fecha.
- Filtra la póliza en
CT001, botón Generar script. - Copia el SQL a Navicat, sustituye
<FECHA>y<EMPRESA>. - Ejecuta, pega el resultado en Excel, suma cargo vs abono.
- Busca en los registros un importe igual a la diferencia.
Causas típicas que reportó: registro duplicado, CeCo no contemplado, algo mal cancelado, importe en negativo (el último caso real).
El Excel poliza_0428.xlsx — anatomía
448 filas, salida cruda del paso 4 contra la config 0428 (previsión
social). Columnas = las de Poliza_Detalle.
- Cargos: 2,391,809.52
- Abonos: 2,391,722.32
- Diferencia: 87.20 (la celda suelta
87.2en la columna H es su fórmula manual)
Ojo al leer el archivo con pandas: cada fila aparece duplicada, así que
df.Cargo.sum() da 4,783,619.04. Hay que agrupar por Tipo (1=cargo,
2=abono) para el número correcto.
Ninguna fila trae importe negativo — el descuadre de 87.20 no es un negativo
crudo, es asimetría entre las condiciones de cargo y las de abono (el
candidato más probable, dado el patrón Cuenta_ContableN: un CeCo del grupo
000006 fuera de las listas IN).
Las referencias (Pd_Referencia) traen el folio de Gasto_Registro con
prefijo de sucursal: 01- (272), 13- (64), 23- (44), 05- (38),
09- (20), y 7 filas con Fa (las de acreedores, que usan
'Fact: '+Grd_Referencia).
Lo importante: el desbalance casi nunca llega a Poliza
Marcelino lo dijo ("normalmente si está desbalanceada, no se genera la
póliza") y los datos lo confirman. De ~40,000 pólizas de 2026, solo 4
están descuadradas en Poliza_Detalle, y 3 de ellas están canceladas:
| Pl_Folio | Fecha | Config | Estado | Cargo | Abono | Dif |
|---|---|---|---|---|---|---|
| 0000478287 | 2026-02-28 | 0210 | CA | 142,596.00 | 85,874.00 | 56,722.10 |
| 0000497728 | 2026-07-31 | (vacía) | CA | 42,791.50 | 85,564.80 | −42,773.30 |
| 0000473109 | 2026-01-19 | 0382 | CA | 140,032.00 | 140,039.00 | −7.29 |
| 0000478288 | 2026-02-19 | 0394 | AC | 237.14 | 242.50 | −5.36 |
Implicación para el diagnóstico: la póliza descuadrada es una que no
existe todavía. Por eso el proceso pasa por regenerar el script — no hay
nada que consultar en Poliza. Un detector automático tiene que reejecutar
la configuración, no leer el resultado.
El único descuadre vivo — 0000478288
Config 0394, COMPROBACION DE GASTOS NACIONAL DEL 19/feb/2026, documento
GASTO_REGISTRO 01-0035339. 81 renglones: 80 cargos de centavos
(0.49, 0.99, 1.97, 2.47, 13.85, 19.79 …) contra un solo abono de 242.50
a un fondo fijo. Los 80 cargos suman 237.14.
Es un prorrateo de un gasto entre 81 centros de costo del que solo 80
generaron cargo. Causa raíz confirmada más abajo ("CeCos no cubiertos"):
falta 000501 PLANTA CONCRETERA, $4.9489, más $0.41 de redondeo.
CeCos no cubiertos — medición rigurosa
Primero se intentó por regex: extraer los CeCos citados en las condiciones
y restarlos del catálogo del grupo 000006. Daba 69 configuraciones en
uso con CeCos "descubiertos". Ese número era ruido — una config filtra
además por grupo de tipo de gasto, proveedor y fecha, así que un CeCo
ausente de sus listas normalmente nunca le llega. Se descartó el enfoque.
La prueba correcta reconstruye el query del generador y pregunta directamente: ¿qué filas entran a la póliza y no encuentran renglón de cargo?
FROM <Pc_Relacion> <Pcd_Relacion>
WHERE <condiciones globales>
AND NOT ( <cond_renglón_1> OR <cond_renglón_2> OR ... )
Cero filas = la configuración cubre todo su universo.
Resultado sobre H1 2026, empresa 0001, 71 configuraciones de
Gasto_Registro activas y en uso (0 errores de ejecución): solo 2
tienen huecos reales.
| Config | Descripción | CeCos sin cubrir | ¿Descuadra? |
|---|---|---|---|
0394 |
COMPROBACION DE GASTOS NACIONAL SEP 2021 EN ADELANTE | 1 (000501 PLANTA CONCRETERA) |
Sí — causa confirmada del único descuadre vivo de 2026 |
0371 |
RECLASIFICACION DE GASTO 2020 EN ADELANTE | 20 | No — falla silenciosa, ver abajo |
La config 0428 del Excel de Marcelino no tiene huecos. Su descuadre de
$87.20 tiene otra causa, aún no identificada.
0394 — causa raíz del descuadre 0000478288, confirmada
La póliza 0000478288 (gasto 01-0035339) tiene 81 CeCos en
Gasto_Registro_Control pero solo 80 renglones de cargo. El que falta
es 000501 PLANTA CONCRETERA, por importe $4.9489 — no está en ninguna
lista IN de los renglones de cargo de 0394.
El descuadre total de la póliza es $5.36. El CeCo faltante explica $4.95; los $0.41 restantes sí son redondeo del prorrateo entre 80 renglones.
Esto corrige la hipótesis de la sesión anterior, que atribuyó todo el descuadre a redondeo. El redondeo era el residuo, no la causa: es exactamente el modo de falla "algún ceco que no se contempló" que describe Marcelino, capturado por primera vez de punta a punta.
0371 — el hueco que no descuadra (y es peor)
Sus 20 CeCos descubiertos incluyen algunos con volumen alto (000509
DESARROLLO DE NEGOCIOS, 80 renglones; 000457 SERVICIOS GENERALES, 78;
000140 PLANEACION Y PRESUPUESTO, 57). Aun así, sus 30 pólizas de 2026
cuadran todas.
La razón: 0371 es una reclasificación, y sus renglones son simétricos
— 12 de cargo y 12 de abono, con las mismas listas de CeCo. Un CeCo
descubierto pierde los dos lados a la vez, así que el descuadre se cancela
solo.
Es un modo de falla que ninguna validación de cuadre puede detectar: la
póliza sale correcta y la reclasificación simplemente no se hizo. El gasto
se queda en la cuenta de origen sin que nada lo señale. Muchos de esos
CeCos están marcados (NO UTILIZAR), lo que sugiere que buena parte es
histórico inofensivo — pero 000020 DIRECCION GENERAL, 000028 RRHH,
000068 TRACTOS PLANAS, 000582 BLOQUERA 1 y 000103 TRITURADORA METSO
están activos y con movimiento. Vale la pena revisarlo con Contabilidad.
Deuda de configuración
- 347 configuraciones activas (
AC), de las cuales 180 no generaron ni una póliza en 2026. Poco más de la mitad del catálogo es histórico que sigue marcado como activo. Como la vigencia se expresa en una condiciónGr_Fecha >= ...y no en un campo de estado, dar de baja de verdad es manual y no se hace. - Churn concentrado en 2026: 188 de 373 configuraciones se modificaron este
año. Editores principales:
IVALDEZ(235),GSANSORES(84),CGARCIA(14),JEROSA(11). - No hay historial de versiones.
Poliza_Configuracionguarda soloOper_Ult_Modif/Fecha_Ult_Modif— se pierde qué cambió. Una config editada re-interpreta el pasado si se regenera una póliza vieja.
Riesgos identificados
- Listas
INde centros de costo cableadas en las condiciones de los renglones del grupo000006. Un CeCo nuevo no cae en ninguna → cargo ausente, abono presente, descuadre. Es la causa estructural más probable de los descuadres recurrentes, y es detectable de forma preventiva. Medido — ver sección siguiente. - SQL como texto sin validación en 7 columnas
ntext. No hay parser que avise de una tabla renombrada o un JOIN roto hasta que el script truena o, peor, devuelve filas de menos silenciosamente. Pc_Aplicacion_Automatica = 'SI'en pares de cuentas de orden: si se crea la config real de un periodo nuevo y se olvida la de cuentas de orden (o al revés), el desbalance no es dentro de una póliza sino entre las dos, y ninguna validación de cuadre por póliza lo detecta.- Vigencia expresada en texto libre (descripción
... MAYO 2026 EN ADEL) + condición de fecha. Dos configs con rangos traslapados sobre la misma tabla generarían pólizas duplicadas sin que nada lo impida.
Pólizas manuales — sí existen, y son identificables
Le preguntaste a Marcelino si hay movimientos manuales de pólizas tipo CONTPAQ y quedó en "no sé si contabilidad tenga alguna forma de moverle". Sí las hay, y tienen una firma exacta en datos:
-- póliza capturada a mano: sin regla que la generó y sin documento origen
SELECT * FROM Poliza p
WHERE ISNULL(p.Pl_Configuracion,'') = ''
AND NOT EXISTS (SELECT 1 FROM Poliza_Control c WHERE c.Pl_Folio = p.Pl_Folio)
Los 3,494 registros con Pl_Configuracion vacío no tienen ni una fila en
Poliza_Control (0 de 3,494). No son carga histórica de migración: están
repartidos por todos los años, con volumen estable, y los captura gente con
nombre.
| Año | 2019 | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | 2026 (parcial) |
|---|---|---|---|---|---|---|---|---|
| Pólizas manuales | 106 | 241 | 774 | 535 | 552 | 489 | 582 | 178 |
Principales capturistas: IVALDEZ (1,282), JFERNANDEZ (582), JEROSA
(390), AMEDINA (285), RMENA (279), GSANSORES (276).
Son ~500 al año, alrededor del 1% del total. Es el canal por el que
Contabilidad hace ajustes fuera del motor — el equivalente a la póliza
manual de CONTPAQ que buscabas. Ninguna reconciliación que parta de
Poliza_Control las verá.
Sobre correcciones manuales en BD
Marcelino confirma que a veces "se puede dar el caso cambiar en base de
datos el registro, para no hacer cancelación de todo", aunque hoy
predomina la corrección por pantalla. No existe bitácora de esos cambios más
allá de Oper_Ult_Modif en la tabla afectada. Sobre pólizas manuales, ver la sección anterior: no las hay en el flujo de
soporte de TIC, pero sí en el de Contabilidad.
Addendum 2026-09-08 — Pcd_Referencia no siempre es el folio: caso CONSUMO_INTERNO activo fijo
Investigando por qué un folio concreto de CONSUMO_INTERNO no conciliaba
(Poliza_Detalle filtrado por Pd_Referencia = Gr_Folio no traía nada,
pese a tener póliza real) se confirmó, leyendo
Poliza_Configuracion_Detalle directamente en vez de inferir el patrón,
que no todas las configuraciones usan el folio como referencia.
De las configuraciones activas sobre Gasto_Registro, dos (0295
"CONSUMO INTERNO ADICION Y MEJORAS 2018-AGO 2021" y 0414 su sucesora
vigente) tienen Pcd_Referencia = Gasto_Registro.Gr_Referencia — una
columna real de Gasto_Registro, no el folio (Gr_Folio). Son las
configuraciones que cubren el grupo de Tipo_Gasto de activo fijo
(0181-0188, 0192, 0246 — condición Pcc_Valor de la cabecera) y
cuentan a 1210.xxx (Activo Fijo), a diferencia del resto de renglones
de CONSUMO_INTERNO (cuenta Tipo_Gasto.Tg_Cuenta_Contable, referencia
Gr_Folio, vía 0396/su par de cuentas de orden 0234).
Consecuencia para cualquier script que reconstruya el join a mano en vez
de leer la configuración: filtrar Poliza_Detalle por
Pd_Referencia = Gr_Folio es correcto para la mayoría de las
configuraciones, pero deja fuera silenciosamente (Cargo=Abono=0, no un
error SQL) los folios de las configs cuya referencia es otro campo. La
Pc_Relacion de 0414 tampoco parte de Gasto_Registro_Documento/
Gasto_Registro_Control (la fuente que usan los scripts de reconciliación
de layout-gastos) — parte de Consumo_Interno/Consumo_Interno_Ceco,
ligadas por Gasto_Registro.Gr_Documento = Consumo_Interno.Ci_Folio. Las
cifras coinciden con Grc_Importe (confirmado en el caso investigado),
pero no se validó que coincidan siempre.
Complicación real al usar Gr_Referencia como llave de join:
Gr_Referencia no es único dentro de una póliza — puede haber 2+ folios
de Gasto_Registro con el mismo código de referencia consolidados bajo el
mismo Pl_Folio (medido: 8 de 29 pólizas de esta rama en enero-marzo
2026). Se resuelve por rank-pairing (ordenar ambos lados por importe
dentro de (Pl_Folio, Referencia) y emparejar por posición) — mismo
patrón que ya usa conciliacion-master/layout-gastos para
Grc_ID↔Poliza_Detalle en los orígenes sin llave real.
Detalle completo, drill-down del folio original (07-0082044/póliza
472949) y el resultado numérico (99.37% → 99.84%) en
conciliacion-master/layout-gastos/PROGRESS.md, entrada 2026-09-08.
Aplicado en 12_reporte_base_ceco_consumo_interno.py de ese proyecto.
Moraleja que confirma la sección anterior: cuando un join "por
inferencia" no cuadra, la fuente de verdad es Poliza_Configuracion_Detalle
(Pcd_Referencia, Pcd_Relacion), no adivinar el patrón por prueba y
error sobre los datos.
Addendum 2026-09-11 — Pcd_Condicion vacío rompe un query que la asume siempre presente
Al generalizar la reconstrucción del query (El query completo,
reconstruido arriba) a las 44 configs reales usadas por
layout-gastos (antes solo probado contra 0450), 4 configs
(0039/0041/0139/0284) fallaban con error de sintaxis SQL
(Incorrect syntax near ')').
Causa: Pcd_Condicion puede venir vacío para un renglón — caso
válido, significa "sin condición extra además de las globales" — pero
un código que siempre lo envuelve como AND ({Pcd_Condicion}) (patrón
documentado arriba) genera AND (), SQL inválido, en vez de omitir la
cláusula o usar 1=1. Nunca se había visto con la config 0450 porque
esa config en particular no tiene ningún renglón con Pcd_Condicion
vacío — pasó desapercibido hasta generalizar a un universo más amplio de
configs.
Fix (aplicado en poliza_configuracion_lib.reconstruir_config(),
github.com/ehalso/conciliacion-cfdi, layout_gastos_poliza/):
cond_sql = Pcd_Condicion if Pcd_Condicion.strip() else "1=1". Aditivo,
sin riesgo para configs que ya funcionaban. Detalle completo en
layout_gastos_poliza/PROGRESS.md de ese repo, entrada 2026-09-11.
Misma moraleja de arriba: cualquier query que reconstruya Poliza_
Configuracion_Detalle a mano debe tratar cada columna de fórmula
(Pcd_Condicion, Pcd_Relacion, etc.) como potencialmente vacía, no
asumir que siempre trae contenido solo porque las configs probadas hasta
ahora lo tenían.
Qué queda pendiente
- Semántica exacta de
Pc_Modo_Contabiliza(0/1/2) y por quéChequees el único en2. Requiere código del ERP o preguntar a Marcelino. - Confirmar con Marcelino la hipótesis de redondeo en
0000478288. - Extender
05_huecos_ceco_configuracion.pya las configs que no son deGasto_Registro(Cheque, Movimiento, Venta…). Hoy asumeGasto_Registro.Gr_Fechacomo campo de día; generalizarlo es leerPoliza_Configuracion_Tabla.Pc_Campo_Dia, que ya trae ese dato por tabla. - La causa del descuadre de $87.20 en el Excel de
0428sigue abierta: no es hueco de CeCo (se descartó) ni importe negativo (no hay filas negativas). Falta pedirle a Marcelino la fecha y empresa exactas para reproducir su corrida. - Revisar con Contabilidad los CeCos activos descubiertos de
0371.