# 🎰 TÓMBOLA VIRTUAL — Documentación de entrega

Sistema profesional de sorteos en vivo con pantalla pública estilo **synthwave años 80**.
Refactorización del antiguo sistema de encuestas + sorteos (PHP plano + MySQL/MariaDB, sin framework).

---

## 1. Resumen del análisis del proyecto original

- **Tecnología:** PHP plano (sin framework, sin Composer), MySQL/MariaDB vía PDO, sesiones nativas, hosting cPanel (PHP 8.1).
- **Autenticación:** tabla `administradores`, contraseñas en `md5`, sesión + timeout. **Se conservó** (ahora también acepta `password_hash`).
- **Núcleo reutilizado:** clase `Database` (PDO singleton), helpers de sesión/CSRF/log, estructura visual con sidebar.
- **Módulos eliminados:** encuestas, preguntas/opciones, votos, QR y "en vivo" de encuestas.
- **Datos antiguos:** la tabla `usuarios_sorteo` tenía las columnas descuadradas (nombre/apellidos mezclados). Se respalda y se parte con una tabla `participantes` limpia (hay que reimportar el Excel).

## 2. Módulos eliminados

Archivos borrados: `encuesta.php`, `envivo.php`, `test_api.php`, `admin/encuestas.php`,
`admin/crear_encuesta.php`, `admin/envivo.php`, `admin/generar_qr.php`, `admin/2generar_qr.php`,
`admin/ver_resultados.php`, `admin/sorteo.php` (viejo), `admin/subir_excel.php` (viejo),
`admin/api_sorteo.php` (viejo). Carpeta `qr_codes/` eliminada.

Tablas eliminadas (respaldadas antes): `encuestas`, `opciones_encuesta`, `votos`
(+ reemplazo de `sorteos` y `usuarios_sorteo`).

## 3. Archivos nuevos / modificados

**Núcleo y BD**
- `admin/config.php` *(refactor)* — DB, auth, CSRF, JSON, configuración, estado de pantalla, **selección segura del ganador**, subida de archivos.
- `migracion_tombola.sql` *(nuevo)* — script de migración completo con respaldos.

**Panel admin (nuevos)**
- `admin/layout.php` — layout/sidebar compartido · `admin/assets/admin.css` · `admin/assets/admin.js`
- `admin/index.php` *(rebrand)* · `admin/dashboard.php` · `admin/participantes.php` · `admin/importar.php`
- `admin/sorteos.php` · `admin/control.php` (control en vivo) · `admin/ganadores.php` · `admin/configuracion.php`
- `admin/api.php` — endpoint AJAX de control (auth + CSRF)

**Pantalla pública (nuevos)**
- `versorteo.php` · `assets/versorteo.css` · `assets/versorteo.js`
- `api_sorteo.php` *(reescrito)* — API pública de solo lectura

**Seguridad de subidas:** `uploads/.htaccess` (desactiva ejecución PHP) + subcarpetas `premios/ logos/ fondos/ sonidos/`.

## 4. Cambios en la base de datos

Nuevas tablas: `participantes`, `sorteos`, `sorteo_ganadores`, `import_batches`,
`configuracion`, `estado_pantalla`. Se conservan `administradores` y `logs_actividad`.
Respaldos automáticos: `_bak_usuarios_sorteo`, `_bak_sorteos`, `_bak_encuestas`, `_bak_opciones`, `_bak_votos`.

## 5. Cómo actualizar el sistema (instalación)

1. **Respaldo:** en phpMyAdmin, exporta la base `re123_gigantes` completa (por seguridad).
2. **Subir archivos:** sube todo el proyecto al `public_html` (reemplazando los antiguos ya borrados).
3. **Ejecutar la migración:** en phpMyAdmin → pestaña **SQL** → pega y ejecuta `migracion_tombola.sql`
   (o `mysql -u re123_gigantes -p re123_gigantes < migracion_tombola.sql`). **Se ejecuta una sola vez.**
4. **Permisos de carpetas** (cPanel → Administrador de archivos → Permisos, o SSH):
   ```
   chmod 755 uploads uploads/premios uploads/logos uploads/fondos uploads/sonidos logs
   chmod 644 uploads/.htaccess
   ```
   La carpeta `uploads/` debe ser escribible por PHP (755; en algunos hostings 775).
