Files
henryor af5535dfcc docs(openspec): archiva el change console-tech-debt-cleanup
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.
2026-05-07 21:14:56 -05:00

82 lines
3.5 KiB
Markdown

# Access Requests Specification
# Delta for: Access Requests — console-tech-debt-cleanup
## ADDED Requirements
### Requirement: Failed Query Surfaces Visible Error State
When `requestsQuery.isError` is true, `AccessRequestsPage` MUST render an explicit error state. The error state MUST be visually distinguishable from an empty list. The error state MUST include a retry action that re-executes the query.
#### Scenario: API 500 on initial load
- GIVEN the operator navigates to the Access Requests page
- WHEN `listAccessRequests` rejects (e.g. HTTP 500)
- THEN the table area is replaced by an error state element (not an empty table)
- AND the error state contains a retry button
- AND clicking the retry button calls `requestsQuery.refetch()`
#### Scenario: API failure mid-session
- GIVEN the page has loaded successfully and shows data
- WHEN the operator triggers a manual refresh and `listAccessRequests` rejects
- THEN the error state renders (replacing the previous data view)
- AND the operator can retry without reloading the full page
---
### Requirement: isActionable Is the Single Source of Truth for Actionable Status
The `RequestDetail` component MUST use the `isActionable(request)` helper to determine whether approve, reject, and "mark as review" actions are enabled. Inline status-string arrays (`['pending','in_review'].includes(...)`) MUST NOT appear in `AccessRequestsPage` or `RequestDetail`. The `isActionable` helper is already defined in the file; this requirement mandates it be used exclusively.
#### Scenario: Pending request — all action buttons enabled
- GIVEN a request with `status = 'pending'`
- WHEN `RequestDetail` renders
- THEN the "Revisar" button is enabled
- AND the "Aprobar" button is enabled
- AND the "Rechazar" button is enabled
- AND these states are derived from `isActionable(request)`
#### Scenario: In-review request — approve and reject enabled, review disabled
- GIVEN a request with `status = 'in_review'`
- WHEN `RequestDetail` renders
- THEN "Aprobar" and "Rechazar" are enabled
- AND "Revisar" is disabled (status is not `'pending'`)
- AND these states are derived from `isActionable(request)`
#### Scenario: Approved or rejected request — all action buttons disabled
- GIVEN a request with `status = 'approved'` or `status = 'rejected'`
- WHEN `RequestDetail` renders
- THEN "Revisar", "Aprobar", and "Rechazar" are all disabled
- AND `isActionable(request)` returns false
---
### Requirement: Mutation Success Shows Toast and Invalidates Cache
Each mutation (approve, reject, mark-as-review) MUST show a `toast.success` message on success and MUST invalidate the `['platform-access-requests']` query key. On error, each mutation MUST show a `toast.error` with the error message.
#### Scenario: Approve mutation success
- GIVEN a request with status `pending` or `in_review`
- WHEN the operator clicks "Aprobar" and `approveAccessRequest` resolves
- THEN `toast.success('Solicitud aprobada')` fires
- AND `queryClient.invalidateQueries({ queryKey: ['platform-access-requests'] })` is called
#### Scenario: Reject mutation success
- GIVEN the rejection modal is open with a reason of at least 3 characters
- WHEN the operator clicks "Rechazar" and `rejectAccessRequest` resolves
- THEN `toast.success('Solicitud rechazada')` fires
- AND the modal closes
- AND the query cache is invalidated
#### Scenario: Mutation error
- GIVEN any mutation (`approve`, `reject`, or `review`) is triggered
- WHEN the API call rejects with an `Error`
- THEN `toast.error(err.message)` fires
- AND the query cache is NOT invalidated