Contexto: simplificar la query de Cargo/Abono (v0.3 propia)

Conexión

connection_200_trivasadb3.py en la raíz del repo (sys.path.append("..") desde layout_gastos/). Expone engine (SQLAlchemy) y q(sql) (helper que regresa DataFrame). Servidor: TRIVASADB3 (.200), empresa 0001.

Estado actual: 99.7% de reconciliación (1421/1425 folios, enero 2026)

Universo: folios de Gasto_Registro en enero 2026, empresa 0001, excluyendo orígenes GASTO_REGISTRO_NOMINA y CONSUMO_INTERNO (no llevan póliza por diseño). Base de comparación: v01_baseline_enero2026_sin_nomina_consumo.csv (columna IMPORTE = subtotal sin IVA, ya con conversión de moneda aplicada).

Query que llega a 99.7% (agregada a nivel folio, sin desglose por documento):

WITH grupo_arbol AS (
    SELECT Gcc_Cve_Grupo_Cuenta_Contable, Gcc_Padre, Gcc_Cve_Grupo_Cuenta_Contable AS raiz
    FROM Grupo_Cuenta_Contable
    WHERE Gcc_Padre = '0' OR Gcc_Padre = '' OR Gcc_Padre IS NULL
    UNION ALL
    SELECT g.Gcc_Cve_Grupo_Cuenta_Contable, g.Gcc_Padre, t.raiz
    FROM Grupo_Cuenta_Contable g
    JOIN grupo_arbol t ON g.Gcc_Padre = t.Gcc_Cve_Grupo_Cuenta_Contable
)
SELECT
    gr.Sc_Cve_Sucursal + '-' + RIGHT('0000000' + CONVERT(varchar, gr.Gr_Folio), 7) AS FOLIO,
    SUM(CASE WHEN pd.Pd_Tipo = 1 THEN pd.Pd_Importe ELSE 0 END) AS CARGO,
    SUM(CASE
            WHEN pd.Pd_Tipo = 2 AND ga.raiz = 'F' THEN pd.Pd_Importe
            WHEN pd.Pd_Tipo = 2 AND ga.raiz IS NULL AND LEFT(pd.Cc_Cve_Cuenta_Contable, 1) = '6' THEN pd.Pd_Importe
            WHEN pd.Pd_Tipo = 2 AND ga.raiz IS NULL AND LOWER(cc.Cc_Descripcion) LIKE '%gastos a cuenta de costo estandar%' THEN pd.Pd_Importe
            ELSE 0
        END) AS ABONO
FROM Gasto_Registro gr
JOIN Sucursal s ON gr.Sc_Cve_Sucursal = s.Sc_Cve_Sucursal
JOIN Poliza_Control plc ON plc.Pc_Tabla = 'GASTO_REGISTRO' AND plc.Pc_Documento = gr.Gr_Folio
JOIN Poliza pl ON pl.Pl_Folio = plc.Pl_Folio
JOIN Poliza_Detalle pd ON pd.Pl_Folio = plc.Pl_Folio AND pd.Pd_Referencia = gr.Gr_Folio
LEFT JOIN Cuenta_Contable cc ON cc.Cc_Cve_Cuenta_Contable = pd.Cc_Cve_Cuenta_Contable
LEFT JOIN grupo_arbol ga ON ga.Gcc_Cve_Grupo_Cuenta_Contable = cc.Cc_Grupo_Cuenta_Contable
WHERE s.Em_Cve_Empresa = '0001'
  AND gr.Gr_Fecha >= '2026-01-01' AND gr.Gr_Fecha < '2026-02-01'
  AND gr.Es_Cve_Estado <> 'CA'
  AND pl.Es_Cve_Estado <> 'CA'
  AND ISNULL(gr.Gr_Tabla, '') NOT IN ('GASTO_REGISTRO_NOMINA', 'CONSUMO_INTERNO')
GROUP BY gr.Sc_Cve_Sucursal, gr.Gr_Folio

Comparación: (CARGO - ABONO) - IMPORTE debe ser < $1 de diferencia.

Bugs ya resueltos (NO reintroducir)

  1. Gr_Folio colisiona entre sucursales → siempre filtrar/joinear con Sc_Cve_Sucursal cuando aplique (aquí no hace falta explícito porque el join a Poliza_Control va fila por fila desde gr ya acotado por fecha+empresa).
  2. Pd_Referencia colisiona entre AÑOS (es solo el número de folio, sin año/sucursal — 3,179 referencias distintas colisionan entre 2020-2026 en toda la tabla). Por eso NO se puede hacer un GROUP BY Pd_Referencia global sobre Poliza_Detalle sin pasar antes por Poliza_Control (Pc_Documento = gr.Gr_Folio) fila por fila desde gr (que ya trae el filtro de fecha). Simplificar el join sin este cuidado revive el bug.
  3. Comparar contra el TOTAL con IVA en vez del SUBTOTAL: Poliza_Detalle nunca incluye impuestos (las líneas de IVA no tienen Pd_Referencia poblado, se agregan a nivel póliza) — comparar contra Grd_Precio_Neto_Importe en vez de Grd_Precio_Descontado_Importe rompe la reconciliación estructuralmente.
  4. Filtro de empresa vía Sucursal.Em_Cve_Empresa = '0001' — Gasto_Registro no trae empresa directo, hay sucursales de otras empresas (0003, 0071; 0004, 0076) mezcladas en la misma base.

4 folios que NO reconcilian (0.3%) — causa conocida, no perseguir más

  • 0005-0181359, 0001-0034998, 0001-0034999: reversiones contables — Importe negativo, póliza trae Cargo≈Abono (partida doble de una reversión). Regla conocida: si Importe_folio <= -$1, usar solo el Abono (poner Cargo en 0).
  • 0005-0177136: origen GASTO_RECLASIFICACION — no debería usar join a póliza en absoluto; su Cargo/Abono real vive en Gasto_Registro_Documento (pares espejo Cargo positivo / Abono negativo, mismo folio, misma cuenta vía Tipo_Gasto).

Implementación de referencia (NO copiar tal cual — es mucho más compleja porque

también resuelve atribución por documento vía Gasto_Registro_Control, que aquí todavía no se necesita)

~/trivasa-bi-dev/layout-gastos-streamlit-claude/scripts/layout_gastos_v0_4_2.py — ver POLIZA_SQL para la query de referencia completa (con las mismas 4 líneas de fallback de cuenta) y armar()/reglas de reversión al final de la función para la lógica de Importe <= -$1.

Objetivo de esta tarea

Simplificar la query de arriba (menos condicionantes en el CASE/WHERE, menos joins si es posible). Piso aceptable: 80% de reconciliación contra v01_baseline_enero2026_sin_nomina_consumo.csv — puedes bajar hasta ahí si eso permite simplificar bastante, no hace falta quedarte cerca de 99%. Está bien perder precisión en casos raros/documentados (los 4 de arriba), pero no reintroducir los 4 bugs de la sección anterior. Iterar y medir el % de reconciliación en cada intento antes de aceptar un cambio; detente en el punto donde menos condicionantes todavía te da >=80%.