The Finance module turns terminal rows in validations into monthly aggregates useful for accounting reconciliation: KPIs, account statements (PDF/XLSX/CSV/HTML), four detail reports, and a ZIP archive of the period's CEP XMLs. Everything is computed on-demand from the user's validations table — there is no pre-aggregator job.

What counts as "verified"

A validation is "verified" only when validations.status = 'valid' AND cep_xml was persisted (Banxico returned the CEP XML). Every other terminal verdict — not_found, cep_unavailable, invalid, failed, error — feeds the "unverified" aggregate and adds to unverified_volume but NOT to verified_volume. The definition matches CEP semantics.

That split is what keeps the KPIs honest when a retry succeeds: the row gets re-marked valid and verified_volume grows while unverified_volume drops by the same amount.

The summary KPI bundle

GET /v1/finance/summary?month=YYYY-MM returns, for the requested period, the bundle that renders the /finanzas panel:

  • counts — total + per-verdict breakdown (valid, not_found, cep_unavailable, invalid, failed, error).
  • amountstotal_volume, verified_volume, unverified_volume, avg_ticket, median_ticket, max_ticket, min_ticket.
  • success_rate — share of terminal rows that landed on valid.
  • avg_processing_ms — average latency from insert to terminal close.
  • top_counterparties / top_banks_receptor / top_banks_emisor — top 5 each, with count + valid + verified_volume.
  • verdict_distribution — counts per verdict shaped for a chart.
  • comparison_prev_month — total + verified volume from the previous month and the percent deltas.

Statement and reports

GET /v1/finance/statement?month=YYYY-MM&format=... emits the monthly statement in four formats:

  • format=pdf (default) — rendered via Dompdf with a repeating header, "Página X de Y" footer, and embedded logo.
  • format=xlsx — multi-sheet workbook (Resumen / Detalle diario / Top contrapartes / Top bancos / KPIs / Transactions).
  • format=csv — sectioned [HEADER] / [KPIS] / [TRANSACTIONS] / [TOTALS] with a UTF-8 BOM.
  • format=html — the same template the PDF uses, useful for browser-print.

The four detail reports (monthly / counterparties / by-bank / accounting) accept format=csv|preview and, on accounting, decimal=comma|dot for SAT/accounting tooling. The PDF caps at 1000 rows to avoid Dompdf OOM.

The CEP archive

GET /v1/finance/ceps?from=YYYY-MM-DD&to=YYYY-MM-DD returns a ZIP with every cep_xml (and cep_pdf_path when present) in range, plus two parallel manifests — manifest.csv and manifest.xlsx — with columns validation_id, fecha, clave_rastreo, archivo. Only rows with cep_xml IS NOT NULL OR cep_pdf_path IS NOT NULL are included, so the archive is exactly the verified subset of the period. An empty range responds 404 no_ceps.

Scope: own vs cross-user

By default the seven endpoints operate against the API key's user_id. An admin with the finance:generate_all permission may pass ?user_id=<UUID> to target any user; without the permission user_id returns 403 forbidden or is silently ignored. There is no public endpoint to list users — the admin operator obtains the UUID through a separate channel.

To download a single XML without going through the bulk archive, GET /v1/validations/{id}/cep returns the XML directly when the row is valid.