From 774e1b982d0852b7114b887e3ce4d6743cb106df Mon Sep 17 00:00:00 2001 From: Resistencia Dev Date: Mon, 8 Dec 2025 13:13:33 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20T=C3=ADtulos=20de=20misiones=20y=20fix?= =?UTF-8?q?=20timer=20votaci=C3=B3n=20post-misi=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nuevas funcionalidades: - Títulos y subtítulos en cartas de misión * Título: 'MISIÓN X' (blanco, mayúsculas) * Subtítulo: Nombre de la misión (amarillo dorado) * Objeto missionNames con 5 nombres editables: 1. Sabotaje en el Tren 2. Rescate del Prisionero 3. Destrucción del Puente 4. Robo de Documentos 5. Asalto al Cuartel General Correcciones: - Timer de votación de líder ahora se inicia correctamente después de terminar una misión - Importado GamePhase en server/src/index.ts para comparaciones de fase - Agregada lógica en finish_mission_result para iniciar timer cuando vuelve a VOTE_LEADER - Votación se resuelve automáticamente si no todos votan (mayoría sobre votos emitidos) Archivos modificados: - client/src/components/GameBoard.tsx: Títulos de misiones - server/src/index.ts: Fix timer post-misión --- client/src/components/GameBoard.tsx | 189 +++++++++++++++++----------- server/src/index.ts | 9 +- 2 files changed, 127 insertions(+), 71 deletions(-) diff --git a/client/src/components/GameBoard.tsx b/client/src/components/GameBoard.tsx index 2afb4ac..95263a9 100644 --- a/client/src/components/GameBoard.tsx +++ b/client/src/components/GameBoard.tsx @@ -48,6 +48,22 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB } }, [gameState.phase, gameState.currentLeaderId]); + // Estado para controlar cuándo mostrar el tablero + const [showBoard, setShowBoard] = useState(false); + + // Mostrar tablero solo 5 segundos después de MISSION_RESULT + useEffect(() => { + if (gameState.phase === GamePhase.MISSION_RESULT) { + setShowBoard(true); + const timer = setTimeout(() => { + setShowBoard(false); + }, 5000); // 5 segundos + return () => clearTimeout(timer); + } else { + setShowBoard(false); + } + }, [gameState.phase]); + const currentPlayer = gameState.players.find(p => p.id === currentPlayerId); const isLeader = gameState.currentLeaderId === currentPlayerId; // FIX: Usar currentLeaderId del estado @@ -80,6 +96,15 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB { left: '82%', top: '40%' }, // Misión 5 ]; + // Nombres de las misiones + const missionNames = [ + 'Sabotaje en el Tren', + 'Rescate del Prisionero', + 'Destrucción del Puente', + 'Robo de Documentos', + 'Asalto al Cuartel General' + ]; + // --- UI/Efectos para FASES TEMPRANAS --- const isHost = gameState.hostId === currentPlayerId; @@ -277,82 +302,106 @@ export default function GameBoard({ gameState, currentPlayerId, actions }: GameB
- {/* --- MAPA TÁCTICO (TABLERO) --- */} + {/* --- MAPA TÁCTICO (TABLERO) O CARTA DE MISIÓN --- */}
- Tactical Map + {showBoard ? ( + <> + {/* TABLERO CON TOKENS */} + Tactical Map - {/* TOKENS SOBRE EL MAPA */} - {missionCoords.map((coord, idx) => { - const result = gameState.questResults[idx]; - const isCurrent = gameState.currentRound === idx + 1; + {/* TOKENS SOBRE EL MAPA */} + {missionCoords.map((coord, idx) => { + const result = gameState.questResults[idx]; + const isCurrent = gameState.currentRound === idx + 1; - return ( -
- {/* Marcador de Ronda Actual */} - {isCurrent && ( - - Current Round - - )} + {/* Marcador de Ronda Actual */} + {isCurrent && ( + + Current Round + + )} - {/* Resultado de Misión (Éxito/Fracaso) */} - {result === true && ( - -
- Success -
-
- )} - {result === false && ( - -
- Fail -
-
- )} + {/* Resultado de Misión (Éxito/Fracaso) */} + {result === true && ( + +
+ Success +
+
+ )} + {result === false && ( + +
+ Fail +
+
+ )} +
+ ); + })} + + {/* TRACK DE VOTOS FALLIDOS */} +
+
Votos Rechazados
+
+ {[...Array(5)].map((_, i) => ( +
+ ))} +
- ); - })} - - {/* TRACK DE VOTOS FALLIDOS (Pequeño indicador en la esquina inferior izquierda del mapa) */} -
-
Votos Rechazados
-
- {[...Array(5)].map((_, i) => ( -
- ))} -
-
+ + ) : ( + /* CARTA DE MISIÓN CON TÍTULO */ + <> + {`Mission + {/* Título y subtítulo sobre la carta */} +
+

+ Misión {gameState.currentRound} +

+

+ {missionNames[gameState.currentRound - 1]} +

+
+ + )}
{/* --- ÁREA DE JUEGO (CARTAS Y ACCIONES) --- */} diff --git a/server/src/index.ts b/server/src/index.ts index 97d239b..cc616bf 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -5,6 +5,7 @@ import cors from 'cors'; import dotenv from 'dotenv'; import crypto from 'crypto'; import { Game } from './models/Game'; +import { GamePhase } from '../../shared/types'; dotenv.config(); @@ -251,9 +252,15 @@ io.on('connection', (socket) => { // 5.2 FINALIZAR PANTALLA DE RESULTADO socket.on('finish_mission_result', ({ roomId }) => { const game = games[roomId]; - if (game && game.hostId === socket.id && game.state.phase === 'mission_result') { + if (game && game.hostId === socket.id && game.state.phase === GamePhase.MISSION_RESULT) { game.finishMissionResult(); io.to(roomId).emit('game_state', game.state); + + // Si volvió a vote_leader (nueva ronda), iniciar timer + // TypeScript no detecta que finishMissionResult() cambia la fase, usamos type assertion + if ((game.state.phase as GamePhase) === GamePhase.VOTE_LEADER) { + startLeaderVoteTimer(roomId); + } } });