ADR-023 — Agregar un sorteo suelto (ad-hoc) a una campaña¶
Status: accepted (operador 2026-08-22)
Context: SPEC §3 define un calendario fijo de 6 sorteos (5 semanales + cierre),
generado por SorteoGeneracionService.generar y editable fecha a fecha vía
PUT /api/gestor/sorteos/{id}. El operador pidió poder sumar un sorteo extra (por
ejemplo, una acción puntual con un lote de premios adicional) sin tener que borrar y
regenerar todo el calendario.
Restricción dura (no se negocia — ADR-001): ADR-001 congela la ventana de un sorteo
en el momento en que se ejecuta (filtros del acta es un snapshot inmutable). El bucketing
de elegibles depende de que la secuencia orden de los sorteos de una campaña coincida
con el orden cronológico real de fecha_hora_publicada (SorteoService.calcularVentana
usa el sorteo con orden inmediato anterior para derivar el desde de la ventana). Insertar
un sorteo nuevo antes o entre sorteos ya ejecutados correría el riesgo de invalidar a
qué sorteo pertenece cada chance ya sorteada — inaceptable (CONSTITUTION: un acta ejecutada
es inmutable).
Por eso: la fecha_hora_publicada del sorteo nuevo debe ser estrictamente posterior
a la del último sorteo ya ejecutado de la campaña (si hay alguno). No hay excepción.
Alternatives:
Exigir borrar y regenerar todo el calendario para agregar un sorteo — descartada: obliga
a re-planificar semanas que ya podrían tener premios asignados, y SorteoGeneracionService.
generar ya rechaza regenerar si hay algo ejecutado (JD-A4).
Permitir insertar el sorteo en cualquier posición cronológica, incluso antes de un
ejecutado, recalculando filtros de actas ya persistidas — descartada: viola
directamente ADR-001/CONSTITUTION (un acta ejecutada nunca se re-escribe).
Elegida: permitir insertar el sorteo en cualquier posición cronológica posterior al
último ejecutado, y renumerar orden solo de los sorteos NO ejecutados para que la
secuencia completa (ejecutados + no ejecutados) siga siendo estrictamente cronológica.
Decision:
Nuevo endpoint POST /api/gestor/campanias/{id}/sorteos (distinto de
.../sorteos/generar, que crea la tanda completa). Body: {nombre, fecha_hora_publicada,
suplentes_a_sortear} (este último con default 10, SPEC §3). tipo siempre semanal — no
se crea un tipo de sorteo nuevo; el único cierre sigue siendo el que genera la regla de
programación.
Validaciones (400/409 vía GestorExceptionHandler, mismo patrón que el resto del gestor):
La campaña debe existir (400 si no) y no estar cerrada (409 — no tiene sentido
agregar sorteos a una campaña que ya terminó).
fecha_hora_publicada dentro de [vigencia_desde, vigencia_hasta] de la campaña (400
si no — simplifica sobre "hasta la fecha del cierre": la vigencia ya es el límite
superior natural de la campaña).
ADR-001 (crítico): si existe algún sorteo ejecutado, la fecha debe ser
estrictamente posterior a la fecha_hora_publicada del último ejecutado (409 si no,
mensaje explícito).
Renumeración: los sorteos ejecutadonunca cambian de orden (su acta ya congeló
esa ventana). Los sorteos pendiente (los existentes no ejecutados + el nuevo) se
ordenan cronológicamente por fecha_hora_publicada y se renumeran a partir de
max(orden de los ejecutados) + 1 — como el nuevo sorteo es posterior a todos los
ejecutados, naturalmente cae después de ellos en esa renumeración.
Consequences: El operador puede sumar un sorteo puntual sin tocar el historial ya
ejecutado. SorteoService.calcularVentana sigue funcionando sin cambios: al mantenerse el
invariante "orden == orden cronológico", las ventanas de los sorteos no ejecutados quedan
contiguas y sin solape automáticamente. La restricción dura empeora la flexibilidad (no se
puede insertar "en el medio" del pasado), pero es exactamente lo que ADR-001 exige.
backend/.../api/gestor/dto/AgregarSorteoRequest.java: DTO de request (nombre,
fechaHoraPublicada, suplentesASortear con default 10).
backend/.../api/gestor/SorteoGeneracionService.agregarSorteo(...): valida campaña/vigencia/
ADR-001, inserta y renumera.
backend/.../api/gestor/CampaniaGestorController: POST /{id}/sorteos → delega en el
servicio, devuelve SorteoResponse.
gestor/src/pages/CampaniaPage.tsx: botón "Agregar sorteo" + form (nombre, fecha/hora,
suplentes) junto a la tabla de sorteos; invalida sorteos-campania + panel al guardar.
[ ] Agregar sin sorteos ejecutados → 201, queda en su posición cronológica correcta.
[ ] Agregar con fecha posterior al último ejecutado → 201, ejecutados conservan su orden.
[ ] Agregar con fecha ≤ fecha del último ejecutado → 409 con mensaje claro (ADR-001).
[ ] Agregar con fecha fuera de la vigencia de la campaña → 400.
[ ] Agregar a una campaña cerrada → 409.
[ ] Test de integración: el sorteo agregado, al ejecutarse, toma únicamente las chances de
su propia ventana (calcularVentana sigue dando ventanas contiguas y sin solape tras
la renumeración).
[ ] Gestor: el form arma el body correcto; un 409 se muestra como mensaje claro (no un toast
genérico ilegible).