Saltar a contenido

Gestor

El gestor es el panel de administración interno del Sistema de Premios: donde el operador de Caracol carga la campaña, arma y ejecuta los sorteos, gestiona ganadores y entregas, administra el padrón de clientes y consulta reportes. A diferencia de la landing, no es anónimo — requiere login — y no tiene presupuesto de peso: es una herramienta de uso interno, no una página que un cliente abre desde un QR.

Se sirve bajo /gestor/, empaquetado dentro del mismo jar que el backend (ver Backend → build del jar único).

Stack

Tecnología Versión Para qué
React 18.3 Runtime de UI (sin cambios respecto al stack original — ver ADR-019, que aclara por qué el gestor no migró a Preact)
Vite ^5.4 Build
Tailwind CSS ^3.4 Estilos, con componentes propios en el estilo shadcn (components/ui/)
React Router ^6.26 Ruteo del lado del cliente, basename="/gestor"
Zustand ^5.0 Store de autenticación
TanStack Query ^5.59 Fetching/caching contra la API del gestor
Axios ^1.7 Cliente HTTP, con interceptors de auth y manejo de 401
React Hook Form + Zod ^7.53 / ^3.23 Formularios y validación
vite-plugin-compression2 ^2.5 Precomprime .br/.gz en build (mismo tratamiento que la landing)

PWA

El gestor es instalable como Progressive Web App — pensado para la tarea genuinamente móvil del panel: registrar la entrega de un premio en sucursal desde el celular. El resto del gestor sigue siendo mayormente de escritorio, pero el sidebar colapsa a un drawer por debajo de 768px y las tablas scrollean dentro de su propio contenedor (nunca el body).

Piezas de la PWA (public/manifest.webmanifest, public/sw.js, íconos 192/512 + maskable):

  • Manifest: nombre "Sistema Premios", scope/start_url = /gestor/, display: standalone, theme color = rojo de branding.
  • Service worker mínimo, a mano (sin Workbox ni vite-plugin-pwa — sin librería nueva): precachea el shell de la app con stale-while-revalidate, pero nunca cachea respuestas de /api/** ni /bases/** — el gestor es una herramienta viva, sus datos siempre tienen que salir frescos de la red. El predicado que decide esto (debeUsarSoloRed) es una función pura, testeada evaluando el propio archivo del service worker (no una copia en TypeScript que se pueda desincronizar).
  • El backend sirve manifest.webmanifest y sw.js con un controller dedicado (GestorPwaController), no por el resource handler estático genérico, porque necesita agregar el header Service-Worker-Allowed: /gestor/ por request.

Detalle de la decisión (responsive + PWA, sin abrir un segundo codebase) en ADR-022.

Autenticación

HTTP Basic contra /api/gestor/** (ver Backend → seguridad). El gestor no usa cookies ni sesión — cada request lleva el header Authorization armado en el cliente:

export function authHeader(credenciales: Credenciales | null): string | null {
  if (!credenciales) return null;
  return "Basic " + btoa(`${credenciales.usuario}:${credenciales.password}`);
}
  • Las credenciales viven en un store de Zustand (store/authStore.ts), persistidas en sessionStorage — a propósito, no localStorage: se pierden al cerrar la pestaña, no quedan indefinidamente en el disco del puesto de trabajo.
  • Un interceptor de request de Axios agrega el header Authorization a cada llamada.
  • Un interceptor de response detecta cualquier 401, desloguea (limpia el store) y muestra un toast de "Sesión vencida" — con un guard para no disparar el toast duplicado si llegan varios 401 concurrentes de una ráfaga de requests en vuelo.
  • El redirect a /login es declarativo, no un location.assign: RequireAuth lee el store y renderiza <Navigate to="/login"> en cuanto credenciales queda null — un reload duro destruiría el toast recién mostrado antes de que React lo pinte.

Diagrama

Secciones y rutas

Todas las rutas cuelgan de basename="/gestor" (React Router) y quedan detrás de RequireAuth, salvo /login:

Ruta Sección
/panel Panel — resumen operativo de la campaña
/campania Datos de la campaña (vigencia, bases, reglas de chances)
/premios Alta y administración de premios
/sorteos Generación y ejecución de sorteos
/sorteos/actas/:id Detalle del acta de un sorteo
/ganadores/:actaId Ganadores de un acta — gestión de entrega
/clientes Padrón de clientes
/clientes/:id Detalle de un cliente
/chances Consulta de chances (soporte/trazabilidad)
/claves Gestión de claves de firma (ClaveFirma)
/modo-prueba Generador de vouchers de prueba (ver Backend → modo prueba)

Cualquier ruta no reconocida (*) y la raíz (/) redirigen a /panel.

Cómo apunta al backend

Misma lógica que la landing: en producción, same-origin (VITE_API_BASE_URL ?? /api, servido por el mismo jar bajo /gestor/); en desarrollo (vite dev, puerto 5174), el proxy de Vite reenvía /api y /branding a http://localhost:8080.