Skip to content

04_diagnostico_compra_indirecto.py

Ruta original: docs/proyectos/poliza-explor/scripts/04_diagnostico_compra_indirecto.py

"""
Diagnostico Compra_Indirecto: CORRIGE la clasificacion de 01_conteo_polizas_por_origen.py.

Ci_Tabla viene siempre vacio (sin sub-origenes propios), lo que en el
analisis inicial sugeria "0 casos de negocio = simple", igual que Cheque y
Recibo_Pago. FALSO -- Compra_Indirecto comparte el mismo patron de "dos
polizas paralelas" ya documentado para CONSUMO_INTERNO en
docs/schema/calidad-de-datos.md de trivasa-context: cada Ci_Folio genera
DOS polizas activas bajo el mismo Pc_Documento:

1. Poliza "real" (Pl_Comentario NOT LIKE '%CUENTAS ORDEN%') -- Cargo a
   cuenta de almacen/costo (ej. 1140.001.001.003), UN renglon por Ci_Folio
   con el TOTAL sumado: Cargo = SUM(Ci_Precio_Descontado_Importe) de todos
   los Ci_ID de ese folio.
2. Poliza "cuentas de orden" (Pl_Comentario LIKE '%CUENTAS ORDEN%') --
   Cargo a cuenta memo raiz H (ej. 10500.012.011), un renglon POR CADA
   Ci_ID (no sumado), mismo importe (Ci_Precio_Descontado_Importe) pero a
   nivel linea.

Ademas hay pares cancelada->activa del mismo folio (mismo patron de
Cheque) que se resuelven con el filtro estandar Es_Cve_Estado <> 'CA'.

Validado: enero 2026, 5/5 documentos (100%) -- la poliza real cuadra
exacto contra SUM(Ci_Precio_Descontado_Importe) filtrando por
Pl_Comentario. Volumen bajo (5 documentos / 14 polizas activas+canceladas
en enero 2026) -- no se investigo Q1 completo por ser de impacto marginal,
a diferencia de Cheque y Recibo_Pago.

Pendiente: nunca se investigo el otro lado (Abono) de ninguna de las dos
polizas -- este script solo valida el Cargo de la poliza "real".

Uso:
    python3 04_diagnostico_compra_indirecto.py --fecha-ini 2026-01-01 --fecha-fin 2026-02-01
"""
import argparse

from connection_205_trivasadb3 import engine  # default de exploracion -- ver skill trivasa-sql-exploracion

import pandas as pd
from sqlalchemy import text
from helpers_output import console, mostrar_tabla, resumen

QUERY_REAL = """
SELECT plc.Pl_Folio, plc.Pc_Documento, pl.Pl_Comentario,
       SUM(CASE WHEN pd.Pd_Tipo = 1 THEN pd.Pd_Importe ELSE 0 END) AS CARGO
FROM Poliza pl
JOIN Poliza_Control plc ON plc.Pl_Folio = pl.Pl_Folio AND plc.Pc_Tabla = 'COMPRA_INDIRECTO'
JOIN Poliza_Detalle pd ON pd.Pl_Folio = plc.Pl_Folio AND pd.Pd_Referencia = plc.Pc_Documento
WHERE pl.Pl_Fecha >= :fecha_ini AND pl.Pl_Fecha < :fecha_fin
  AND pl.Es_Cve_Estado <> 'CA'
  AND UPPER(pl.Pl_Comentario) NOT LIKE '%CUENTAS ORDEN%'
GROUP BY plc.Pl_Folio, plc.Pc_Documento, pl.Pl_Comentario
"""

QUERY_ESPERADO = """
SELECT Ci_Folio, SUM(Ci_Precio_Descontado_Importe) AS ESPERADO
FROM Compra_Indirecto
GROUP BY Ci_Folio
"""


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--fecha-ini", required=True)
    ap.add_argument("--fecha-fin", required=True)
    args = ap.parse_args()

    real = pd.read_sql(
        text(QUERY_REAL), engine,
        params={"fecha_ini": args.fecha_ini, "fecha_fin": args.fecha_fin},
    )
    esperado = pd.read_sql(text(QUERY_ESPERADO), engine)

    comp = real.merge(esperado, left_on="Pc_Documento", right_on="Ci_Folio", how="left")
    comp["DIFERENCIA"] = (comp["CARGO"] - comp["ESPERADO"]).abs()
    comp["CUADRA"] = comp["DIFERENCIA"] <= 1

    out = f"04_diagnostico_compra_indirecto_{args.fecha_ini}_{args.fecha_fin}.csv"
    comp.to_csv(out, index=False)

    mostrar_tabla(comp, "Poliza real (Compra_Indirecto) vs SUM(Ci_Precio_Descontado_Importe)")
    n, c = len(comp), int(comp["CUADRA"].sum())
    resumen(documentos=n, cuadran=c, pct=(f"{100 * c / n:.2f}%" if n else "n/a"))
    console.print(f"\n[green]Guardado: {out}[/green]")
    console.print(
        "[dim]Nota: solo valida la poliza 'real' (sin CUENTAS ORDEN). Volumen bajo -- "
        "confirmar si vale la pena profundizar la poliza memo y el lado Abono.[/dim]"
    )


if __name__ == "__main__":
    main()