# 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