5. **Entrar al panel:** `https://TU-DOMINIO/admin/` — usuario **admin**, contraseña **admin123**.
6. **Configura** (menú *Configuración*): nombre del evento, logo, fondo, colores, sonidos.
7. **Importa participantes** (*Participantes → Importar Excel/CSV*).

## 6. Cómo abrir la pantalla pública

- URL: **`https://TU-DOMINIO/versorteo.php`**
- Ábrela en el TV/proyector, pulsa el botón **⛶** (o tecla **F**) para pantalla completa.
- No tiene controles administrativos: solo refleja el estado del sorteo en tiempo real.
- Optimizada para 1920×1080 (16:9). Se recupera sola al recargar o reconectar.

## 7. Credenciales y configuración de entorno

- Credenciales de BD ya están en `admin/config.php` (las mismas del hosting).
- **Cambiar la contraseña admin** (recomendado). En phpMyAdmin ejecuta, por ejemplo:
  ```sql
  UPDATE administradores SET password = MD5('TU_NUEVA_CLAVE') WHERE usuario='admin';
  ```
  El login también acepta hashes modernos `password_hash()` si prefieres migrar.

## 8. Comunicación en tiempo real (explicación)

Se usa **polling AJAX** (compatible con cualquier hosting cPanel, sin WebSockets):
- La pantalla pública consulta `api_sorteo.php?action=estado` cada **~1,2 s**.
- Se persiste una fila única en `estado_pantalla` con `estado_actual`, `sorteo_activo_id`,
  `ganador_activo_id` y un `version_token` que se incrementa en cada cambio.
- El panel de control (`control.php`) también hace polling para mantenerse sincronizado.
- Como **todo el estado vive en la BD**, si se recarga `versorteo.php` recupera exactamente
  lo que estaba mostrando (espera, girando o ganador).

Estados implementados: `waiting`, `running`, `stopping`, `winner`, `cancelled`, `finished`.

## 9. Cómo se evita que un ganador vuelva a participar

- La selección ocurre **en el servidor** (`seleccionarGanadorSeguro()` en `config.php`) dentro de
  una **transacción** con **bloqueo de fila** (`SELECT ... FOR UPDATE`).
- Solo entran participantes con `estado='disponible'`.
- Al ganar, el participante pasa a `estado='ganador'`, `ha_ganado=1` → queda excluido.
- **Doble clic / solicitudes simultáneas:** el bloqueo serializa; si el sorteo ya no está
  `running`, la operación es **idempotente** y devuelve el ganador ya elegido (no crea otro).
- Solo el administrador puede devolver a alguien a `disponible` (restaurar / anular).

## 10. Seguridad implementada

Consultas preparadas (anti-SQLi) · escape de salida (anti-XSS) · **CSRF** en todo el admin ·
control de sesión + timeout · validación de archivos (extensión, tamaño y MIME real) ·
`uploads/` sin ejecución de PHP · transacciones en operaciones de sorteo · bloqueo de doble clic ·
anulación **lógica** (con quién/cuándo/motivo) en vez de borrado · auditoría en `logs_actividad` ·
respuestas JSON estandarizadas · la vista pública solo expone datos permitidos (`campos_ganador`).

## 11. Pruebas (checklist de verificación manual)

> No se pudo ejecutar PHP/MySQL en el entorno de desarrollo (sin servidor local), por lo que el
> código se revisó estáticamente. Ejecuta esta lista una vez desplegado:

Importar Excel válido · Excel con duplicados (se omiten) · archivo inválido (rechazado) ·
crear participante manual · iniciar sorteo · parar · mostrar ganador · confirmar exclusión del
ganador · recargar la pantalla mientras gira · recargar mostrando ganador · dejar de mostrar ·
segundo sorteo · repetir premio · anular ganador · restaurar participante · sortear sin
disponibles (alerta) · doble clic en «Parar» · abrir la pantalla en dos dispositivos ·
imagen de fondo · video de fondo · logo personalizado · 1920×1080 · panel desde el teléfono.

---

