phase-5: declarations, from generated numbers to a PDF you file yourself

Formulario 120 and 515 assembly, the review screen, approval, the PDF, and the
guided checklist that walks the user through presenting it in Marangatu.

Approval is a promise about what the user actually saw: the numbers are
recomputed on the way in, and if the documents moved since the declaration was
generated the answer is a 409 with the fresh figures stored as a draft, not a
silent approval of numbers nobody read. An approved declaration is never
regenerated or invalidated.

Editing, confirming, rejecting or reclassifying a document flips any ready
declaration covering its period back to draft, which is what the dashboard
reads to stop offering it for review.

The PDF is rendered once and cached against the declaration, so two downloads
are byte for byte the same document; every write that changes the numbers
clears the cache. Approval pre-renders it, and the route renders on demand, so
a failed job costs a wait rather than a missing file. It stays Spanish in both
locales, like the form it mirrors, and carries a BORRADOR watermark until it is
approved.

The seeded declarations are computed through the same code path the product
uses, so their figures agree with the documents behind them.

Three defects the screenshots caught: the floating scan button swallowed the
tap meant for Aprobar on a phone, the summary breakdown reused labels meant for
other screens, and five tab labels collided at 390px.

Tests that can avoid depending on a fresh seed now do; the ones that cannot say
so in the assertion rather than timing out. db:reset warns that the API has to
be stopped first, having learned that the hard way.

293 vitest tests, 59 Playwright tests, rules coverage still 100%, typecheck and
lint clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Michilis
2026-09-04 05:50:29 +00:00
co-authored by Claude Opus 5
parent 5dc284c5f2
commit 6620650e9e
33 changed files with 2522 additions and 23 deletions
+84 -1
View File
@@ -441,7 +441,14 @@ The suite also erodes the seed it runs against: after a run, documents that star
bandeja are confirmed and insights are dismissed, so the next run is not the same run.
`pnpm db:reset` rebuilds the local database from the migrations and the seed, and the
README says to use it before an e2e run. It refuses to touch anything but a local SQLite
file, because it is destructive by definition.
file, because it is destructive by definition, and it warns that the API has to be stopped
first: deleting a file the running process holds open leaves it writing to an inode nothing
else can see, which looks like half the suite breaking at once.
Where a test can avoid depending on a fresh seed, it does. Golden path 4 generates its own
declaration for a month that has documents and no declaration yet, because approving is a
one way door. The ones that genuinely cannot, like scanning into an empty bandeja, say so
in the assertion message rather than timing out on a locator.
The dashboard's insight test self-skips once every insight for that account has been
dismissed, since dismissals persist by design; the persistence guarantee itself is asserted
against the API from a fresh database.
@@ -453,3 +460,79 @@ against the API from a fresh database.
obtains one is not in v1 scope.
- **Declarations.** `declaration_ready` is in the next-action priority and the sweep reads
from it, but nothing sets a declaration to `ready` until phase 5.
---
## Phase 5
### A generated declaration is `ready`, and approval is a promise about what was seen
Generating assembles the numbers and marks them `ready`: settled, waiting for a person to
agree. Approving recomputes them first, and if the documents moved in between it returns a
409, stores the new figures as a `draft`, and asks the user to look again. Approving
figures nobody saw is the one outcome that must be impossible, and it is the reason the
recompute happens on the way in rather than at render time.
An approved declaration is never regenerated or invalidated. Once a person has put their
name to a set of numbers, rewriting them behind their back is worse than any staleness.
### A document that moves invalidates the declaration that counted it
CONTRACTS.md 5.4. Confirming, rejecting, editing or reclassifying a document flips any
`ready` declaration covering its period back to `draft`, which is what the dashboard reads
to stop offering it for review. The month decides for a 120 and the year for a 515.
### The PDF is cached against the declaration, not regenerated per download
Two downloads of the same declaration are byte for byte the same document, which matters
when someone is transcribing from a printout. Every write that changes the numbers clears
`pdf_file_id`, which is what makes the cache safe. Approval enqueues a render so the
download on the success screen is instant; the route renders on demand too, so a failed
job costs a wait rather than a missing document.
### The form preview and the PDF stay Spanish in both locales
FLOWS.md section 1 and the COPY.md policy. The casilla labels mirror the DNIT form, and a
form whose labels have been translated is harder to transcribe from, not easier. The
caption around the preview says so in the reader's language. Two strings are hoisted out
of JSX with the reason written down, because the inline copy lint rule is right to flag
them and the answer is a comment, not an exception.
### The platform never files anything
Flow D3 is a checklist, not a submission: numbered steps, every casilla one tap from the
clipboard, and "Ya la presente" as the user telling us rather than us finding out. Marking
filed is only possible after approval, because the checklist is what follows approval.
### The seeded declarations are computed, not written down
CONTRACTS.md section 4 asks for a ready F120 for the previous month and an approved one two
months back. Both are assembled through the same code path the product uses, so their
figures agree with the documents behind them. A fixture whose numbers disagreed with its
own documents would be worse than no fixture.
### The declarations tab joined the shell
Five tabs now. Vencimientos stays reachable from the dashboard's deadline card and its
overdue action, so nothing lost a route.
### A test that had to grow
The next-action priority test now walks two rungs of the ladder rather than one: with the
seeded `ready` declaration in place, `declaration_ready` correctly outranks `bandeja`, and
the test approves it to check the fall through. The 5.4 test clears the outstanding
periods first, because `overdue` outranks everything and Maria has never filed a 515.
### Three defects the screenshots caught
**The scan button swallowed the tap meant for "Aprobar".** The floating button and the
sticky approve footer overlap on a 390px screen, and the desktop e2e never saw it because
the viewport is wider. The button now appears on the screens you arrive at, not on a detail
screen that has its own primary action, and a test asserts it.
**The summary breakdown was mislabelled.** It called the debito "A pagar" and the saldo
anterior "Casilla", because it reused keys meant for other screens. Three labels of its own
now.
**Five tab labels collided at 390px.** Smaller, truncating labels rather than dropping a
tab, since every one of them is a place the user needs to reach.
### Deferred, deliberately
- **Motion.** The confetti on approval and the canvas moment on the filed screen are
FLOWS.md D3; the motion pass and the canvas touches are phase 7. The screens are built
with the right structure and states.
- **Payment reminders after filing.** FLOWS.md D3 mentions scheduling one on "Ya lo
presente". The deadline sweep already covers T-2 and T-0 for the period; a separate
payment date is not in RULES.md and was not invented.