Files
Trivy/backend/app/services/question_service.py
consultoria-as ab201e113a feat: 5 categorías rotativas por partida + pool de 200 preguntas + mejoras UI
Cambios principales:
- Tablero ahora muestra 5 categorías aleatorias (de 8 disponibles)
- Pool de 200 preguntas (8 cats × 5 diffs × 5 opciones)
- Preguntas rotan aleatoriamente entre partidas
- Diseño mejorado estilo Jeopardy con efectos visuales
- Socket singleton para conexión persistente
- Nuevos sonidos: game_start, player_join, question_reveal, hover, countdown
- Control de volumen vertical
- Barra de progreso del timer en modal de preguntas
- Animaciones mejoradas con Framer Motion

Backend:
- question_service: selección aleatoria de 5 categorías
- room_manager: fix retorno de create_room
- game_events: carga board desde DB, await en enter_room

Frontend:
- Game.tsx: tablero dinámico, efectos hover, mejor scoreboard
- useSocket: singleton service, eventos con sonidos
- SoundControl: slider vertical
- soundStore: 5 nuevos efectos de sonido

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-26 23:44:55 +00:00

174 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Optional, List, Dict
from datetime import date
import random
from sqlalchemy import select, and_, func
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.question import Question
from app.models.category import Category
# Number of categories per game
CATEGORIES_PER_GAME = 5
class QuestionService:
async def get_daily_questions(
self,
db: AsyncSession,
target_date: Optional[date] = None
) -> Dict[int, List[dict]]:
"""
Obtiene preguntas del día organizadas por categoría.
Args:
db: Sesión de BD
target_date: Fecha (default: hoy)
Returns:
Dict[category_id, List[question_dict]] para el tablero
"""
if target_date is None:
target_date = date.today()
# Buscar preguntas aprobadas para la fecha
query = select(Question).where(
and_(
Question.date_active == target_date,
Question.status == "approved"
)
).order_by(Question.category_id, Question.difficulty)
result = await db.execute(query)
questions = result.scalars().all()
# Organizar por categoría
board: Dict[int, List[dict]] = {}
for q in questions:
if q.category_id not in board:
board[q.category_id] = []
board[q.category_id].append({
"id": q.id,
"category_id": q.category_id,
"question_text": q.question_text,
"correct_answer": q.correct_answer,
"alt_answers": q.alt_answers or [],
"difficulty": q.difficulty,
"points": q.points,
"time_seconds": q.time_seconds,
"fun_fact": q.fun_fact,
"answered": False,
"selected": False
})
return board
async def get_board_for_game(
self,
db: AsyncSession,
target_date: Optional[date] = None
) -> Dict[str, List[dict]]:
"""
Genera el tablero 5×5 para el juego.
Selecciona 5 categorías aleatorias y 1 pregunta por dificultad.
Returns:
Dict con category_id como string (para JSON) -> lista de preguntas
"""
full_board = await self.get_daily_questions(db, target_date)
if not full_board:
return {}
# Get available category IDs that have questions
available_categories = list(full_board.keys())
# Select random categories (up to CATEGORIES_PER_GAME)
num_categories = min(CATEGORIES_PER_GAME, len(available_categories))
selected_categories = random.sample(available_categories, num_categories)
# Build the game board with selected categories
game_board: Dict[str, List[dict]] = {}
for cat_id in selected_categories:
questions_by_difficulty: Dict[int, List[dict]] = {}
# Group questions by difficulty
for q in full_board[cat_id]:
diff = q["difficulty"]
if diff not in questions_by_difficulty:
questions_by_difficulty[diff] = []
questions_by_difficulty[diff].append(q)
# Select one random question per difficulty
selected_questions = []
for difficulty in range(1, 6): # 1-5
if difficulty in questions_by_difficulty:
questions = questions_by_difficulty[difficulty]
selected_q = random.choice(questions)
selected_questions.append(selected_q)
if selected_questions:
game_board[str(cat_id)] = selected_questions
return game_board
async def get_question_by_id(
self,
db: AsyncSession,
question_id: int
) -> Optional[Question]:
"""Obtiene una pregunta por ID."""
result = await db.execute(
select(Question).where(Question.id == question_id)
)
return result.scalar_one_or_none()
async def mark_question_used(
self,
db: AsyncSession,
question_id: int
) -> bool:
"""Marca una pregunta como usada."""
question = await self.get_question_by_id(db, question_id)
if question:
question.status = "used"
await db.commit()
return True
return False
async def get_categories_with_questions(
self,
db: AsyncSession,
target_date: Optional[date] = None
) -> List[dict]:
"""
Obtiene categorías que tienen preguntas para la fecha.
Útil para mostrar solo categorías disponibles.
"""
board = await self.get_daily_questions(db, target_date)
# Obtener info de categorías
cat_ids = list(board.keys())
if not cat_ids:
return []
result = await db.execute(
select(Category).where(Category.id.in_(cat_ids))
)
categories = result.scalars().all()
return [
{
"id": c.id,
"name": c.name,
"icon": c.icon,
"color": c.color,
"question_count": len(board.get(c.id, []))
}
for c in categories
]
# Singleton
question_service = QuestionService()