Skip to content

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 a Poliza.Pl_Tipo. 1 = Ingresos, 2 = Egresos, 3 = Diario (la pantalla muestra "3 - Diario"). Distribución en Poliza: tipo 2 = 176,776, tipo 3 = 133,186, tipo 1 = 27,906.
  • Pc_Relacion (ntext) — el bloque FROM ... 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 produce Poliza.Pl_Comentario. Aquí vive el placeholder <FECHA>.
  • Pc_Aplicacion_Automatica — SI/NO. Si es SI, el proceso nocturno la ejecuta sin intervención.
  • Pc_Permitir_Importe_Cero — si NO, los renglones en cero se descartan.
  • Es_Cve_Estado — AC (347) / BA (26). No hay CA en 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_Registro concentra la mitad del catálogo (176 de 347). Es donde vive la complejidad y donde apuntan los descuadres.
  • Casi toda Pc_Tabla tiene 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 columna Xx_Tabla del 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:

  1. 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.
  2. Grd_Precio_Neto_Importe >= 0 filtra 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_Credito es la única fila cuyo Pc_Campo_Movimiento viene sin calificar con el nombre de la tabla (Nc_Folio, no Nota_Credito.Nc_Folio). Concatenarlo a ciegas en un SELECT con varios JOINs puede dar ambigüedad — hay que calificarlo a mano.
  • Pago_Cxc y Pago_Cxp usan folio compuesto (Cxc_Folio + Pc_ID). Por eso Poliza_Control.Pc_Documento de 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_CXC como 🔴 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

  1. Contabilidad reporta número de póliza + fecha.
  2. Filtra la póliza en CT001, botón Generar script.
  3. Copia el SQL a Navicat, sustituye <FECHA> y <EMPRESA>.
  4. Ejecuta, pega el resultado en Excel, suma cargo vs abono.
  5. 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.2 en 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ón Gr_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_Configuracion guarda solo Oper_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

  1. Listas IN de centros de costo cableadas en las condiciones de los renglones del grupo 000006. 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.
  2. 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.
  3. 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.
  4. 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é Cheque es el único en 2. Requiere código del ERP o preguntar a Marcelino.
  • Confirmar con Marcelino la hipótesis de redondeo en 0000478288.
  • Extender 05_huecos_ceco_configuracion.py a las configs que no son de Gasto_Registro (Cheque, Movimiento, Venta…). Hoy asume Gasto_Registro.Gr_Fecha como campo de día; generalizarlo es leer Poliza_Configuracion_Tabla.Pc_Campo_Dia, que ya trae ese dato por tabla.
  • La causa del descuadre de $87.20 en el Excel de 0428 sigue 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.