Intelligent Payment Verification
Universidad Nacional de Colombia · 2026 - At present
A contractor at the National University of Colombia has to prove, every month, that their social security contributions are in order before they can be paid. This app takes that whole procedure — reading the contract, the ARL certificate and the PILA form, checking the figures against Colombian regulation, and filling the institutional templates — and turns it into a four-step wizard for the Research and Extension Directorate of the Manizales campus.
Architecture and Tech Stack
Core Architecture
- Framework: Next.js 16 (App Router), TypeScript, deployed on Vercel
- AI extraction: Vercel AI SDK over a Mistral + OpenAI model chain with automatic fallback (
src/lib/ai/client.ts) - Document reading:
unpdf/mupdffor born-digital PDFs, Mistral OCR for scans and screenshots - Business rules: a pure calculation engine for form 069, parameterized per fiscal year (
src/lib/formato069/) - Validation: Zod schemas per document type (contract, ARL, payment sheet, activity report)
- PDF generation:
@pdfmefilling the official templates, merged with their annexes - State: Zustand store across the wizard steps
- UI: shadcn/ui + Tailwind CSS 4
- Analytics: PostHog behind a typed event whitelist
Layered Architecture
The browser never talks to a model directly: every extraction goes through a route handler that streams NDJSON back, so the wizard can render fields as they are recognized instead of waiting for the whole document.
Request Flow
Key Features
Features at a Glance
Multi-document upload
Contract, ARL certificate, paid PILA form and — when the contract requires it — the activity report, all dropped at once. Each file is profiled and validated before extraction (src/lib/pdf/document-profiles.ts, document-validator.ts), so a wrong document is caught at the door rather than halfway through the flow.
Extraction with per-field confidence
The model returns every field tagged with how confident it is. Step 2 renders those tags, so review effort concentrates on what is actually uncertain instead of re-reading everything. The user edits inline and the corrected values feed the rest of the flow.
Regulation validation
Contributions, ARL validity and coverage, PILA deadlines, activity report percentages and the tax certification are checked by independent validators orchestrated in src/lib/validations/index.ts. Each finding cites the specific rule it comes from — and only rules whose text was verified directly.
Form 069 calculation engine
A pure, side-effect-free engine computes the contribution base and deductions. Fiscal parameters live in their own files (params/2026.ts, params/2027.ts), so rolling the app into a new year is a parameter change, not a rewrite.
Signature-ready output
Official formats U.FT.12.010.053 and U.FT.12.010.069 are filled and merged with their annexes into a single downloadable PDF.
Technical Highlights
Nothing is cited that has not been read
src/lib/constants/fuentes.ts only holds regulation whose text was verified first-hand, and the rule extends to quoted sentences inside validation messages. A quote attributed to Circular GNFA 019/2018 turned out not to exist and had to be reverted; the same happened with a sentence from form 053. When a document cannot be opened, the app says so instead of reconstructing it.
Analytics that cannot leak a person
src/lib/analytics.ts accepts only enums, counts, booleans and field keys — never values, names, ID numbers, amounts or document content. The EventMap type is the whitelist and TypeScript enforces it at compile time. Rule identifiers are never renamed either: they identify the situation, not the wording, so the historical series in PostHog stays comparable.
The official spreadsheet wins
When normative research and the official .xlsx contradict each other, the spreadsheet wins — the app has to reproduce the format the university actually receives, not the one the regulation implies.
Never silently overwrite a declaration
Form 053 is a signed statement. An automatic coercion of the payment type to "Único" was quietly changing what the contractor had marked; it was removed and replaced with a warning. When the app detects an inconsistency it flags it and lets the person decide.
Dates that often are not there
About a quarter of contracts express their term only in days ("doscientos sesenta y ocho (268) días calendario"), so startDate/endDate arrive null and only durationDays exists. The app infers dates from the ARL certificate only when the match is unambiguous; otherwise the user types the start date and the end is derived with the same shared utility, never overwriting an end date already present.
A corpus of real cases as the test harness
docs/Casos/ holds real PDFs paired with expected.json — operators, ARL insurers, contract shapes, and the awkward ones: scans, screenshots, rotated pages, expired ARL, incomplete contributions, several payment sheets. Obvious heuristics have failed against it more than once, which is exactly why every rule or extractor change is replayed over the corpus before it ships. "It works on my document" is not evidence.
Project Structure
src/ ├── app/ │ ├── api/extract/ # AI extraction, streams NDJSON │ ├── api/extract-text/ # text layer + OCR fallback │ ├── api/generate-pdf/ # fills and merges the official formats │ └── verify/ # the four-step wizard ├── components/ │ ├── upload/ # dropzone + manual entry form │ └── wizard/ # step-1 … step-4, extraction editors ├── lib/ │ ├── ai/client.ts # model chain + fallback │ ├── extraction/ # anchors, preprocessing, name inference │ ├── formato069/ # pure calc engine + per-year parameters │ ├── pdf/ # profiles, validator, OCR, form filling │ ├── validations/ # contributions, dates, deadlines, report │ ├── schemas/ # Zod schema per document type │ ├── constants/fuentes.ts # verified regulation catalog │ └── analytics.ts # typed event whitelist docs/ ├── LOGICA-NEGOCIO.md # single source of truth for the rules └── Casos/ # real-PDF corpus with expected.json
Impact and Scalability
- Replaces a fully manual procedure: reading each document by hand, cross-checking figures and typing the templates.
- Review time concentrates on low-confidence fields instead of every field.
- Rolling to a new fiscal year is one parameter file.
- The case corpus makes every rule change verifiable against real documents before it ships.
- Usage is measured in PostHog without storing a single personal value.
Notes
Built with Next.js 16, the Vercel AI SDK over Mistral and OpenAI, @pdfme, Zod and shadcn/ui. The repository is private — it belongs to the Centro de Prototipado at the National University of Colombia.