Files
Trivy/backend/app/services/question_service.py
consultoria-as be5b1775a0 feat: sistema de 2 rondas con puntos dobles
Ronda 1: 5 categorías con puntos normales (100-500)
Ronda 2: 5 categorías diferentes con puntos x2 (200-1000)

Backend:
- question_service: soporta excluir categorías y multiplicador de puntos
- game_manager: trackea current_round, start_round_2() carga nuevo tablero
- game_events: emite round_started al completar ronda 1

Frontend:
- useSocket: escucha evento round_started
- Game.tsx: muestra indicador de ronda actual
- types: GameRoom incluye current_round

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 02:28:28 +00:00

194 lines
6.1 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,
exclude_categories: Optional[List[int]] = None,
point_multiplier: int = 1
) -> Dict[str, List[dict]]:
"""
Genera el tablero 5×5 para el juego.
Selecciona 5 categorías aleatorias y 1 pregunta por dificultad.
Args:
db: Database session
target_date: Date for questions (default: today)
exclude_categories: Category IDs to exclude (for round 2)
point_multiplier: Multiply points by this value (for round 2)
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())
# Exclude categories from previous round
if exclude_categories:
available_categories = [
c for c in available_categories if c not in exclude_categories
]
if not available_categories:
return {}
# 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).copy()
# Apply point multiplier for round 2
if point_multiplier > 1:
selected_q["points"] = selected_q["points"] * point_multiplier
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()