Saltar a contenido

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):
    1. 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ó).
    2. 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).
    3. 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 ejecutado nunca 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.

Implementation Plan

  • 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.
  • gestor/src/api/gestor.ts + gestor/src/types/api.ts: agregarSorteo + tipos.

Verification

  • [ ] 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).