# Farolero — SDD: Multidispositivo completo + Impostor seguro

## 1. Explore

### Hallazgos

**Bug 1: El host funciona como juego en un solo móvil**

- En `pantalla_crear_partida.dart` → `_iniciarPartidaMulti()`, al pulsar "Iniciar":
  1. Crea la partida localmente ✅
  2. Envía palabras a clientes via Nearby ✅
  3. Navega a `PantallaVerPalabra` ❌
- `PantallaVerPalabra` está diseñada para juego en un solo móvil (muestra TODOS los jugadores en lista)
- El host no necesita ver palabras — es el gestor, no un jugador

**Bug 2: Clientes no se enteran de que el juego ha started**

- `ServicioNearby` procesa `partidaInicio` y actualiza `_palabraRecibida` / `_soyImpostor` ✅
- Llama a `notifyListeners()` ✅
- Pero `PantallaUnirse` → `_buildPantallaEspera` no escucha cambios de `ServicioNearby`
- Se queda infinamente en "Esperando al host..." aunque la partida ya haya started

**Bug 3: Random de impostores predecible**

- `EstadoJuego.crearPartida()` usa `Random()` con seed del sistema (determinista)
- `indices.shuffle(rng)` mezcla un array [0,1,2,...,n-1] — la mezcla depende del estado inicial
- Si un atacante conoce el número de jugadores y el momento de creación, puede reconstruir la seed
- Solución: usar `Random.secure()` (CSPRNG) o al menos `Random.fromSecureRandom()`

### Restricciones

- Nearby Connections tiene límite de payloads (4KB por mensaje)
- Google Nearby Connections requiere Bluetooth + Location permissions en Android/iOS
- La palabra secreta NO debe enviarse en texto plano a la red ( Nearby es inseguro en teoría — cualquier dispositivo puede sniffear)
- Compatibilidad hacia atrás: modo un solo móvil no debe romperse

---

## 2. Propose

### Cambio 1: Pantalla Gestor Host (multidispositivo)

El host en modo multi-device necesita una pantalla que:
- Muestre la lista de jugadores conectados y sus estados (listo para debate/votación)
- Permita avanzar de fase (debate → votación → resultado)
- NO muestre palabras a nadie
- Gestione la coordinación de la partida completa

**Pantallas afectadas/creadas:**
- Nueva: `pantalla_gestor_host.dart`
- Modificar: `pantalla_crear_partida.dart` (onIniciar → navegar a gestor)
- Modificar: `pantalla_lobby_host.dart` (el QR se queda ahí)

### Cambio 2: Reacción de clientes al inicio de partida

Los clientes deben:
- Escuchar cambios en `ServicioNearby` (registrar listener)
- Cuando llega `partidaInicio`, navegar automáticamente a la pantalla de ver su palabra
- Crear pantalla dedicada para cliente: `pantalla_palabra_cliente.dart`

**Pantallas afectadas/creadas:**
- Nueva: `pantalla_palabra_cliente.dart`
- Nueva: `pantalla_debate_cliente.dart`
- Nueva: `pantalla_votacion_cliente.dart`
- Modificar: `pantalla_unirse.dart` (registrar listener y reaccionar)

### Cambio 3: Impostor con Random seguro

Reemplazar `Random()` por `Random.secure()` en `EstadoJuego.crearPartida()`.

**Archivos afectados:**
- `lib/estado/estado_juego.dart`

---

## 3. Spec

### 3.1 PantallaGestorHost

**Props:**
```dart
class PantallaGestorHost extends StatelessWidget {
  final VoidCallback onPartidaFin;
}
```

**Responsabilidades:**
- Escuchar cambios de `EstadoJuego` (fase actual)
- Escuchar cambios de `ServicioNearby` (desconexiones)
- Botones para avanzar de fase
- Mostrar estado de cada jugador (en debate, votando, eliminado)
- NO mostrar palabras a nadie

**Estados:**
- `debate`: muestra timer si hay tiempo configurado, botón "Iniciar votación"
- `votacion`: muestra quién ha votado, botón "Ver resultados" cuando todos voted
- `resultado`: muestra quién fue eliminado y si era impostor
- `adivinanza`: el impostor剩余 intenta adivinar
- `finPartida`: muestra ganador