### Flujo de operación en vivo (resumen)
**Participantes → Importar** → **Sorteos → Crear** → **Control en vivo**: `Iniciar` ▶ `Parar` ⏹
(el servidor elige) → se muestra el ganador → `Dejar de mostrar` → (opcional) `Repetir premio`.
Todo desde una sola pantalla con vista previa de `versorteo.php` incluida.

---
# 🆕 ACTUALIZACIÓN v2 — sesiones/rondas, administradores y galería de fotos

## A. Cómo actualizar
1. Ejecuta **`migracion_tombola_v2.sql`** en phpMyAdmin (aditiva y segura; **no borra** sorteos ni ganadores existentes). Requiere MariaDB 10.5+.
2. Sube los archivos nuevos/actualizados.
3. Crea/permite las carpetas de subida de la galería:
   ```
   chmod 755 uploads/galeria uploads/galeria/thumbs
   ```
   (Se crean solas al subir la primera foto si `uploads/` es escribible.)
4. **Recomendado:** que el servidor tenga la extensión **GD** activa (para optimizar y generar miniaturas). Si no está, las fotos se guardan sin optimizar pero el sistema sigue funcionando. No se necesita Composer ni dependencias externas.

## B. Gestión de administradores
- Nuevo: `admin/admins.php` (crear / editar / eliminar administradores). Acceso desde **Configuración → Gestionar administradores**.
- Las contraseñas nuevas se guardan con `password_hash()`. No se puede eliminar la propia cuenta ni el último administrador.

## C. Cambios en `versorteo.php` (pantalla pública)
- **Eliminado** el título “Tómbola Virtual” de la esquina superior derecha (texto, contenedor, borde, sombra y fondo).
- **Logo centrado** arriba (`object-fit:contain`, alto máx `12vh`, ancho automático, sin deformar), presente en espera, sorteo y ganador.
- **Eliminado** el rectángulo interior de la tómbola, sus bordes, sombras, máscaras y la **línea con puntos azules** central. Los nombres giran ahora integrados y limpios, manteniendo el estilo synthwave.

## D. Modelo Sorteo general → Ronda → Ganador
- **`sorteos`** = sesión/evento general. **Al crear solo se pide el nombre** (la fecha se guarda sola).
- **`sorteo_rondas`** (nueva) = cada premio sorteado dentro de la sesión. En el control se escribe el **nombre del premio** y se pulsa **Iniciar**; cada ronda selecciona **un** ganador en el servidor.
- **`sorteo_ganadores`** gana la columna `ronda_id` (los ganadores antiguos quedan con `ronda_id` NULL).
- **`estado_pantalla`** gana `ronda_activa_id` (el premio en pantalla proviene de la ronda activa).
- Controles del control en vivo: **Iniciar, Parar, Repetir premio, Cancelar ronda, Dejar de mostrar**, con vista previa, estado, premio y ganador actuales, disponibles y **lista de ganadores de la sesión que se refresca sola** (sin recargar la página).
- **Repetir premio**: elige *“mantener el anterior y sortear otro”* o *“anular el anterior y volver a sortear”* (la anulación queda registrada con motivo, fecha y administrador; el participante vuelve a disponible).
- **Cancelar ronda**: cancela solo la ronda en curso; **no** elimina la sesión, ganadores ni el historial.

## E. Galería de fotos
- Nuevo menú **“Galería de fotos”** (`admin/galeria.php`): subida múltiple, miniaturas, ordenar (subir/bajar), activar/desactivar, eliminar, fecha y usuario que subió, abrir la pantalla pública.
- **Optimización** (con GD): corrección de orientación EXIF, redimensionado a máx. 1920px, compresión JPEG y miniatura para el panel. Nombres únicos, validación de tipo real (MIME) y de imagen válida, bloqueo de ejecución PHP en `uploads/`.
- Nueva pantalla pública **`verfotos.php`** (independiente): pantalla completa, sin controles, `object-fit:contain` por defecto, fondo negro o **desenfoque** de la propia foto, transiciones (fundido, deslizamiento, zoom, desenfoque, disolución, vertical, Ken Burns o aleatoria) y precarga de la siguiente imagen.
- **Configuración de galería** en *Configuración*: logo, mensaje de espera, segundos por foto, duración/tipo de transición, ajuste (contain/cover), fondo, orden (secuencial/aleatorio), bucle, mostrar logo/contador/fecha e intervalo de comprobación.

