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.
82 lines
3.5 KiB
Markdown
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
|