# 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.