## F. Tiempo real y cola dinámica de fotos
- `verfotos.php` hace **polling** a `api_galeria.php?action=lista` (intervalo configurable) y usa un **token `galeria_version`** que se incrementa ante cualquier cambio (subir/activar/desactivar/ordenar/eliminar o cambio de config).
- **Cola dinámica:** una foto nueva **no interrumpe** la actual ni reinicia la presentación; se **precarga** y se **inserta tras 1–2 imágenes** (posición `idx+2`). No hay parpadeo ni se sale de pantalla completa.
- **Eliminar/desactivar:** si la foto no está en pantalla, sale de la cola de inmediato; si está visible, **termina su turno** y no vuelve a aparecer. Nunca deja la pantalla vacía (cae al estado de espera si no quedan activas).
- **Reconexión automática:** ambas pantallas (`versorteo.php` y `verfotos.php`) recuperan su estado tras recarga, pérdida de red o suspensión, porque todo el estado vive en la base de datos.

## G. Archivos de esta actualización
**Nuevos:** `migracion_tombola_v2.sql`, `admin/admins.php`, `admin/galeria.php`, `verfotos.php`,
`api_galeria.php`, `assets/verfotos.css`, `assets/verfotos.js`.
**Modificados:** `admin/config.php` (rondas, versión de galería, procesamiento de imágenes),
`admin/api.php` (acciones por ronda), `admin/sorteos.php` (crear solo con nombre),
`admin/control.php` (flujo por ronda + premio + lista en vivo), `admin/configuracion.php` (galería + admins),
`admin/layout.php` (menú Galería), `admin/dashboard.php`, `api_sorteo.php` (premio desde la ronda),
`versorteo.php` + `assets/versorteo.css` + `assets/versorteo.js` (simplificación visual).

## H. Rutas públicas
- Pantalla de sorteo: **`https://TU-DOMINIO/versorteo.php`**
- Pantalla de fotos: **`https://TU-DOMINIO/verfotos.php`**

## I. Pruebas (checklist v2)
> No hay PHP/MySQL en el entorno de desarrollo; revisión estática. Verificar en el hosting:
Crear sorteo solo con nombre · ingresar premio e iniciar · premio visible en la pantalla externa ·
**sin** título “Tómbola Virtual” · logo centrado · sin rectángulo/línea azul · parar (un ganador) ·
guardado y exclusión · dejar de mostrar · segundo premio en la misma sesión · repetir (ambos modos) ·
cancelar ronda sin perder la sesión · recargar girando / mostrando ganador · galería sin fotos (espera) ·
subir 1 y varias · aparición sin recargar · transiciones · subir mientras otra se muestra (entra tras 1–2) ·
eliminar/desactivar visible y no visible · cambiar orden/duración/transición · verticales y horizontales ·
alta resolución · varias pantallas a la vez · pérdida de conexión y reconexión · consola sin errores.

---
# 🎨 MÓDULO GENERACIÓN DE IMÁGENES CON IA (v4)

Módulo **aditivo** (prefijo `gi_`). Solo se modificó `admin/layout.php` (una línea de menú). Usa **OpenAI `gpt-image-1`** con la API Key **cifrada** en el servidor, una **cola procesada por Cron**, captura pública por **QR** e integración con el **slider de Galería existente** (no se tocó `verfotos.php`).

## A. Instalación
1. Ejecuta **`migracion_gen_imagenes.sql`** en phpMyAdmin (crea tablas `gi_*`; no altera nada existente).
2. Sube todos los archivos nuevos. Permisos: `chmod 755 uploads/gi uploads/gi/*`.
3. **Requisitos del servidor:** extensión **cURL** (llamadas a OpenAI), **GD** (miniaturas) y **OpenSSL** (cifrado). Todos estándar en cPanel PHP 8.1. Sin Composer.
4. **Cron (obligatorio para procesar la cola):** en cPanel → *Cron Jobs*, cada minuto. El comando exacto aparece en **Generación IA → General**. Ejemplo:
   ```
   * * * * * php /home/USUARIO/public_html/gi_worker.php >/dev/null 2>&1
   ```
   (o por URL con `wget` usando el token del worker, también indicado en esa página).
