Files
Aegis/docs/superpowers/specs/2026-07-02-campaign-manager-approval-design.md
T

6.2 KiB

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.