fix/rpu-12-digit
needs attentionviewing older commit586531a · fullPR #246reviewed 2026-07-05 20:51 UTC0H · 5M · 10L · 4I- Purpose
- Fix broken UX when creating a CFE contract with an incomplete RPU — 11-digit RPU was shown as 'Disponible' and triggered a server-side ZodError with blank toast + frozen submit.
- Goal
- Ensure only valid 12-digit RPUs reach the contract create endpoint; surface real server error messages to users; fix submit lock freeze on error path.
- Sub-goals
- SG-1: Gate lookup and create form on isValidRpu (exactly 12 digits)
- SG-2: Extract real error messages from all ts-rest/JSend response shapes
- SG-3: Release submit lock on non-201 response (fix frozen 'Creando...')
- SG-4: Send stripped rpuDigits (not raw rpu) to API
- SG-5: Add i18n strings rpuInvalid + createFailed
- SG-6: Cover all changes with unit + jsdom component tests
- What
- New _lib/rpu-errors.ts with isValidRpu + extractServerErrorMessage. AddContractDrawer: gate on isValidRpu (was >= 11). InlineContractForm: RPU guard, submit lock fix, better error messages, send rpuDigits. Three new test files.
- Why
- 11-digit RPU slipped through client validation, causing opaque server rejection with blank toast and frozen UI. Server-side validation was already strict (z.string().length(12)) but the client gate was off-by-one.
- Areas
- apps/platform/src/app/[locale]/(dashboard)/bills/_lib+134−0apps/platform/src/app/[locale]/(dashboard)/bills/_components+275−7apps/platform/src/messages+6−2
- Blast
- 8 files, +415/-9 lines. Frontend only — bills route. No domain/API/DB changes.
Findings · 9
correctness2
13-digit RPU causes permanent loading state
apps/platform/src/app/[locale]/(dashboard)/bills/_components/AddContractDrawer.tsx:176
digits.length < 12 guard skips 'too-short' for 13+ digits, but isValidRpu also rejects them — UI freezes in loading indefinitely. Fix: `if (!isValidRpu(digits) && digits.length > 0)` → too-short.
data.error no longer extracted — regression in error messages
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/rpu-errors.ts:37
JSend fail shape uses data.error (tag), not data.message. Old code read data.error; new extractor probes only data.message → falls back to generic t('createFailed') for all existing fail responses.
security1
bodyErrors ZodError message surfaced verbatim to user
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/rpu-errors.ts:44
Benign for RPU field; generic extractor could leak regex patterns from stricter schemas if reused on other endpoints.
conventions2
rpu-errors.ts name undersells validation content
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/rpu-errors.ts:1
Contains isValidRpu (validation) + extractServerErrorMessage (error). rpu-utils.ts would better describe both exports.
JSDoc block explains WHAT, not WHY
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/rpu-errors.ts:1
8-line header narrates self-evident content. One sentence for the non-obvious WHY (avoid opaque ZodError) is enough.
tests3
setTimeout(r, 400) debounce wait is timing-dependent
apps/platform/src/app/[locale]/(dashboard)/bills/_components/__tests__/AddContractDrawer.rpuGate.test.tsx:81
Fragile under CI process starvation. Use vi.useFakeTimers() + vi.advanceTimersByTime(400) for deterministic control.
Missing regression test: data.error-only JSend fail should return undefined
apps/platform/src/app/[locale]/(dashboard)/bills/_lib/__tests__/rpu-errors.test.ts:60
Current test has both data.error + data.message. A case with only data.error asserts the exact regression this PR fixed.
document.querySelector('form') breaks with multiple forms in jsdom
apps/platform/src/app/[locale]/(dashboard)/bills/_components/__tests__/InlineContractForm.rpuError.test.tsx:127
Picks first form element — fragile if mocks render additional forms. Prefer aria-role or data-testid.
improvement1
Hardcoded Spanish strings in CFE job-failure toast bypass i18n
apps/platform/src/app/[locale]/(dashboard)/bills/_components/InlineContractForm.tsx:279
Pre-existing: 'RPU creado' and the warning copy should use t(...) keys per ui-patterns.md.
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:25
- 20aa256safeincremental0H · 0M · 2L2026-07-05 21:14
- 77a5d70needs attentionincremental0H · 2M · 9L2026-07-05 21:09
- 586531aneeds attentionfull0H · 5M · 10L2026-07-05 20:51current