5. Sube fotos grandes: si tus selfies pesan, sube `upload_max_filesize` y `post_max_size` en PHP (cPanel → *MultiPHP INI Editor*).

## B. Configurar OpenAI (una vez)
**Generación IA → Config. IA → Nuevo proveedor**: nombre, modelo `gpt-image-1`, pega tu **API Key** (se guarda cifrada, se muestra `sk-***…9F2`), marca *Activo*, **Guardar**, y pulsa **Probar conexión** (🔌). La Key nunca viaja al navegador del participante ni queda en el código.

## C. Flujo del administrador
Crear evento (solo nombre) → **Configurar**: sube **fondos** (los que quieras, sin límite), escribe el **prompt principal** (+ prompt por fondo, con *Proteger textos/logos*), elige formato/calidad, método de fondo, **aprobación** (manual/automática) y **publicación** en el slider → prueba con **“Probar/cargar foto (admin)”** → **Activar** el evento → descarga/proyecta el **QR**. Revisa **Cola** e **Imágenes** (aprobar/rechazar, *Enviar al slider*, **Mostrar ahora ⚡**, descargar, eliminar).

## D. Flujo del participante (QR)
Escanea → ve el evento → **Tomar selfie** (cámara con front/back) o **Elegir foto** → confirma → **acepta la autorización** → **Generar** → pantalla de espera con posición en la fila → recibe su imagen, la **descarga** (si está permitido) y ve si saldrá en la pantalla.

## E. Integración con el slider (sin tocarlo)
“Enviar al slider” / publicación automática = **insertar la imagen en `galeria_fotos`** (`activo=1`) + `galeriaBumpVersion()`. Se proyecta en `verfotos.php` con el mecanismo actual. “Quitar de pantalla” borra la fila del slider **sin borrar** el archivo generado. **Mostrar ahora** la manda al frente de la rotación.

## F. Seguridad y control de abuso
API Key cifrada (AES-256, clave derivada de `APP_SECRET`); validación 100% en servidor; MIME real + peso + dimensiones; nombres únicos; `uploads/` sin ejecución PHP; CSRF en el admin; token firmado en la página pública; **consentimiento** guardado (evento, versión de texto, IP hasheada, token); rate-limit por IP y **máx. fotos por dispositivo**; cola con reintentos, recuperación de trabajos colgados y cambio a `error` ante Key inválida/saldo; auditoría en `gi_historial`; registro de consumo en `gi_consumo`. La IP se guarda **hasheada**.

## G. Archivos nuevos
**Público:** `gi_captura.php`, `api_gi.php`, `gi_qr.php`, `gi_worker.php`, `gi_lib.php`, `assets/gi_captura.css/js`.
**Admin:** `admin/gi_eventos.php`, `admin/gi_evento.php`, `admin/gi_ia.php`, `admin/gi_cola.php`, `admin/gi_imagenes.php`, `admin/gi_config.php`, `admin/api_gi.php`.
**BD:** `migracion_gen_imagenes.sql` (tablas `gi_config_ia`, `gi_eventos`, `gi_fondos`, `gi_fotos_originales`, `gi_autorizaciones`, `gi_trabajos`, `gi_imagenes`, `gi_consumo`, `gi_historial`, `gi_config`).
**Modificado:** solo `admin/layout.php`.

## H. Honestidad sobre alcance y calidad
- La **calidad “natural”** de recorte/integración/identidad la aporta **el modelo de OpenAI + tu prompt**, no el PHP. Ajusta el prompt del evento y de cada fondo hasta lograr el look deseado; usa la prueba de admin antes de activar.
- `gpt-image-1` genera en 1024×1024 / 1536×1024 / 1024×1536 (mapeo automático desde tu formato); no entrega 1920×1080 exacto (para el slider, que usa *contain*, es suficiente).
- **Simplificado / a futuro** (documentado, no incluido en esta entrega): editor manual de posición, marcado visual de zonas protegidas, descarga ZIP múltiple, captcha, selección de fondo por el participante en la página pública, papelera con restauración y estimación de costo en dinero. Todo lo demás del flujo está implementado y operativo.

