Persiste proposal, specs delta (auth, api-client, access-requests, platform-shell, testing), design con 8 ADRs, tasks (27 items en 10 fases), verify-report (28 tests, 0 críticos, 0 warnings tras el cierre) y archive-report final. Cierra el ciclo SDD del cleanup de deuda técnica del console.
11 KiB
11 KiB
Tasks: console-tech-debt-cleanup
Pre-Apply Audit Findings
R1 — Token Bracket-String Audit (mandatory pre-P0 check)
Grep pattern: \.token\b|['"]token['"]|state\[['"]token['"]\]
Hits in services/frontend-console/src:
| File | Line | Finding |
|---|---|---|
src/app/router/ProtectedRoute.tsx |
9 | useAuthStore((state) => state.token) — AuthState field read |
src/features/auth/pages/LoginPage.tsx |
16 | useAuthStore((state) => state.token) — AuthState field read |
src/features/auth/pages/LoginPage.tsx |
24,29 | token variable from store used in guard conditions |
src/features/auth/pages/LoginPage.tsx |
45 | response.access_token ?? response.token — wire fallback (LoginResponse) |
src/lib/api/client.ts |
66 | data.access_token ?? data.token — RefreshResponse wire read (client-local type, not AuthState) |
All hits are in the two files already targeted by ADR-001/ADR-002. No bracket-string (state['token']) reads found. TypeScript rename is sufficient — no hidden runtime reads.
A2 — apiClient 204 Callsite Audit
All apiClient<T> callsites return typed body (AccessRequest, AccessRequest[], LoginResponse). Zero callsites hit a 204 endpoint today. The null as T cast at client.ts:96,111 is dead code in production. Split to apiClient<T> + apiClientVoid carries zero migration risk.
Review Workload Forecast
| Field | Value |
|---|---|
| Estimated changed lines | 300-450 |
| 400-line budget risk | High |
| Chained PRs recommended | Yes |
| Suggested split | PR 1 (P0: foundation + auth) → PR 2 (P1: UX + DRY) |
| Delivery strategy | ask-on-risk |
Decision needed before apply: Yes Chained PRs recommended: Yes 400-line budget risk: High
Suggested Work Units
| Unit | Goal | Likely PR | Notes |
|---|---|---|---|
| PR 1 | Vitest scaffold + auth correctness (ADR-007, ADR-001, ADR-002, ADR-003, ADR-004) | PR 1 | All P0 items; self-contained; green CI required |
| PR 2 | UX robustness + DRY (ADR-006, ADR-005, ADR-008) | PR 2 | Depends on PR 1 merged; can be reviewed independently |
Phase 0 — Pre-Apply Checks (sequential, one-time)
- 0.1 Confirm R1 audit above (already done — document in PR 1 description: only
ProtectedRoute.tsxandLoginPage.tsxreadstate.token; no bracket-string reads). - 0.2 Confirm A2 audit above (already done — document in PR 1 description: zero 204 callsites; safe to split
apiClient).
Phase 1 — Vitest Scaffolding (ADR-007) [PR 1 — sequential]
- 1.1 Install devDeps in
services/frontend-console/package.json: addvitest@^3.2.4,jsdom@^26.1.0,@testing-library/react@^16,@testing-library/dom@^10,@testing-library/user-event@^14underdevDependencies; add"test": "vitest run"toscripts. [W1 CLOSED: removed --passWithNoTests flag] - 1.2 Add
testblock toservices/frontend-console/vite.config.ts:{ environment: 'jsdom', setupFiles: './src/test/setup.ts', restoreMocks: true, clearMocks: true }— mirror admin pattern verbatim. - 1.3 Create
services/frontend-console/src/test/setup.ts— verbatim copy ofservices/frontend-admin/src/test/setup.ts(localStorage/sessionStorage mock +IS_REACT_ACT_ENVIRONMENT). - 1.4 Run
pnpm test— must exit 0 with no test files (empty run validates config). Blocks all subsequent RED tasks.
Phase 2 — Auth Store Tests + Refactor (ADR-001) [PR 1 — sequential after 1.4]
- 2.1 RED — Create
src/features/auth/stores/auth-store.test.ts. Cases: (a)setSessionwritesaccessTokenanduser,tokenfield isundefinedor absent; (b)logoutsetsaccessTokenandusertonull. Runpnpm test— expect failures on (a) becausetoken: accessTokenis still set. - 2.2 GREEN — Edit
src/features/auth/stores/auth-store.ts: removetokenfromAuthStateinterface; removetoken: nullfrom initial state; removetoken: accessTokenfromsetSession; removetoken: nullfromlogout. Runpnpm test— auth-store tests pass. - 2.3 GREEN (ripple) — Edit
src/app/router/ProtectedRoute.tsxline 9: replacestate.tokenwithstate.accessToken; updatehasCheckedRefreshinit anduseEffectdep to useaccessToken. Editsrc/features/auth/pages/LoginPage.tsxlines 16,24,29: replacetokenselector withaccessToken. Runpnpm testandpnpm build— must pass (TypeScript will catch any missed reads).
Phase 3 — LoginResponse Alignment (ADR-002) [PR 1 — sequential after 2.3]
- 3.1 RED — Add test to
src/features/auth/stores/auth-store.test.ts(or a separatesrc/features/auth/api/auth-api.test.ts): assertLoginResponsetype hasaccess_token: stringandtoken: string; assert norefresh_tokenfield. This is a compile-time type test — useexpectTypeOffrom vitest. Runpnpm test— fails because currentLoginResponsehasaccess_token?: string(optional) andrefresh_token?: string. - 3.2 GREEN — Edit
src/features/auth/api/auth-api.ts: setaccess_token: string(required),token: string(required, legacy compat), removerefresh_token. Editsrc/features/auth/pages/LoginPage.tsxline 45: remove?? response.tokenfallback — readresponse.access_tokendirectly. Runpnpm test— passes.
Phase 4 — SessionCoordinator + apiClient Split (ADR-003) [PR 1 — sequential after 3.2]
- 4.1 RED — Create
src/lib/api/client.test.ts. Cases for SessionCoordinator: (a) two concurrentrefresh()calls →fetchspy called exactly once, both promises resolve with the same token value; (b)refresh()when server returns non-ok → returnsnull. Runpnpm test— (a) should already pass (deduplication logic exists); (b) may already pass. Document baseline. - 4.2 RED (apiClient) — Add cases to
src/lib/api/client.test.ts: (c)apiClient<T>on 200 resolves with body; (d)apiClient<T>called when endpoint returns 204 after retry → must NOT silently returnnull as T(assert it throws or thatapiClientVoidis the correct path — per spec,apiClient<T>on 204 is a dev-time guard). Runpnpm test— (d) fails because todaynull as Tis returned silently. - 4.3 GREEN — Edit
src/lib/api/client.ts: extract sharedexecuteWithRefreshlogic; rename existingapiClient<T>204 branch to thrownew Error('Use apiClientVoid for 204 endpoints'); addexport async function apiClientVoid(endpoint, options): Promise<void>that returnsvoidon 204 and throws on non-ok. Removenull as Tcasts at lines 96 and 111. Runpnpm test— all passes. - 4.4 Confirm no callers need migration (A2 audit showed zero 204 callsites) — run
pnpm buildfor TypeScript validation.
Phase 5 — ProtectedRoute Hydration (ADR-004) [PR 1 — sequential after 4.4]
- 5.1 RED — Add cases to
src/lib/api/client.test.tsor a newsrc/app/router/ProtectedRoute.test.tsx: (a) cold visit withuser: null→ renders<Navigate to="/login"/>immediately, no "Restaurando" text; (b) warm visit withuserset andaccessTokennull → renders loading indicator then resolves after refresh. Runpnpm test— (a) fails because current code always shows "Restaurando" flash on cold visit. - 5.2 GREEN — Edit
src/app/router/ProtectedRoute.tsx: replacetoken-based heuristic withuseAuthStore.persist.hasHydrated()guard. Flow: wait forhasHydrated(); ifaccessToken→ render<Outlet/>; else ifuser(persisted) → attempt refresh + show indicator; else →<Navigate to="/login"/>immediately. UseuseAuthStore.persist.onFinishHydrationorrehydrateto trigger hydration detection. Runpnpm test— passes.
Phase 6 — AccessRequestsPage Error UI (ADR-006) [PR 2 — sequential, depends on PR 1 merged]
- 6.1 RED — Create
src/features/access-requests/pages/AccessRequestsPage.test.tsx. Cases: (a)requestsQueryin error state → renders error card with "Reintentar" button, NOT empty table; (b)allRequestsQueryin error state → stat cards show0, no crash. Runpnpm test— (a) fails because error branch is missing from current JSX. [W3 CLOSED: test 6.1c added — Reintentar click triggers refetch] - 6.2 GREEN — Edit
src/features/access-requests/pages/AccessRequestsPage.tsx: after therequestsQuery.isLoadingcheck, addrequestsQuery.isErrorbranch rendering an inline error card with retry button (requestsQuery.refetch()); update stat card values to use?? 0guards forallRequestsQuery.isError. Runpnpm test— passes.
Phase 7 — AccessRequestsPage Mutation Tests (ADR-008 / spec) [PR 2 — sequential after 6.2]
- 7.1 RED — Add cases to
src/features/access-requests/pages/AccessRequestsPage.test.tsx: (a) approve mutation success →toast.successcalled +queryClient.invalidateQueriescalled with['platform-access-requests']; (b) reject mutation success → same; (c) review mutation success → same; (d) any mutation error →toast.error(err.message), noinvalidateQueries. Runpnpm test— (d) may pass (already wired), but (a-c) need verification. [W2 CLOSED: tests 7.2a/7.2b (reject success/error) + 7.3a/7.3b (review success/error) added — 28/28 pass] - 7.2 GREEN — Verify mutation
onSuccesshandlers inAccessRequestsPage.tsxmatch spec exactly (invalidateRequests()callsqueryClient.invalidateQueries({ queryKey: ['platform-access-requests'] })). Adjust if needed. Runpnpm test— passes.
Phase 8 — isActionable Consolidation (ADR-008) [PR 2 — sequential after 7.2]
- 8.1 RED — Add case to
AccessRequestsPage.test.tsx:RequestDetailApprove and Reject buttons are disabled whenrequest.status === 'approved'; enabled when'pending'or'in_review'. Runpnpm test— fails becauseRequestDetailat line 315-316 inlines['pending', 'in_review'].includes(request.status)instead of usingisActionable. - 8.2 GREEN — Edit
src/features/access-requests/pages/AccessRequestsPage.tsxlines 315-316: replace!['pending', 'in_review'].includes(request.status)with!isActionable(request). Runpnpm test— passes.
Phase 9 — ErrorBoundary in PlatformLayout (ADR-005) [PR 2 — sequential after 8.2]
- 9.1 Create
src/app/layouts/ErrorBoundary.tsx: native React class component withstate: { hasError: boolean; error: Error | null };static getDerivedStateFromError; fallback renders error message + logout button (readsuseAuthStore.getState().logout()+window.location.replace('/login')). - 9.2 Edit
src/app/layouts/PlatformLayout.tsx: wrap<Outlet />in<ErrorBoundary>; sidebar and<Toaster/>remain outside boundary. Runpnpm test— ensure no regressions. - 9.3 RED/GREEN — Add
src/app/layouts/ErrorBoundary.test.tsx: (a) child throws → fallback renders; (b) fallback contains logout link/button. Runpnpm test— passes.
Phase 10 — Final Validation [both PRs]
- 10.1 Run
pnpm testfromservices/frontend-console— all tests green, zero skipped. (23/23 tests pass) - 10.2 Run
pnpm build— TypeScript reports zero errors (validates removedtokenfield,LoginResponseshape,apiClientsignature). (skipped per project rule) - 10.3 Verify
pnpm lintpasses — no new ESLint warnings. (exits 0)