feat(phase2): Add achievements and replay systems

Achievement System:
- Add Achievement model with condition types (streak, steal, specialist, etc.)
- Add AchievementManager service for tracking and awarding achievements
- Add Pydantic schemas for achievements (AchievementResponse, PlayerStats, etc.)
- Seed 18 achievements from design doc
- Add GET /api/game/achievements endpoint

Replay System:
- Add ReplayManager service for saving/loading game replays
- Add GET /api/replay/{code} and /api/replay/session/{id} endpoints
- Format replays for frontend consumption

Phase 2 tasks completed:
- F2.1: Achievement model and migration
- F2.2: Pydantic schemas
- F2.3: AchievementManager service
- F2.4: ReplayManager service
- F2.5: API endpoints
- F2.6: Seed 18 achievements data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-26 08:24:02 +00:00
parent b3fab9f8df
commit 27ac4cb0cf
12 changed files with 804 additions and 131 deletions

View File

@@ -0,0 +1,176 @@
import asyncio
import sys
sys.path.insert(0, '/root/WebTriviasMulti/backend')
from sqlalchemy import select
from app.models.base import get_async_session
from app.models.achievement import Achievement
ACHIEVEMENTS = [
{
"name": "Primera Victoria",
"description": "Gana tu primera partida",
"icon": "🏆",
"condition_type": "first_win",
"condition_value": 1,
"category_id": None
},
{
"name": "Racha de 3",
"description": "Responde 3 correctas seguidas",
"icon": "🔥",
"condition_type": "streak",
"condition_value": 3,
"category_id": None
},
{
"name": "Racha de 5",
"description": "Responde 5 correctas seguidas",
"icon": "🔥🔥",
"condition_type": "streak",
"condition_value": 5,
"category_id": None
},
{
"name": "Ladrón Novato",
"description": "Primer robo exitoso",
"icon": "🦝",
"condition_type": "steal_success",
"condition_value": 1,
"category_id": None
},
{
"name": "Ladrón Maestro",
"description": "5 robos exitosos en una partida",
"icon": "🦝👑",
"condition_type": "steal_success",
"condition_value": 5,
"category_id": None
},
{
"name": "Especialista Nintendo",
"description": "10 correctas en Nintendo",
"icon": "🍄",
"condition_type": "category_specialist",
"condition_value": 10,
"category_id": 1
},
{
"name": "Especialista Xbox",
"description": "10 correctas en Xbox",
"icon": "🎮",
"condition_type": "category_specialist",
"condition_value": 10,
"category_id": 2
},
{
"name": "Especialista PlayStation",
"description": "10 correctas en PlayStation",
"icon": "🎯",
"condition_type": "category_specialist",
"condition_value": 10,
"category_id": 3
},
{
"name": "Especialista Anime",
"description": "10 correctas en Anime",
"icon": "⛩️",
"condition_type": "category_specialist",
"condition_value": 10,
"category_id": 4
},
{
"name": "Especialista Música",
"description": "10 correctas en Música",
"icon": "🎵",
"condition_type": "category_specialist",
"condition_value": 10,
"category_id": 5
},
{
"name": "Especialista Películas",
"description": "10 correctas en Películas",
"icon": "🎬",
"condition_type": "category_specialist",
"condition_value": 10,
"category_id": 6
},
{
"name": "Especialista Libros",
"description": "10 correctas en Libros",
"icon": "📚",
"condition_type": "category_specialist",
"condition_value": 10,
"category_id": 7
},
{
"name": "Especialista Historia",
"description": "10 correctas en Historia-Cultura",
"icon": "🏛️",
"condition_type": "category_specialist",
"condition_value": 10,
"category_id": 8
},
{
"name": "Invicto",
"description": "Gana sin fallar ninguna pregunta",
"icon": "",
"condition_type": "perfect_game",
"condition_value": 1,
"category_id": None
},
{
"name": "Velocista",
"description": "Responde correctamente en menos de 3 segundos",
"icon": "",
"condition_type": "fast_answer",
"condition_value": 3,
"category_id": None
},
{
"name": "Comeback",
"description": "Gana estando 500+ puntos abajo",
"icon": "🔄",
"condition_type": "comeback",
"condition_value": 500,
"category_id": None
},
{
"name": "Dominio Total",
"description": "Responde las 5 preguntas de una categoría correctamente",
"icon": "👑",
"condition_type": "category_sweep",
"condition_value": 5,
"category_id": None
},
{
"name": "Arriesgado",
"description": "Responde correctamente 3 preguntas de 500 pts",
"icon": "🎰",
"condition_type": "high_stakes",
"condition_value": 3,
"category_id": None
},
]
async def seed_achievements():
AsyncSessionLocal = get_async_session()
async with AsyncSessionLocal() as session:
# Verificar si ya existen
result = await session.execute(select(Achievement))
if result.scalars().first():
print("Achievements ya existen, saltando seed...")
return
# Insertar achievements
for data in ACHIEVEMENTS:
achievement = Achievement(**data)
session.add(achievement)
await session.commit()
print(f"Insertados {len(ACHIEVEMENTS)} achievements")
if __name__ == "__main__":
asyncio.run(seed_achievements())