## H.2 REARQUITECTURA v4.1 — fondo intacto (recorte + composición)
**Problema detectado:** enviar fondo+selfie a `gpt-image-1` (modelo generativo) **redibujaba el fondo**, cambiaba identidad, encuadre y aspecto (no daba 1920×1080). Un modelo generativo no puede preservar píxeles del fondo por más que lo pida el prompt.
**Nuevo flujo (determinista):**
1. **Recorte** — se envía **solo la selfie** a `gpt-image-1` con `background=transparent`; devuelve la persona recortada (PNG con alpha). *(`gi_lib.php › giRecortarPersonaOpenAI`)*.
2. **Composición** — **motor de fotomontaje en GD** que integra el recorte sobre el **fondo original sin tocar un píxel**: escalado automático por altura, posición/escala por fondo, **feather + antialias** de bordes (sin efecto sticker), **sombra de contacto** bajo el cuerpo y **sombra ambiental** desplazada, **igualación de color/contraste** y **rebote** desde el promedio del fondo, y **luces direccionales** (magenta desde el piso, azul lateral, contraluz cálida). Salida en el **tamaño exacto del fondo** (1920×1080). *(`gi_lib.php › giComponer`)*.
Resultado: fondo idéntico, formato exacto, identidad = píxeles reales de la persona.
**Editor visual:** en cada fondo, botón **↔ Posicionar** abre un editor donde arrastras/escalas el recuadro de la persona (se guarda `persona_x/y/escala/sombra`).
**Archivos:** `gi_lib.php`, `gi_worker.php`, `admin/gi_evento.php` (modificados) + **`migracion_gen_imagenes_v2.sql`** (nuevo; ALTER `gi_fondos`).
**Requisitos:** los fondos deben ser **1920×1080** con espacio libre donde irá la persona. Nota: `gpt-image-1` recorta bien pero puede alterar levemente el rostro (es generativo); si se necesita identidad 100% exacta, se puede cambiar el recorte a remove.bg (segmentación pura) sin tocar el resto.

## H.3 v4.2 — Recorte con BRIA + composición con Imagick
**Recorte:** ahora por **BRIA AI** (Background Removal, segmentación pura → identidad 100% exacta, no recrea a la persona). Solo cambia el proveedor; el resto del flujo (cola, QR, slider, etc.) queda igual. Se configura en **Config. IA**: proveedor `bria`, URL `https://engine.prod.bria-api.com/v1`, endpoint `/background/remove`, y tu API Key (cifrada, header `api_token`). OpenAI queda como alternativa seleccionable. *(`gi_lib.php › giRecortarPersonaBria` + despachador `giRecortarPersona`)*.
**Composición:** motor **Imagick** (`giComponerImagick`) con **GD como respaldo automático** si el servidor no tiene Imagick. Hace: recorte de márgenes transparentes, autoescala por altura, feather/antialias (blur del canal alfa), corrección de color/contraste/saturación/luminosidad + temperatura (modulate + tinte del color del fondo), **sombra de contacto** y **ambiental tintadas** (nunca negro puro) con dirección según el lado más iluminado del fondo, y **luces direccionales** (magenta desde el piso, azul, contraluz cálida) con gradientes enmascarados a la silueta. Fondo intacto y salida exacta 1920×1080.
**Editor por fondo:** el editor arrastrable ahora guarda además **intensidad y desenfoque de sombra** y **intensidad de luz magenta/azul/contraluz** (sliders), reutilizados en todas las fotos de ese fondo.
**Archivos:** `gi_lib.php`, `gi_worker.php`, `admin/gi_ia.php`, `admin/gi_evento.php` (modificados) + **`migracion_gen_imagenes_v3.sql`** (nuevo). **Requiere** la extensión **Imagick** (recomendada) y una **API Key de BRIA**.

