# Módulo de Torneos — Full Dominoes

Sistema de administración para **torneos presenciales**: inscripción de
jugadores, emparejamiento automático por rondas, captura de resultados y
tabla de posiciones en vivo. Es independiente del juego online (no requiere
Google/Facebook ni el motor de jugadas en tiempo real) — está pensado para
que un organizador lo use desde una laptop/tablet en el evento.

## Instalación

```bash
mysql -u root -p domino_2v2 < torneos/sql/schema_torneos.sql
php torneos/sql/crear_admin.php admin_kenny "ClaveSegura123" "Kenny Hernández"
```
Sirve `torneos/public/` bajo tu dominio (ej: `https://tu-dominio.com/torneos/`)
igual que hiciste con `public/` del juego — y crea el symlink de `torneos/api`
hacia `torneos/public/api` como se explica en el README principal.

## URLs del módulo

| Página | Para quién | Requiere login |
|---|---|---|
| `admin_login.html` / `admin_panel.html` | Organizador del evento | Sí (usuario/clave de `crear_admin.php`) |
| `ranking.html` | Público — proyectar en pantalla del evento | No |
| `portal_jugador.html` | Cada jugador (ve su propio ranking) | Sí (cédula + clave, inicial = la cédula) |

## Reglas de emparejamiento implementadas (`includes/PairingService.php`)

1. **Ronda 1 — sorteo puro**: aleatorio total, sin mirar país/departamento/
   ciudad/club. A propósito: el spec pide que ningún criterio favorezca ni
   evite nada en el primer cruce.
2. **Ronda 2 en adelante — sorteo suizo real**: se ordena por la tabla de
   posiciones actual (partidos ganados → diferencia de pintas → pintas a
   favor). Cada mesa se arma tomando al mejor ubicado restante como ancla y
   completándola con los mejores candidatos disponibles que **no hayan
   compartido mesa antes** con nadie ya sentado ahí (evita repetir rivales,
   como en un suizo de ajedrez/dominó real). Si ya no quedan candidatos sin
   repetir — puede pasar en la última ronda de un torneo con pocos
   inscritos — se acepta el mejor disponible aunque repita: evitar una mesa
   incompleta es peor que un rival repetido. En pruebas con 16 jugadores a
   3 rondas, esto redujo los rivales repetidos a casi cero (solo aparecen
   cuando matemáticamente ya no hay alternativa).
3. **Misma zona = mismo club, misma ciudad o mismo departamento** (el país
   NO cuenta, según lo acordado). Si una mesa resultante tiene 2+ entidades
   de la misma zona, el sistema intenta un intercambio "limpio" con mesas
   vecinas (que no cree un conflicto nuevo en la otra mesa).
4. **Deadline de corrección**: si para la ronda `ronda_limite_zonas` (por
   defecto `numero_rondas - 2`, ej. ronda 5 de 7) todavía queda un conflicto
   sin resolver, el sistema FUERZA el mejor intercambio disponible aunque
   no sea perfecto a nivel de ranking — garantizando que la zona quede
   resuelta antes de llegar a la ronda 6.
5. **Fase final (`ronda_inicio_fase_final`)**: desde esa ronda en adelante
   la restricción de zona se desactiva por completo — puede cruzarse gente
   del mismo club/ciudad/departamento sin problema, porque en esa etapa
   importa el nivel deportivo, no la procedencia.

Es una heurística "greedy" (no un solver matemáticamente óptimo de
emparejamiento suizo con manejo de "floats"), pero es determinística,
transparente, y suficiente para el tamaño típico de estos torneos. Cualquier
mesa que quede sin poder resolverse aparece en `advertencias` para que el
organizador la revise a mano.

## Modalidades

- **Individual**: cada jugador se inscribe solo; las "parejas" de mesa las
  arma el emparejamiento cada ronda (no hay pareja fija). El ranking es
  por jugador.
- **Grupal**: se crean equipos fijos (2, 3 o 4 integrantes, según
  `tamano_equipo`) que juegan juntos toda la competencia. El ranking es
  por equipo.
- El tamaño de mesa (2, 3 o 4 "entidades" por mesa) se define **por
  torneo**, al crearlo — "depende del evento que se publique", como pediste.

## Limitación documentada

En modalidad grupal, la "zona" de un equipo se toma del club del primer
integrante registrado (un equipo puede tener jugadores de distintos
clubes/ciudades). Si tu caso de uso necesita una regla distinta (ej. "zona
mixta cuenta como conflicto si CUALQUIER integrante comparte club con
CUALQUIER integrante del otro equipo"), es un cambio acotado en
`PairingService::listarEntidadesConZona()`.
