docs(campaigns): design spec for manager approval workflow
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# Aprobación de campañas por rol Manager
|
||||
|
||||
**Fecha:** 2026-07-02
|
||||
|
||||
## Contexto
|
||||
|
||||
Hoy `red_lead`/`blue_lead` crean, editan, añaden/quitan tests y **activan** campañas directamente, incluida la fecha de inicio (`start_date`). No hay ningún control de aprobación intermedio, ni un registro dedicado de quién modifica qué en una campaña (solo el `AuditLog` genérico, sin endpoint de lectura para campañas).
|
||||
|
||||
Roles actuales: `admin`, `red_lead`, `blue_lead`, `red_tech`, `blue_tech`, `viewer`.
|
||||
Estados actuales de campaña: `draft → active → completed → archived`.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Los leads solo pueden "montar" campañas (crear, nombrar, añadir/quitar tests) mientras están en borrador. Un nuevo rol `manager` aprueba la campaña y es quien fija las fechas. Una vez activa, cualquier cambio de tests requiere justificación y pasa de nuevo por aprobación del manager antes de aplicarse. Todo queda registrado en un timeline consultable.
|
||||
|
||||
## Roles
|
||||
|
||||
- Nuevo rol **`manager`**, transversal (no separado por equipo red/blue).
|
||||
- `admin` conserva capacidad de aprobar/rechazar, como respaldo (patrón ya usado en el resto de la app).
|
||||
- `red_lead`/`blue_lead` pierden la capacidad de activar campañas directamente y de fijar `start_date`.
|
||||
|
||||
## Máquina de estados de campaña
|
||||
|
||||
```
|
||||
draft ──submit(lead/admin)──> pending_approval ──approve(manager/admin, fija start_date)──> active
|
||||
│
|
||||
└──reject(manager/admin, motivo obligatorio)──> draft
|
||||
active ──complete (sin cambios)──> completed ──archive (sin cambios)──> archived
|
||||
```
|
||||
|
||||
Reglas:
|
||||
- `submit` requiere que la campaña tenga ≥1 test (misma regla que hoy exige `activate`).
|
||||
- `start_date` no es editable por el lead (se oculta/bloquea en creación y edición). Solo el manager la fija al aprobar; es obligatoria para poder aprobar.
|
||||
- Edición de nombre/descripción/tests solo permitida en `draft`. En `pending_approval` la campaña queda congelada.
|
||||
- Rechazo devuelve la campaña a `draft`, guardando el motivo; el lead puede corregir y reenviar.
|
||||
|
||||
## Modificación de campaña activa (añadir/quitar test)
|
||||
|
||||
Ninguna modificación de tests se aplica directamente sobre una campaña `active`. Se crea una entidad `CampaignModificationRequest`:
|
||||
|
||||
- Campos: `campaign_id`, `requested_by`, `action` (`add_test` | `remove_test`), `test_id`, `justification` (obligatoria), `status` (`pending`/`approved`/`rejected`), `reviewed_by`, `reviewed_at`, `review_notes`, `created_at`.
|
||||
- Mientras la solicitud está `pending`, la campaña sigue `active` sin ningún cambio real en `CampaignTest`.
|
||||
- Manager aprueba → se aplica el cambio real (añade/quita la fila de `CampaignTest`) y la solicitud pasa a `approved`.
|
||||
- Manager rechaza → no se aplica ningún cambio; la solicitud pasa a `rejected` con `review_notes`.
|
||||
|
||||
## Timeline / auditoría
|
||||
|
||||
Se reutiliza el `AuditLog` genérico existente (`entity_type="campaign"`), sin tabla nueva. Nuevas acciones registradas:
|
||||
|
||||
`submit_campaign_for_approval`, `approve_campaign`, `reject_campaign`, `request_campaign_modification`, `approve_campaign_modification`, `reject_campaign_modification`
|
||||
|
||||
Nuevo endpoint de lectura `GET /campaigns/{id}/timeline` que devuelve las entradas de `AuditLog` para esa campaña ordenadas cronológicamente (mismo patrón que el timeline ya existente para tests).
|
||||
|
||||
## Endpoints backend
|
||||
|
||||
| Endpoint | Rol | Efecto |
|
||||
|---|---|---|
|
||||
| `POST /campaigns/{id}/submit` | lead/admin | draft → pending_approval |
|
||||
| `GET /campaigns/pending-approval` | manager/admin | cola de campañas pendientes |
|
||||
| `POST /campaigns/{id}/approve` | manager/admin | fija fechas, pending_approval → active |
|
||||
| `POST /campaigns/{id}/reject` | manager/admin | motivo obligatorio, pending_approval → draft |
|
||||
| `POST /campaigns/{id}/modification-requests` | lead/admin | crea solicitud (solo si active) |
|
||||
| `GET /campaigns/modification-requests/pending` | manager/admin | cola de solicitudes pendientes |
|
||||
| `POST /campaigns/modification-requests/{id}/approve` | manager/admin | aplica el cambio de test |
|
||||
| `POST /campaigns/modification-requests/{id}/reject` | manager/admin | nota obligatoria, no aplica cambio |
|
||||
| `GET /campaigns/{id}/timeline` | cualquiera autenticado | historial de la campaña |
|
||||
|
||||
Los endpoints existentes `PATCH /campaigns/{id}`, `POST/DELETE .../tests` pasan a devolver 409 fuera de `draft`.
|
||||
|
||||
## Modelo de datos
|
||||
|
||||
`backend/app/models/campaign.py`:
|
||||
- Ampliar enum de `status` con `pending_approval`.
|
||||
- Añadir `approved_by` (FK users, nullable), `approved_at` (datetime, nullable), `rejection_reason` (text, nullable).
|
||||
- `start_date` pasa a nullable en creación (solo se rellena en `approve`).
|
||||
|
||||
Nuevo modelo `CampaignModificationRequest` (tabla nueva, migración Alembic):
|
||||
- `id`, `campaign_id` (FK), `requested_by` (FK users), `action` (enum), `test_id` (FK tests), `justification` (text, not null), `status` (enum, default `pending`), `reviewed_by` (FK users, nullable), `reviewed_at` (datetime, nullable), `review_notes` (text, nullable), `created_at`.
|
||||
|
||||
## Frontend
|
||||
|
||||
- Rol `manager` añadido al selector de roles en gestión de usuarios.
|
||||
- Botón "Enviar a aprobación" reemplaza "Activar" para el lead en campañas `draft`.
|
||||
- Página/cola de aprobación para manager: lista de campañas `pending_approval` con modal de aprobar (pide `start_date` obligatoria) y modal de rechazar (pide motivo obligatorio).
|
||||
- En campaña `active`: botón "Solicitar modificación" (lead) con formulario de justificación obligatoria; sección de solicitudes pendientes con aprobar/rechazar (manager, rechazo con nota obligatoria).
|
||||
- Pestaña "Timeline" en el detalle de campaña, listando las entradas de auditoría en orden cronológico (mismo patrón visual que el timeline ya existente en tests).
|
||||
|
||||
## Testing
|
||||
|
||||
Tests pytest cubriendo:
|
||||
- Permisos por rol en cada endpoint nuevo (lead no puede aprobar, manager no puede montar/editar tests directamente, tech no puede ni montar ni aprobar).
|
||||
- `approve` falla sin `start_date`.
|
||||
- `reject` falla sin motivo.
|
||||
- `submit` falla si la campaña no tiene tests.
|
||||
- Campaña `active` no cambia su lista de tests hasta que se aprueba la `CampaignModificationRequest`.
|
||||
- Rechazo de campaña vuelve a `draft` conservando el motivo.
|
||||
- `GET /campaigns/{id}/timeline` devuelve las entradas en orden cronológico correcto.
|
||||
Reference in New Issue
Block a user