phase-2: identity, from the landing hook to the profile screen
Flows A1 to A6 and E3 end to end. A visitor types a RUC on the landing page, sees their real filing dates, registers, verifies a six digit code, grants consent, completes a three step setup and lands on the first run screen, with the profile, consent and audit rows to show for it. API: public RUC lookup behind a token bucket (10/min/IP), the full /me surface (profile, dependents, consents, notification prefs, data export, account deletion), an append-only audit module that exports an insert and nothing else, and a PII module that is the only thing allowed near those tables. Deletion and consent revocation both freeze the account and drop every session, reusing better-auth's ban flag rather than adding a second notion of disabled. Nothing is destroyed yet: the purge is a job for phase 4. deadlineDigit is always derived server side, never accepted from the client. Web: landing with the RUC hook, registration, OTP verification, consent, the setup wizard, the profile screen with "Tus datos", and legal pages that ship as marked placeholders per COPY.md section 13. Money, Skeleton, Switch and EmptyState components added. The seed is now complete for identity: Maria at 4123456-1, filing digit 6 and day 19, with a dependant, consents and prefs; Carlos as an IVA-only company. Two real defects found by building the screens and fixed with tests: the OTP boxes dropped a digit because the handler fired effects inside a setState updater that React 19 invokes twice, and the switch knob rendered outside its track because translate-x-5.5 does not resolve. 232 vitest tests, 26 Playwright tests across mobile and desktop, coverage still 100% on the rules, typecheck and lint clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
80b10c958e
commit
0d7651b17c
@@ -209,3 +209,67 @@ the Playwright default base URL uses localhost.
|
||||
|
||||
This deviates from the literal example values in SPEC.md section 15, which use port 3000.
|
||||
The compose stack still publishes 3000 and is unaffected.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Phase 2
|
||||
|
||||
### The seed is now complete for identity
|
||||
Maria is an individual whose RUC is her CI plus a check digit: base `4123456`, DV computed
|
||||
by `computeRucDv`, so her filing digit is 6 and her day is the 19th, which is the example
|
||||
FLOWS.md uses throughout. Carlos is a company registered for IVA only, so the IVA-only view
|
||||
has real data behind it. Both have consents, notification preferences and the audit rows
|
||||
CONTRACTS.md section 4 asks for. Documents arrive in phase 3.
|
||||
|
||||
### `GET /lookup/ruc/:number` reads a bare number as a CI
|
||||
A number with a hyphen and a check digit is a RUC and the digit is verified. A bare number
|
||||
is a CI, which is what individuals type when their RUC is their CI plus a digit; the filing
|
||||
day is identical either way, which is the only thing the landing card shows. Neither form
|
||||
touches the database: the endpoint reveals only what the number itself already encodes.
|
||||
|
||||
An unparseable number is a 200 with `valid: false`, per CONTRACTS.md section 3, so the
|
||||
landing corrects the user inline rather than dead ending.
|
||||
|
||||
### Freezing an account reuses better-auth's ban flag
|
||||
Both a deletion and a data processing consent revocation set `banned` with a distinct
|
||||
`banReason` and drop every session. That reuses the check better-auth's sign in path
|
||||
already performs, rather than introducing a second, parallel notion of a disabled account
|
||||
that some code path would eventually forget to check. Nothing is destroyed yet: the purge
|
||||
is a job, which lands with the jobs module in phase 4 (`softDeleteAccount` carries the TODO).
|
||||
|
||||
### `deadlineDigit` is derived, never accepted from the client
|
||||
`PUT /me/profile` takes `ProfileInput`, which is `ProfileDto` minus `deadlineDigit`. The
|
||||
digit decides real filing dates, so it is computed from the identity document server side.
|
||||
The document itself is read only on the profile screen for the same reason: changing it is
|
||||
a support conversation, not a text field.
|
||||
|
||||
### Deactivating a dependant rather than deleting it
|
||||
A confirmed document may already be classified against a dependant, and that classification
|
||||
has to keep making sense. `DELETE /me/dependents/:id` sets `active = 0`; the list filters on
|
||||
it. Scoped by `user_id`, so one user cannot touch another's row (404, tested).
|
||||
|
||||
### Two real defects found by building the screens
|
||||
**The OTP boxes dropped a digit.** The handler spread the pasted code inside a `setState`
|
||||
updater and fired the focus move and the verify request from in there. React 19 invokes
|
||||
updaters twice in development to check they are pure, so the effects ran twice and a digit
|
||||
was lost. The spread is now computed outside the updater. Caught by golden path 1.
|
||||
|
||||
**The switch knob rendered outside its track.** `translate-x-5.5` does not resolve, so the
|
||||
knob sat 44px along a 44px track, entirely outside it. Now `left-0.5` plus `translate-x-5`,
|
||||
both on the spacing scale. `e2e/profile.spec.ts` asserts the knob stays within the track
|
||||
bounds in both states, because this is invisible to every other kind of test.
|
||||
|
||||
### Deferred, deliberately
|
||||
- **Motion.** FLOWS.md A2 and A6 call for GSAP (the card flip, the deadline stagger, the
|
||||
first savings counter) and A1 for a canvas hero effect. The phase plan puts the motion
|
||||
pass and the canvas touches in phase 7, so the screens here are built with the right
|
||||
structure and states and no animation.
|
||||
- **Push and Telegram.** Hidden until configured (FLOWS.md section 9). Setup step 3 offers
|
||||
only email today; `POST /push/subscribe` needs the service worker and arrives in phase 7.
|
||||
- **`/comprobantes`.** The first run buttons point at it. Ingestion is phase 3.
|
||||
|
||||
### The legal pages ship as marked placeholders
|
||||
`/legal/privacidad` and `/legal/terminos` render "Documento en preparacion" and say a
|
||||
document is being drafted, per COPY.md section 13. No legal text was generated.
|
||||
**TODO: human written before launch.**
|
||||
|
||||
Reference in New Issue
Block a user