## I. Pruebas del módulo (checklist)
> Revisión estática (sin PHP/MySQL local). Verificar en el hosting:
Configurar OpenAI + *Probar conexión* OK · crear evento + subir fondos + prompt · prueba de admin genera imagen · activar evento · QR abre `gi_captura.php` · tomar selfie (cámara) y elegir archivo · aceptar autorización obligatoria · ver pantalla de espera y recibir resultado · aprobar/rechazar · *Enviar al slider* aparece en `verfotos.php` · *Mostrar ahora* · límite por dispositivo/IP · pausar/finalizar evento corta la recepción · reintentar un trabajo con error · Cron procesa la cola · consola/PHP sin errores · el resto del sistema (sorteos, galería, votaciones) sigue igual.

---
# ❓ MÓDULO TRIVIA EN VIVO CON QR

Trivia en vivo: el admin crea una pregunta con alternativas (sin límite) y marca la correcta + un tiempo; los asistentes escanean el QR, entran con su **apodo**, ven la pregunta con **cuenta regresiva** y responden; **gana el primero que acierta antes de que acabe el tiempo**. Aditivo, reutiliza toda la infraestructura (patrón de Votaciones).

## Instalación
1. Ejecuta **`migracion_trivia.sql`** (crea `trivias`, `trivia_opciones`, `trivia_participantes`, `trivia_respuestas`).
2. Sube los archivos nuevos. `chmod 755 uploads/trivias`.

## Archivos
**Público:** `trivia_jugar.php`, `api_trivia.php`, `trivia_qr.php`, `vertrivia.php`, `trivia_lib.php`, `assets/trivia_jugar.css/js`, `assets/vertrivia.css/js`.
**Admin:** `admin/trivias.php`, `admin/trivia_editar.php`, `admin/trivia_control.php`, `admin/api_trivia.php`.
**Modificado:** solo `admin/layout.php` (menú “Trivias”).

## Uso
Crear trivia (pregunta) → **Editar**: alternativas (agrega las que quieras, marca la correcta), tiempo, logo/fondo, QR → **Control en vivo**: **Iniciar** (arranca el reloj) / **Cerrar** / **Reiniciar** (nueva ronda). Rutas públicas: jugar **`/trivia_jugar.php?codigo=`**, pantalla **`/vertrivia.php?codigo=`**.

## Cómo se define el ganador (a prueba de carreras)
Al llegar una respuesta correcta se ejecuta, dentro de una transacción con la fila bloqueada, `UPDATE trivias SET ganador=? WHERE id=? AND ganador IS NULL AND estado='activa' AND cierra_at > NOW(3)`. El **primer commit gana**; los demás ven que ya hay ganador. Un intento por dispositivo (cookie + hash + único). Todo el control de tiempo usa el **reloj de MySQL** (NOW(3)) para evitar desfases PHP/MySQL.

---
# 🗳️ MÓDULO DE VOTACIONES EN VIVO (v3)

Módulo **totalmente aditivo**. El único archivo existente modificado es
`admin/layout.php` (una línea de menú). No se tocó ninguna tabla, función ni
módulo previo.

## A. Instalación
1. Ejecuta **`migracion_votaciones.sql`** en phpMyAdmin (crea tablas nuevas; no altera las existentes).
2. Sube los archivos nuevos. `chmod 755 uploads/votaciones`.
3. **GD recomendado** para el QR en PNG. Si no hay GD, `qr.php` entrega el QR en **SVG** automáticamente (igual de válido). Sin Composer ni dependencias externas.
4. (Opcional) Puedes definir `define('APP_SECRET','una-frase-larga-secreta');` en `admin/config.php` para firmar cookies/tokens. Si no lo defines, se deriva automáticamente de la configuración de BD (no requiere editar nada).

## B. Archivos nuevos
**Público:** `votar.php`, `vervotaciones.php`, `api_votacion.php`, `qr.php`, `qrlib.php`,
`assets/votar.css/js`, `assets/vervotaciones.css/js`.
**Compartido:** `votaciones_lib.php` (lógica del módulo).
**Admin:** `admin/votaciones.php`, `admin/votacion_editar.php`, `admin/votacion_control.php`, `admin/api_votaciones.php`.
**BD:** `migracion_votaciones.sql`.
**Modificado:** solo `admin/layout.php` (menú “Votaciones”).

## C. Tablas nuevas
`votaciones`, `votacion_participantes`, `votacion_votos` (con **UNIQUE `(votacion_id, ronda, navegador_hash)`**),
`votacion_historial`, `votacion_configuracion`.

