+
+
+
+
+ {/* --- MAPA TÁCTICO (TABLERO) --- */}
+
+
+
+ {/* 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 && (
+
+
+
+ )}
+
+ {/* Resultado de Misión (Éxito/Fracaso) */}
+ {result === true && (
+
+
+
+ )}
+ {result === false && (
+
+
+
+ )}
+
+ );
+ })}
+
+ {/* TRACK DE VOTOS FALLIDOS (Pequeño indicador en la esquina inferior izquierda del mapa) */}
+
+
Votos Rechazados
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
+
+
+
+ {/* --- ÁREA DE JUEGO (CARTAS Y ACCIONES) --- */}
+
+
+
+ {/* FASE: VOTACIÓN DE LÍDER */}
+ {gameState.phase === 'vote_leader' as any && (
+
+
+
+ Confirmar Líder
+
+
+ ¿Aceptas a {gameState.players.find(p => p.id === gameState.currentLeaderId)?.name} como Líder?
+
+
+ {/* Timer */}
+ {!gameState.leaderVotes?.[currentPlayerId] && (
+
actions.voteLeader(null)} />
+ )}
+
+
+ {gameState.leaderVotes?.[currentPlayerId] === undefined ? (
+
+
+
+
+ ) : (
+
+ VOTO REGISTRADO. ESPERANDO AL RESTO...
+
+ )}
+
+ )}
+
+ {/* FASE: CONSTRUCCIÓN DE EQUIPO */}
+ {gameState.phase === GamePhase.TEAM_BUILDING && (
+
+
+ {isLeader ? 'TU TURNO: ELIGE EQUIPO' : `ESPERANDO AL LÍDER...`}
+
+
+ Se necesitan {currentQuestSize} agentes para esta misión.
+
+
+ {isLeader && (
+
+ )}
+
+ )}
+
+ {/* FASE: VOTACIÓN DE EQUIPO */}
+ {gameState.phase === GamePhase.VOTING_TEAM && (
+
+
+
PROPUESTA DE MISIÓN
+
+ {gameState.proposedTeam.map(id => {
+ const p = gameState.players.find(pl => pl.id === id);
+ return (
+
+ {p?.name}
+
+ );
+ })}
+
+
+
+ {!currentPlayer?.hasVoted ? (
+
+
+
+
+ ) : (
+
+ VOTO REGISTRADO. ESPERANDO AL RESTO...
+
+ )}
+
+ )}
+
+ {/* FASE: MISIÓN */}
+ {gameState.phase === GamePhase.MISSION && (
+
+ {gameState.proposedTeam.includes(currentPlayerId) ? (
+
+
¡ESTÁS EN LA MISIÓN!
+
+
+
+ {/* Solo los malos pueden sabotear */}
+ {currentPlayer?.faction === 'spies' && (
+
+ )}
+
+
+ ) : (
+
+ La misión está en curso...
+ Rezando por el éxito.
+
+ )}
+
+ )}
+
+ {/* FASE: REVELACIÓN DE CARTAS */}
+ {gameState.phase === 'mission_reveal' as any && (
+ isHost && actions.finishMissionReveal()}
+ />
+ )}
+
+ {/* FASE: RESULTADO DE MISIÓN */}
+ {gameState.phase === 'mission_result' as any && (
+ isHost && actions.finishMissionResult()}
+ />
+ )}
+
+
+
+
+ {/* JUGADORES (TIENDA DE CAMPAÑA) */}
+
+
+ {gameState.players.map((player) => {
+ const isSelected = selectedTeam.includes(player.id);
+ const isMe = player.id === currentPlayerId;
+
+ // Avatar logic
+ const avatarSrc = `/assets/images/characters/${player.avatar}`;
+
+ return (
+
isLeader && gameState.phase === GamePhase.TEAM_BUILDING && toggleTeamSelection(player.id)}
+ className={`
+ relative flex flex-col items-center cursor-pointer transition-all duration-300
+ ${isSelected ? 'scale-110' : 'scale-100 opacity-80 hover:opacity-100'}
+ `}
+ >
+ {/* Avatar */}
+
+
+
+ {/* Icono de Líder */}
+ {player.isLeader && (
+
+ L
+
+ )}
+
+
+ {/* Nombre */}
+
+ {player.name}
+
+ );
+ })}
+
+
+
+ {/* HISTÓRICO DE MISIONES (Esquina superior derecha) */}
+ {gameState.missionHistory.length > 0 && (
+
+
Historial
+
+ {gameState.missionHistory.map((mission, idx) => (
+
+ {mission.round}
+
+ ))}
+
+
+ )}
+
+
+ );
+}
+
+
+// Subcomponente para el Timer de Votación
+function VotingTimer({ onTimeout }: { onTimeout: () => void }) {
+ const [timeLeft, setTimeLeft] = useState(10);
+
+ useEffect(() => {
+ if (timeLeft <= 0) {
+ onTimeout();
+ return;
+ }
+ const interval = setInterval(() => setTimeLeft(t => t - 1), 1000);
+ return () => clearInterval(interval);
+ }, [timeLeft, onTimeout]);
+
+ return (
+
+ {timeLeft}
+
+ );
+}
+
+// Componente para revelar cartas una a una
+function MissionReveal({ votes, onComplete }: { votes: boolean[], onComplete: () => void }) {
+ const [revealedCount, setRevealedCount] = useState(0);
+
+ useEffect(() => {
+ if (revealedCount < votes.length) {
+ const timer = setTimeout(() => {
+ setRevealedCount(c => c + 1);
+ }, 2000); // 2 segundos entre carta y carta
+ return () => clearTimeout(timer);
+ } else if (revealedCount === votes.length && votes.length > 0) {
+ // Todas reveladas, esperar 2s más y avanzar
+ const timer = setTimeout(() => {
+ onComplete();
+ }, 2000);
+ return () => clearTimeout(timer);
+ }
+ }, [revealedCount, votes.length, onComplete]);
+
+ return (
+
+
+ Revelando Votos...
+
+
+
+ {votes.slice(0, revealedCount).map((vote, idx) => (
+
+
+
+ ))}
+
+
+
+ {revealedCount} / {votes.length} cartas reveladas
+
+
+ );
+}
+
+// Componente para mostrar el resultado de la misión
+function MissionResult({ gameState, onContinue }: { gameState: any, onContinue: () => void }) {
+ const currentMission = gameState.missionHistory[gameState.missionHistory.length - 1];
+ const isHost = gameState.hostId === gameState.players[0]?.id; // Simplificado
+
+ useEffect(() => {
+ // Auto-avanzar después de 5 segundos
+ const timer = setTimeout(() => {
+ onContinue();
+ }, 5000);
+ return () => clearTimeout(timer);
+ }, [onContinue]);
+
+ if (!currentMission) return null;
+
+ const { isSuccess, successes, fails, team, round } = currentMission;
+
+ return (
+
+ {/* Título */}
+
+
+ {isSuccess ? '✓ MISIÓN EXITOSA' : '✗ MISIÓN FALLIDA'}
+
+
Misión #{round}
+
+
+ {/* Estadísticas */}
+
+
+ {/* Equipo */}
+
+
Equipo de Misión:
+
+ {team.map((playerId: string) => {
+ const player = gameState.players.find((p: any) => p.id === playerId);
+ return (
+
+ {player?.name || 'Desconocido'}
+
+ );
+ })}
+
+
+
+ {/* Mensaje */}
+
+ Continuando en breve...
+
+
+ );
+}
diff --git a/client/src/components/GameBoard_temp.tsx b/client/src/components/GameBoard_temp.tsx
new file mode 100644
index 0000000..3896eae
--- /dev/null
+++ b/client/src/components/GameBoard_temp.tsx
@@ -0,0 +1,544 @@
+import { useState, useEffect } from 'react';
+import { motion, AnimatePresence } from 'framer-motion';
+import Image from 'next/image';
+import { GameState, GamePhase, Player, GAME_CONFIG } from '../../../shared/types';
+
+interface GameBoardProps {
+ gameState: GameState;
+ currentPlayerId: string;
+ actions: any;
+}
+
+export default function GameBoard({ gameState, currentPlayerId, actions }: GameBoardProps) {
+ const [selectedTeam, setSelectedTeam] = useState
([]);
+
+ // Hooks para FASE REVEAL ROLE
+ const [revealCard, setRevealCard] = useState(false);
+
+ // Timer para avanzar automáticamente en REVEAL_ROLE
+ useEffect(() => {
+ if (gameState.phase === 'reveal_role' as any) {
+ const timer = setTimeout(() => {
+ actions.finishReveal();
+ }, 10000);
+ return () => clearTimeout(timer);
+ }
+ }, [gameState.phase, actions]);
+
+ const currentPlayer = gameState.players.find(p => p.id === currentPlayerId);
+ const isLeader = currentPlayer?.isLeader;
+ const config = GAME_CONFIG[gameState.players.length as keyof typeof GAME_CONFIG];
+ const currentQuestSize = config?.quests[gameState.currentRound - 1];
+
+ // Manejar selección de equipo
+ const toggleTeamSelection = (playerId: string) => {
+ if (selectedTeam.includes(playerId)) {
+ setSelectedTeam(selectedTeam.filter(id => id !== playerId));
+ } else {
+ if (selectedTeam.length < currentQuestSize) {
+ setSelectedTeam([...selectedTeam, playerId]);
+ }
+ }
+ };
+
+ // Coordenadas porcentuales de los hexágonos de misión en el mapa
+ const missionCoords = [
+ { left: '12%', top: '55%' }, // Misión 1
+ { left: '28%', top: '15%' }, // Misión 2
+ { left: '52%', top: '25%' }, // Misión 3
+ { left: '42%', top: '70%' }, // Misión 4
+ { left: '82%', top: '40%' }, // Misión 5
+ ];
+
+ // --- UI/Efectos para FASES TEMPRANAS ---
+ const isHost = gameState.hostId === currentPlayerId;
+
+ // FASE INTRO
+ if (gameState.phase === 'intro' as any) {
+ return (
+
+
+
+
+ Guerra Total
+
+
+ {/* Audio Auto-Play */}
+
+ );
+ }
+
+ // FASE REVEAL ROLE NO HOOKS HERE
+
+ if (gameState.phase === 'reveal_role' as any) {
+ // Determinar imagen basada en el rol
+ // Mapeo básico:
+ // Merlin -> good_merlin.png
+ // Percival -> good_percival.png
+ // Servant -> good_soldier_X.png (random)
+ // Assassin -> evil_assassin.png
+ // Morgana -> evil_morgana.png
+ // Mordred -> evil_mordred.png
+ // Oberon -> evil_oberon.png
+ // Minion -> evil_minion_X.png
+
+ let roleImage = '/assets/images/characters/good_soldier_1.png'; // Default
+
+ const role = currentPlayer?.role;
+ if (role === 'merlin') roleImage = '/assets/images/characters/good_merlin.png';
+ else if (role === 'assassin') roleImage = '/assets/images/characters/evil_assassin.png';
+ else if (role === 'percival') roleImage = '/assets/images/characters/good_percival.png';
+ else if (role === 'morgana') roleImage = '/assets/images/characters/evil_morgana.png';
+ else if (role === 'mordred') roleImage = '/assets/images/characters/evil_mordred.png';
+ else if (role === 'oberon') roleImage = '/assets/images/characters/evil_oberon.png';
+ else if (role === 'loyal_servant') {
+ // Random soldier 1-5
+ const idx = (currentPlayerId.charCodeAt(0) % 5) + 1;
+ roleImage = `/assets/images/characters/good_soldier_${idx}.png`;
+ }
+ else if (role === 'minion') {
+ // Random minion 1-3
+ const idx = (currentPlayerId.charCodeAt(0) % 3) + 1;
+ roleImage = `/assets/images/characters/evil_minion_${idx}.png`;
+ }
+
+ return (
+
+ {/* FONDO (Mismo que Roll Call) */}
+
+
+
+
+ Tu Identidad Secreta
+
+
+
+ Desliza hacia arriba para revelar
+
+
+
+ {/* Carta Revelada (Fondo) */}
+
+
+
+ {role?.replace('_', ' ')}
+
+
+
+ {/* Reverso de Carta (Draggable) */}
+
{
+ // Reducir umbral a -50 para facilitar
+ if (info.offset.y < -50) {
+ setRevealCard(true);
+ }
+ }}
+ whileHover={{ scale: 1.02 }}
+ whileTap={{ scale: 0.98, cursor: 'grabbing' }}
+ animate={revealCard ? { y: -1000, opacity: 0 } : { y: 0, opacity: 1 }}
+ className="absolute inset-0 w-full h-full rounded-xl overflow-hidden shadow-2xl z-20 cursor-grab active:cursor-grabbing hover:ring-2 hover:ring-white/50 transition-all"
+ >
+
+
+
+
+
+
+
+ );
+ }
+
+ // FASE ROLL CALL
+ if (gameState.phase === 'roll_call' as any) {
+ return (
+
+
+
+
+
+ Pasando Lista...
+
+
+ {isHost && (
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ {/* --- MAPA TÁCTICO (TABLERO) --- */}
+
+
+
+ {/* 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 && (
+
+
+
+ )}
+
+ {/* Resultado de Misión (Éxito/Fracaso) */}
+ {result === true && (
+
+
+
+ )}
+ {result === false && (
+
+
+
+ )}
+
+ );
+ })}
+
+ {/* TRACK DE VOTOS FALLIDOS (Pequeño indicador en la esquina inferior izquierda del mapa) */}
+
+
Votos Rechazados
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
+
+
+
+ {/* --- ÁREA DE JUEGO (CARTAS Y ACCIONES) --- */}
+
+
+
+ {/* FASE: VOTACIÓN DE LÍDER */}
+ {gameState.phase === 'vote_leader' as any && (
+
+
+
+ Confirmar Líder
+
+
+ ¿Aceptas a {gameState.players.find(p => p.id === gameState.currentLeaderId)?.name} como Líder?
+
+
+ {/* Timer */}
+ {!gameState.leaderVotes?.[currentPlayerId] && (
+
actions.voteLeader(null)} />
+ )}
+
+
+ {gameState.leaderVotes?.[currentPlayerId] === undefined ? (
+
+
+
+
+ ) : (
+
+ VOTO REGISTRADO. ESPERANDO AL RESTO...
+
+ )}
+
+ )}
+
+ {/* FASE: CONSTRUCCIÓN DE EQUIPO */}
+ {gameState.phase === GamePhase.TEAM_BUILDING && (
+
+
+ {isLeader ? 'TU TURNO: ELIGE EQUIPO' : `ESPERANDO AL LÍDER...`}
+
+
+ Se necesitan {currentQuestSize} agentes para esta misión.
+
+
+ {isLeader && (
+
+ )}
+
+ )}
+
+ {/* FASE: VOTACIÓN DE EQUIPO */}
+ {gameState.phase === GamePhase.VOTING_TEAM && (
+
+
+
PROPUESTA DE MISIÓN
+
+ {gameState.proposedTeam.map(id => {
+ const p = gameState.players.find(pl => pl.id === id);
+ return (
+
+ {p?.name}
+
+ );
+ })}
+
+
+
+ {!currentPlayer?.hasVoted ? (
+
+
+
+
+ ) : (
+
+ VOTO REGISTRADO. ESPERANDO AL RESTO...
+
+ )}
+
+ )}
+
+ {/* FASE: MISIÓN */}
+ {gameState.phase === GamePhase.MISSION && (
+
+ {gameState.proposedTeam.includes(currentPlayerId) ? (
+
+
¡ESTÁS EN LA MISIÓN!
+
+
+
+ {/* Solo los malos pueden sabotear */}
+ {currentPlayer?.faction === 'spies' && (
+
+ )}
+
+
+ ) : (
+
+ La misión está en curso...
+ Rezando por el éxito.
+
+ )}
+
+ )}
+
+ {/* FASE: REVELACIÓN DE CARTAS */}
+ {gameState.phase === 'mission_reveal' as any && (
+ isHost && actions.finishMissionReveal()}
+ />
+ )}
+
+ {/* FASE: RESULTADO DE MISIÓN */}
+ {gameState.phase === 'mission_result' as any && (
+ isHost && actions.finishMissionResult()}
+ />
+ )}
+
+
+
+
+ {/* JUGADORES (TIENDA DE CAMPAÑA) */}
+
+
+ {gameState.players.map((player) => {
+ const isSelected = selectedTeam.includes(player.id);
+ const isMe = player.id === currentPlayerId;
+
+ // Avatar logic
+ const avatarSrc = `/assets/images/characters/${player.avatar}`;
+
+ return (
+
isLeader && gameState.phase === GamePhase.TEAM_BUILDING && toggleTeamSelection(player.id)}
+ className={`
+ relative flex flex-col items-center cursor-pointer transition-all duration-300
+ ${isSelected ? 'scale-110' : 'scale-100 opacity-80 hover:opacity-100'}
+ `}
+ >
+ {/* Avatar */}
+
+
+
+ {/* Icono de Líder */}
+ {player.isLeader && (
+
+ L
+
+ )}
+
+
+ {/* Nombre */}
+
+ {player.name}
+
+ );
+ })}
+
+
+
+ {/* HISTÓRICO DE MISIONES (Esquina superior derecha) */}
+ {gameState.missionHistory.length > 0 && (
+