Inspiration

In Mexico, recovering a VAT credit means assembling an eight-part file for the tax authority (SAT): reconciling every digital tax receipt (CFDI) against the bank statements, integrating creditable and collected VAT, and backing every figure with its supporting document. A professional firm spends weeks on a single month. The cost of that work is why many businesses simply never claim what they are owed.

What it does

TICIAN Cierre takes the CFDI for the period, reconciles them against the bank, assembles the complete SAT file, and — this is the part that is different — submits its own result to an AI auditor before delivering it.

The validator runs on Cloud Run against Vertex AI. It receives the reconciliation summary, judges it, and returns approved or rejected with a reason and a list of specific findings: receipts with no UUID, duplicated receipts, VAT that does not correspond to its taxable base, variances against the declared totals. Every run leaves its verdict in the log.

How we built it

Python and FastAPI for the service, Cloud Run to deploy it, Vertex AI for the judgement. The reconciliation engine runs in Docker on a VPS and authenticates to the validator with a dedicated service account whose permission is to invoke that one service and nothing else.

One design decision governs the rest: Python does the arithmetic, not the model. The service deduplicates by UUID and sums deterministically, then hands Gemini the resolved figures to judge against. Models are bad at adding and good at judging, so the adding is not delegated to them. The arithmetic block travels back inside the response, so anyone can audit that the model did not invent the total.

The validator is an auditor, not a gate: if it does not answer, the reconciliation finishes anyway and the reason is logged. A client does not lose their file because the auditor was down.

Challenges we ran into

The first was a data problem, not a code problem. When we connected the validator to the engine, the first real run flagged ten receipts whose VAT did not correspond to its taxable base. It turned out the test-data generator was drawing bases and VAT amounts from independent random draws: the aggregates balanced to the cent, but no individual receipt was valid under SAT rules. The validator found the defect on its first contact with the engine.

The second was worse and more useful. The validator reported a variance nobody expected: the engine was summing both appearances of a duplicated UUID, while the validator counted it once. Fiscally the validator was right — a CFDI is credited exactly once — and the engine could have silently inflated VAT after a re-download from the SAT or an overlapping date range. This one is history, and worth stating precisely because the repository shows the current state. It was fixed on 3 August 2026 (commit "motor: un CFDI repetido se acredita una sola vez"). Today the engine deduplicates at pipeline.py:72, before anything consumes the invoice lists, and only calls the validator at pipeline.py:154. Gemini can no longer see a duplicated UUID coming from the engine, because by the time the summary reaches it there are none left.

The third is the cleanest of the three, and it is the one that makes the case for having an outside reviewer at all.

The engine negates credit notes when it computes its declared total — under Mexican VAT law they reduce creditable VAT, so this is correct. But the validator client was sending those same credit notes to the auditor as positive amounts. The result was a reported variance of exactly twice the value of the notes. Measured on fiscal period 202409: six received credit notes worth 560.00 MXN of VAT produced a reported variance of 1,119.97. Fixed on 12 August 2026 (commit "validador: las notas de credito se le mandan negadas, como las declara el motor").

What makes this one interesting is that the engine was not wrong. It was perfectly consistent with itself on both sides of its own ledger — it subtracted the notes when declaring, and it would have subtracted them again anywhere it checked its own work. A system consistent with itself cannot detect this class of error from the inside, no matter how carefully it is tested against itself. It takes a reviewer standing outside, comparing two representations of the same month that the system never compares against each other.

That is not our insight. It is a documented idea we implemented: Bakhtin called it exotopy — the surplus of vision available only to whoever stands outside. We built the validator as a separate service, with its own arithmetic and its own view of the data, for reasons that were mostly architectural. The credit-note bug is the clearest evidence we have that the separation was the point.

Accomplishments that we're proud of

An accountant with 227,000 real CFDI turned up not a single duplicate, so the deduplication fix was a no-op over everything already delivered: the figures we had handed over were never inflated. We established that by measuring rather than assuming — the totals were frozen before the change and the run was required to reproduce them identically, to the cent.

What we learned

That an automated auditor is worth what it rejects, not what it approves. The day it found a real bug in the engine that feeds it, it stopped being a demo and became a tool.

And that the value of an outside reviewer is structural, not a matter of being smarter. The credit-note variance was invisible from inside a system that was consistent with itself, and obvious to one that was not part of it.

What's next

Extending the validator to all eight parts of the file, and opening it to the accounting firms that today do by hand what the engine already does on its own.

Built With

  • artifact-registry
  • cloud-build
  • docker
  • fastapi
  • gemini
  • google-cloud-run
  • pytest
  • python
  • satcfdi
  • secret-manager
  • streamlit
  • vertex-ai
Share this project:

Updates

Submission history