fix/rpu-12-digit
needs attentionviewing older commitd8cbd91 · incrementalPR #246reviewed 2026-07-05 21:25 UTC0H · 0M · 3L · 2I- Purpose
- Fix the 'Nuevo RPU' drawer accepting an 11-digit RPU as 'Disponible', leading to a server-side validation failure with an opaque blank-description toast and a frozen 'Creando…' submit lock
- Goal
- Surface clear, actionable errors for incomplete/invalid RPU inputs in the contract creation flow
- Sub-goals
- SG-1: Gate lookup + create form on isValidRpu (exactly 12 digits), eliminating the 11-digit early-open bug
- SG-2: Add InlineContractForm client-side RPU guard as defense-in-depth for the PDF-extraction path
- SG-3: Unwrap ts-rest framework 400 ZodError into a human-readable toast message via extractServerErrorMessage()
- SG-4: Release the submit lock on the error path so the form does not freeze on 'Creando…'
- SG-5 (this commit): Derive digits via normalizeRpuInput() in useMemo — defense-in-depth so the lookup gate can never see >12 digits regardless of how value was set
- What
- Single useMemo change in AddContractDrawer: digits derivation switched from value.replace(/\D/g,'') to normalizeRpuInput(value), which adds .slice(0, RPU_LENGTH) capping. Adds a 4-line comment explaining the rationale (though the comment's autofill framing is slightly inaccurate — see findings).
- Why
- Defense-in-depth: all current setValue() call sites already normalize, but the useMemo now enforces the cap at the derivation site so any future programmatic setValue() call that forgets to normalize cannot hang the drawer on 'Verificando…'.
- Areas
- apps/platform/src/app/[locale]/(dashboard)/bills/_components+299−12apps/platform/src/app/[locale]/(dashboard)/bills/_lib+174−0apps/platform/src/messages+6−2
- Blast
- 8 files, +479/-14 across bills components, lib, and messages. All changes are client-side UI only — no API surface, no DB schema, no server logic changed.
Findings · 5
correctness3
Block comment misattributes the defense-in-depth rationale to autofill/password-manager DOM writes
apps/platform/src/app/[locale]/(dashboard)/bills/_components/AddContractDrawer.tsx:147
The comment claims the cap guards against 'browser autofill, a password-manager/extension writing .value directly'. For a React controlled input, direct DOM .value writes do not update React state, and autofill fires a synthetic change event that goes through onChange (which already normalizes). The real rationale is defense-in-depth against internal programmatic setValue() calls that omit normalizeRpuInput — with the PDF-extraction path at line 365 being the canonical example. The comment should say: 'Defense-in-depth: value can be set programmatically (e.g. PDF extraction at line 365); the useMemo ensures the cap is enforced regardless of write path, so the lookup gate can never see >12 digits and hang on Verificando.'
PR description misquotes the prior regex as /D/g (missing backslash) — documentation artifact only
apps/platform/src/app/[locale]/(dashboard)/bills/_components/AddContractDrawer.tsx:151
The PR description's DIFF section shows the old line as value.replace(/D/g, '') (no backslash). Git history confirms the actual pre-commit code was value.replace(/\D/g, '') (correct non-digit strip). The code change itself is correct; this is a rendering/escaping artifact in the PR description only.
.slice(0, RPU_LENGTH) cap in useMemo is forward defense, not a fix for a current live bug
apps/platform/src/app/[locale]/(dashboard)/bills/_components/AddContractDrawer.tsx:151
All current setValue() call sites already pass through normalizeRpuInput (onChange line 426, PDF extraction line 365, reset line 136 sets ''). Under the current code, React state value can never hold more than 12 digits. The cap is legitimate defense-in-depth against a future setValue() call that omits normalization — no action required; noting for clarity.
security1
extractServerErrorMessage passes arbitrary server-controlled strings verbatim to toast UI
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/rpu-errors.ts:39
The function extracts body.data.message, a Zod-issue message, or body.message from the server response and passes it as the toast description prop without any length cap or allowlist. React renders it as a text node so XSS is not exploitable under the current rendering model, but a compromised or misconfigured API endpoint could surface arbitrary strings in the UI. Defense-in-depth would be a max-length cap (e.g. 200 chars) or a map of known error tags to localized strings. Pre-existing issue, not introduced by this commit.
conventions1
No component-level regression test for >12-digit value being capped by useMemo
apps/platform/src/app/[locale]/(dashboard)/bills/_components/__tests__/AddContractDrawer.rpuGate.test.tsx:69
The pure-function cap is covered in rpu-errors.test.ts, but no component-level test exercises the specific regression this commit closes: a value state holding >12 digits (e.g. via a future un-normalized setValue call) should still produce digits.length === 12 and not leave the drawer stuck on 'Verificando…'. A single fireEvent.change with a 14+ digit string asserting the create form remains available would close this gap and make the intent auditable.
History · 6 commits
- 427115eneeds attentionincremental0H · 1M · 3L2026-07-05 21:43
- c6ce7d8needs attentionincremental0H · 3M · 6L2026-07-05 21:35
- d8cbd91needs attentionincremental0H · 0M · 3L2026-07-05 21:25current
- 20aa256safeincremental0H · 0M · 2L2026-07-05 21:14
- 77a5d70needs attentionincremental0H · 2M · 9L2026-07-05 21:09
- 586531aneeds attentionfull0H · 5M · 10L2026-07-05 20:51