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.
130 lines
11 KiB
Markdown
130 lines
11 KiB
Markdown
# 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)
|
|
|
|
- [x] 0.1 Confirm R1 audit above (already done — document in PR 1 description: only `ProtectedRoute.tsx` and `LoginPage.tsx` read `state.token`; no bracket-string reads).
|
|
- [x] 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]
|
|
|
|
- [x] 1.1 Install devDeps in `services/frontend-console/package.json`: add `vitest@^3.2.4`, `jsdom@^26.1.0`, `@testing-library/react@^16`, `@testing-library/dom@^10`, `@testing-library/user-event@^14` under `devDependencies`; add `"test": "vitest run"` to `scripts`. [W1 CLOSED: removed --passWithNoTests flag]
|
|
- [x] 1.2 Add `test` block to `services/frontend-console/vite.config.ts`: `{ environment: 'jsdom', setupFiles: './src/test/setup.ts', restoreMocks: true, clearMocks: true }` — mirror admin pattern verbatim.
|
|
- [x] 1.3 Create `services/frontend-console/src/test/setup.ts` — verbatim copy of `services/frontend-admin/src/test/setup.ts` (localStorage/sessionStorage mock + `IS_REACT_ACT_ENVIRONMENT`).
|
|
- [x] 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]
|
|
|
|
- [x] 2.1 **RED** — Create `src/features/auth/stores/auth-store.test.ts`. Cases: (a) `setSession` writes `accessToken` and `user`, `token` field is `undefined` or absent; (b) `logout` sets `accessToken` and `user` to `null`. Run `pnpm test` — expect failures on (a) because `token: accessToken` is still set.
|
|
- [x] 2.2 **GREEN** — Edit `src/features/auth/stores/auth-store.ts`: remove `token` from `AuthState` interface; remove `token: null` from initial state; remove `token: accessToken` from `setSession`; remove `token: null` from `logout`. Run `pnpm test` — auth-store tests pass.
|
|
- [x] 2.3 **GREEN (ripple)** — Edit `src/app/router/ProtectedRoute.tsx` line 9: replace `state.token` with `state.accessToken`; update `hasCheckedRefresh` init and `useEffect` dep to use `accessToken`. Edit `src/features/auth/pages/LoginPage.tsx` lines 16,24,29: replace `token` selector with `accessToken`. Run `pnpm test` and `pnpm build` — must pass (TypeScript will catch any missed reads).
|
|
|
|
---
|
|
|
|
## Phase 3 — LoginResponse Alignment (ADR-002) [PR 1 — sequential after 2.3]
|
|
|
|
- [x] 3.1 **RED** — Add test to `src/features/auth/stores/auth-store.test.ts` (or a separate `src/features/auth/api/auth-api.test.ts`): assert `LoginResponse` type has `access_token: string` and `token: string`; assert no `refresh_token` field. This is a compile-time type test — use `expectTypeOf` from vitest. Run `pnpm test` — fails because current `LoginResponse` has `access_token?: string` (optional) and `refresh_token?: string`.
|
|
- [x] 3.2 **GREEN** — Edit `src/features/auth/api/auth-api.ts`: set `access_token: string` (required), `token: string` (required, legacy compat), remove `refresh_token`. Edit `src/features/auth/pages/LoginPage.tsx` line 45: remove `?? response.token` fallback — read `response.access_token` directly. Run `pnpm test` — passes.
|
|
|
|
---
|
|
|
|
## Phase 4 — SessionCoordinator + apiClient Split (ADR-003) [PR 1 — sequential after 3.2]
|
|
|
|
- [x] 4.1 **RED** — Create `src/lib/api/client.test.ts`. Cases for SessionCoordinator: (a) two concurrent `refresh()` calls → `fetch` spy called exactly once, both promises resolve with the same token value; (b) `refresh()` when server returns non-ok → returns `null`. Run `pnpm test` — (a) should already pass (deduplication logic exists); (b) may already pass. Document baseline.
|
|
- [x] 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 return `null as T` (assert it throws or that `apiClientVoid` is the correct path — per spec, `apiClient<T>` on 204 is a dev-time guard). Run `pnpm test` — (d) fails because today `null as T` is returned silently.
|
|
- [x] 4.3 **GREEN** — Edit `src/lib/api/client.ts`: extract shared `executeWithRefresh` logic; rename existing `apiClient<T>` 204 branch to throw `new Error('Use apiClientVoid for 204 endpoints')`; add `export async function apiClientVoid(endpoint, options): Promise<void>` that returns `void` on 204 and throws on non-ok. Remove `null as T` casts at lines 96 and 111. Run `pnpm test` — all passes.
|
|
- [x] 4.4 Confirm no callers need migration (A2 audit showed zero 204 callsites) — run `pnpm build` for TypeScript validation.
|
|
|
|
---
|
|
|
|
## Phase 5 — ProtectedRoute Hydration (ADR-004) [PR 1 — sequential after 4.4]
|
|
|
|
- [x] 5.1 **RED** — Add cases to `src/lib/api/client.test.ts` or a new `src/app/router/ProtectedRoute.test.tsx`: (a) cold visit with `user: null` → renders `<Navigate to="/login"/>` immediately, no "Restaurando" text; (b) warm visit with `user` set and `accessToken` null → renders loading indicator then resolves after refresh. Run `pnpm test` — (a) fails because current code always shows "Restaurando" flash on cold visit.
|
|
- [x] 5.2 **GREEN** — Edit `src/app/router/ProtectedRoute.tsx`: replace `token`-based heuristic with `useAuthStore.persist.hasHydrated()` guard. Flow: wait for `hasHydrated()`; if `accessToken` → render `<Outlet/>`; else if `user` (persisted) → attempt refresh + show indicator; else → `<Navigate to="/login"/>` immediately. Use `useAuthStore.persist.onFinishHydration` or `rehydrate` to trigger hydration detection. Run `pnpm test` — passes.
|
|
|
|
---
|
|
|
|
## Phase 6 — AccessRequestsPage Error UI (ADR-006) [PR 2 — sequential, depends on PR 1 merged]
|
|
|
|
- [x] 6.1 **RED** — Create `src/features/access-requests/pages/AccessRequestsPage.test.tsx`. Cases: (a) `requestsQuery` in error state → renders error card with "Reintentar" button, NOT empty table; (b) `allRequestsQuery` in error state → stat cards show `0`, no crash. Run `pnpm test` — (a) fails because error branch is missing from current JSX. [W3 CLOSED: test 6.1c added — Reintentar click triggers refetch]
|
|
- [x] 6.2 **GREEN** — Edit `src/features/access-requests/pages/AccessRequestsPage.tsx`: after the `requestsQuery.isLoading` check, add `requestsQuery.isError` branch rendering an inline error card with retry button (`requestsQuery.refetch()`); update stat card values to use `?? 0` guards for `allRequestsQuery.isError`. Run `pnpm test` — passes.
|
|
|
|
---
|
|
|
|
## Phase 7 — AccessRequestsPage Mutation Tests (ADR-008 / spec) [PR 2 — sequential after 6.2]
|
|
|
|
- [x] 7.1 **RED** — Add cases to `src/features/access-requests/pages/AccessRequestsPage.test.tsx`: (a) approve mutation success → `toast.success` called + `queryClient.invalidateQueries` called with `['platform-access-requests']`; (b) reject mutation success → same; (c) review mutation success → same; (d) any mutation error → `toast.error(err.message)`, no `invalidateQueries`. Run `pnpm 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]
|
|
- [x] 7.2 **GREEN** — Verify mutation `onSuccess` handlers in `AccessRequestsPage.tsx` match spec exactly (`invalidateRequests()` calls `queryClient.invalidateQueries({ queryKey: ['platform-access-requests'] })`). Adjust if needed. Run `pnpm test` — passes.
|
|
|
|
---
|
|
|
|
## Phase 8 — isActionable Consolidation (ADR-008) [PR 2 — sequential after 7.2]
|
|
|
|
- [x] 8.1 **RED** — Add case to `AccessRequestsPage.test.tsx`: `RequestDetail` Approve and Reject buttons are disabled when `request.status === 'approved'`; enabled when `'pending'` or `'in_review'`. Run `pnpm test` — fails because `RequestDetail` at line 315-316 inlines `['pending', 'in_review'].includes(request.status)` instead of using `isActionable`.
|
|
- [x] 8.2 **GREEN** — Edit `src/features/access-requests/pages/AccessRequestsPage.tsx` lines 315-316: replace `!['pending', 'in_review'].includes(request.status)` with `!isActionable(request)`. Run `pnpm test` — passes.
|
|
|
|
---
|
|
|
|
## Phase 9 — ErrorBoundary in PlatformLayout (ADR-005) [PR 2 — sequential after 8.2]
|
|
|
|
- [x] 9.1 Create `src/app/layouts/ErrorBoundary.tsx`: native React class component with `state: { hasError: boolean; error: Error | null }`; `static getDerivedStateFromError`; fallback renders error message + logout button (reads `useAuthStore.getState().logout()` + `window.location.replace('/login')`).
|
|
- [x] 9.2 Edit `src/app/layouts/PlatformLayout.tsx`: wrap `<Outlet />` in `<ErrorBoundary>`; sidebar and `<Toaster/>` remain outside boundary. Run `pnpm test` — ensure no regressions.
|
|
- [x] 9.3 **RED/GREEN** — Add `src/app/layouts/ErrorBoundary.test.tsx`: (a) child throws → fallback renders; (b) fallback contains logout link/button. Run `pnpm test` — passes.
|
|
|
|
---
|
|
|
|
## Phase 10 — Final Validation [both PRs]
|
|
|
|
- [x] 10.1 Run `pnpm test` from `services/frontend-console` — all tests green, zero skipped. (23/23 tests pass)
|
|
- [ ] 10.2 Run `pnpm build` — TypeScript reports zero errors (validates removed `token` field, `LoginResponse` shape, `apiClient` signature). (skipped per project rule)
|
|
- [x] 10.3 Verify `pnpm lint` passes — no new ESLint warnings. (exits 0)
|