## D. Rutas / endpoints
- Página pública móvil: **`/votar.php?codigo=CODIGO`**
- Pantalla externa: **`/vervotaciones.php?codigo=CODIGO`** (pantalla completa con F o el botón ⛶)
- QR: **`/qr.php?codigo=CODIGO`** (`&download=1` PNG, `&fmt=svg` SVG)
- API pública: `api_votacion.php` (`estado`, `pantalla`, `votar`)
- API admin: `admin/api_votaciones.php` (`cambiar_estado`, `toggle`, `ganadores`, `reiniciar`, `resolver_empate`, `estado`)

## E. Identificación del navegador y sus límites
- Al abrir una votación, el servidor crea una **cookie aleatoria** `tv_bid` (HttpOnly, Secure, SameSite=Lax, 1 año). No contiene datos personales.
- Se guarda solo el **hash** `navegador_hash = HMAC(secreto, votacion:ronda:tv_bid)`. La regla es **un voto por navegador/dispositivo por votación y ronda**, garantizada por transacción + **índice único** (el doble clic y las carreras chocan con la clave y devuelven “ya votaste”).
- La **IP** se guarda **hasheada** y solo se usa como señal (rate-limit 30/min), nunca como bloqueo principal (muchos comparten Wi-Fi).
- **Límite real reconocido:** una persona podría volver a votar con otro dispositivo, otro navegador, en modo privado o borrando cookies. **No se garantiza un voto por persona física**, solo por dispositivo/navegador. La validación es **100% en el servidor** (nunca se confía en JavaScript/cookies del cliente).

## F. Tiempo real
Polling con `version_token` por votación (se incrementa en cada voto y cambio de estado). La página pública, la pantalla externa y el control se sincronizan sin recargar; el video de fondo no se reinicia. Todo el estado vive en BD → se recupera tras recargar o reconectar.

## G. Flujo
Crear votación (solo nombre) → **Editar** (participantes desde `participantes`, ganadores, logo, fondo, textos, QR) → **Control en vivo**: Iniciar / Pausar / Reanudar / Finalizar / Reiniciar, mostrar-ocultar QR y resultados, y **revelar ganadores** (todos o uno por uno, de último a primer lugar). Empates detectados con alerta y resolución manual registrada en el historial. **Reiniciar** archiva los votos (no los borra) e inicia una ronda nueva donde los mismos navegadores pueden volver a votar.

## H. Configurar logo y fondo
En **Votaciones → Editar**: “Logo de la votación” (si se omite, usa el logo del sistema) y “Tipo de fondo” (animación synthwave / color-degradado / imagen / video MP4) con su archivo. El video va automático, silenciado y en bucle, sin reiniciarse con cada voto.

## I. Seguridad
CSRF en todo el admin; token firmado (HMAC) en la página pública; consultas preparadas; validación y sanitización; escape de HTML; código público difícil de adivinar; protección de doble clic; rate-limit; transacciones; restricción única; auditoría en `votacion_historial`. La pantalla externa y la actividad reciente **nunca** muestran la identidad del votante, ni IP, ni hash, ni datos del dispositivo.

## J. Compatibilidad
Los módulos previos (participantes, sorteos, ganadores, galería, configuración, pantallas `versorteo.php`/`verfotos.php`) **no se modificaron** y siguen funcionando igual.

## K. Pruebas del módulo (checklist)
> Revisión estática (sin PHP/MySQL local). Verificar en el hosting:
Crear votación → editar y elegir participantes → QR abre `votar.php` correcto → logo del sistema y nombre correctos, sin fotos ni descripciones → buscador y scroll fluidos → votar + confirmación → doble clic no duplica → mismo navegador no puede repetir → otra votación sí permite votar → pausada/finalizada no aceptan votos → contador coincide con BD → ranking y porcentajes correctos → actividad anónima → pantalla externa se actualiza sin recargar → video de fondo no se reinicia → 1920×1080 → ganadores no automáticos, solo por orden del admin → empates detectados → reconexión tras pérdida de red → probar navegación normal, modo privado, borrado de cookies, cambio de navegador/dispositivo (documentando que el control es por dispositivo, no por persona).