### 3.2 PantallaPalabraCliente

**Props:**
```dart
class PantallaPalabraCliente extends StatelessWidget {
  final String palabra;
  final bool esImpostor;
  final String? pistaCategoria;
  final VoidCallback onVisto;
}
```

**Responsabilidades:**
- Mostrar la palabra solo al jugador correspondiente
- Si es impostor: mostrar pista de categoría (si está activa)
- Mantener pulsado para revelar (mismo UX que `_PantallaRevelarPalabra`)
- Al pulsar "Ya la he visto" → enviar `listo` al host via Nearby
- Esperar al host que inicie el debate

### 3.3 Sincronización de fases

**Protocolo de mensajes Via Nearby:**

```
HOST → CLIENTE:
  partidaInicio    → { palabra, esImpostor, categoria, numJugadores }
  fase             → { fase: "debate"|"votacion"|"resultado"|"adivinanza"|"fin" }
  votacionResultado → { eliminadoId, eliminadoNombre, eraImpostor, votos }
  impostorAdivina   → { acierto: bool }
  partidaFin        → { ganador: "jugadores"|"impostores" }

CLIENTE → HOST:
  listo             → {} (el cliente ha visto su palabra y está listo)
  voto              → { votoporId }
```

### 3.4 Random seguro para impostores

```dart
// ANTES (predecible):
final rng = Random();
final indices = List.generate(jugadores.length, (i) => i);
indices.shuffle(rng);

// DESPUÉS (seguro):
final rng = Random.secure();
final impostoresElegidos = <int>{};
while (impostoresElegidos.length < numImpostores) {
  impostoresElegidos.add(rng.nextInt(jugadores.length));
}
```

---

## 4. Tasks

### Epic 1: Impostor seguro
- [ ] **Task 1.1**: Cambiar `Random()` por `Random.secure()` en `EstadoJuego.crearPartida()`

### Epic 2: PantallaGestorHost
- [ ] **Task 2.1**: Crear `pantalla_gestor_host.dart` con estructura de fases
- [ ] **Task 2.2**: Implementar transición `lobby → gestor` en `pantalla_crear_partida.dart`
- [ ] **Task 2.3**: Implementar envío de cambio de fase a clientes
- [ ] **Task 2.4**: Gestionar desconexiones de jugadores durante la partida

### Epic 3: Cliente recibe inicio de partida
- [ ] **Task 3.1**: Registrar listener en `PantallaUnirse` para `partidaInicio`
- [ ] **Task 3.2**: Crear `pantalla_palabra_cliente.dart` (similar a `_PantallaRevelarPalabra` pero StatelessWidget)
- [ ] **Task 3.3**: Enviar mensaje `listo` al host cuando el cliente confirma que ha visto la palabra

### Epic 4: Cliente en fases de juego
- [ ] **Task 4.1**: Crear `pantalla_debate_cliente.dart` (muestra countdown, botón para solicitar votación)
- [ ] **Task 4.2**: Crear `pantalla_votacion_cliente.dart` (votar desde el móvil del cliente)
- [ ] **Task 4.3**: Cliente reacciona a `fase: votacion` y muestra pantalla de voto
- [ ] **Task 4.4**: Cliente reacciona a `fase: resultado` y muestra quién fue eliminado
- [ ] **Task 4.5**: Si el cliente es impostor剩余, mostrar pantalla de adivinanza

### Epic 5: Testing
- [ ] **Task 5.1**: Probar flujo completo multi-device (3+ jugadores)
- [ ] **Task 5.2**: Probar desconexión de un jugador durante la partida
- [ ] **Task 5.3**: Probar que en modo un solo móvil no se rompe nada

---

## 5. Apply

*(Ejecución por agente OpenCode)*

---

## 6. Verify

- [ ] Flujo multi-device: host inicia → clientes ven sus palabras → debate → votan → resultado
- [ ] Modo un solo móvil sigue funcionando exactamente igual que antes
- [ ] Impostores se seleccionan con Random.secure() — no reproducibles
- [ ] Sin regresiones en localized strings
