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).amounts—total_volume,verified_volume,unverified_volume,avg_ticket,median_ticket,max_ticket,min_ticket.success_rate— share of terminal rows that landed onvalid.avg_processing_ms— average latency from insert to terminal close.top_counterparties/top_banks_receptor/top_banks_emisor— top 5 each, withcount+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.