- README.md: descripción general, stack, instalación rápida - docs/API.md: referencia completa de API REST y WebSocket - docs/ARCHITECTURE.md: arquitectura del sistema con diagramas - docs/INSTALLATION.md: guía detallada de instalación - backend/.env.example: plantilla de configuración Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
375 lines
16 KiB
Markdown
375 lines
16 KiB
Markdown
# Arquitectura de Trivy
|
|
|
|
## Visión General
|
|
|
|
Trivy utiliza una arquitectura de microservicios con comunicación en tiempo real mediante WebSockets.
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────┐
|
|
│ CLIENTES │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
│ │ Browser │ │ Browser │ │ Browser │ │ Browser │ │
|
|
│ │ React App│ │ React App│ │ React App│ │ React App│ │
|
|
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
|
|
│ │ │ │ │ │
|
|
│ └─────────────┴──────┬──────┴─────────────┘ │
|
|
│ │ │
|
|
│ WebSocket/HTTP │
|
|
└────────────────────────────┼─────────────────────────────────────────┘
|
|
│
|
|
┌────────────────────────────┼─────────────────────────────────────────┐
|
|
│ BACKEND │
|
|
│ │ │
|
|
│ ┌─────────────────────────▼─────────────────────────────┐ │
|
|
│ │ FastAPI + Socket.IO │ │
|
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
|
│ │ │ REST API │ │ WebSocket │ │ ASGI │ │ │
|
|
│ │ │ Endpoints │ │ Events │ │ Middleware │ │ │
|
|
│ │ └──────┬──────┘ └──────┬──────┘ └─────────────┘ │ │
|
|
│ └─────────┼────────────────┼────────────────────────────┘ │
|
|
│ │ │ │
|
|
│ ┌─────────▼────────────────▼────────────────────────────┐ │
|
|
│ │ SERVICES │ │
|
|
│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │
|
|
│ │ │ Room │ │ Game │ │ AI │ │ │
|
|
│ │ │ Manager │ │ Manager │ │ Validator │ │ │
|
|
│ │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │ │
|
|
│ └────────┼───────────────┼───────────────┼──────────────┘ │
|
|
│ │ │ │ │
|
|
│ ┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐ │
|
|
│ │ Redis │ │ PostgreSQL│ │ Claude │ │
|
|
│ │ (State) │ │ (Data) │ │ API │ │
|
|
│ └───────────┘ └───────────┘ └───────────┘ │
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Componentes
|
|
|
|
### Frontend (React + TypeScript)
|
|
|
|
```
|
|
frontend/src/
|
|
├── components/ # Componentes reutilizables
|
|
│ ├── chat/ # EmojiReactions, ReactionOverlay, TeamChat
|
|
│ ├── game/ # Componentes del tablero
|
|
│ ├── lobby/ # Sala de espera
|
|
│ └── ui/ # SoundControl, botones, inputs
|
|
├── hooks/ # Custom hooks
|
|
│ ├── useSocket.ts # Gestión de WebSocket
|
|
│ └── useSound.ts # Gestión de audio
|
|
├── pages/ # Rutas principales
|
|
│ ├── Home.tsx # Crear/unir sala
|
|
│ ├── Lobby.tsx # Sala de espera
|
|
│ ├── Game.tsx # Tablero de juego
|
|
│ ├── Results.tsx # Resultados finales
|
|
│ └── admin/ # Panel de administración
|
|
├── services/
|
|
│ └── socket.ts # Singleton de Socket.IO
|
|
├── stores/ # Estado global (Zustand)
|
|
│ ├── gameStore.ts # Estado del juego
|
|
│ ├── themeStore.ts # Tema visual
|
|
│ └── soundStore.ts # Configuración de audio
|
|
├── themes/ # Definiciones de temas
|
|
└── types/ # Tipos TypeScript
|
|
```
|
|
|
|
### Backend (FastAPI + Python)
|
|
|
|
```
|
|
backend/app/
|
|
├── api/ # Endpoints REST
|
|
│ ├── admin.py # CRUD de preguntas/categorías
|
|
│ ├── auth.py # Autenticación admin
|
|
│ └── game.py # Datos públicos del juego
|
|
├── models/ # Modelos SQLAlchemy
|
|
│ ├── question.py # Preguntas
|
|
│ ├── category.py # Categorías
|
|
│ ├── admin.py # Administradores
|
|
│ └── game_session.py # Sesiones de juego
|
|
├── schemas/ # Schemas Pydantic
|
|
├── services/ # Lógica de negocio
|
|
│ ├── room_manager.py # Gestión de salas (Redis)
|
|
│ ├── game_manager.py # Lógica del juego
|
|
│ ├── question_service.py # Carga de preguntas
|
|
│ └── ai_validator.py # Validación con Claude
|
|
├── sockets/ # Eventos WebSocket
|
|
│ └── game_events.py # Todos los eventos del juego
|
|
├── config.py # Configuración
|
|
└── main.py # Punto de entrada ASGI
|
|
```
|
|
|
|
## Flujo de Datos
|
|
|
|
### 1. Creación de Sala
|
|
|
|
```
|
|
Cliente Backend Redis
|
|
│ │ │
|
|
│──── create_room ────────>│ │
|
|
│ │ │
|
|
│ │──── SETEX room:CODE ────>│
|
|
│ │ │
|
|
│ │<─── OK ─────────────────│
|
|
│ │ │
|
|
│<─── room_created ────────│ │
|
|
│ │ │
|
|
```
|
|
|
|
### 2. Unirse a Sala
|
|
|
|
```
|
|
Cliente Backend Redis
|
|
│ │ │
|
|
│──── join_room ──────────>│ │
|
|
│ │ │
|
|
│ │──── GET room:CODE ──────>│
|
|
│ │<─── room_data ──────────│
|
|
│ │ │
|
|
│ │──── SETEX (updated) ────>│
|
|
│ │ │
|
|
│<─── player_joined ───────│ │
|
|
│ │ │
|
|
│ │──── emit to room ───────>│ (otros clientes)
|
|
```
|
|
|
|
### 3. Inicio del Juego
|
|
|
|
```
|
|
Cliente Backend PostgreSQL
|
|
│ │ │
|
|
│──── start_game ─────────>│ │
|
|
│ │ │
|
|
│ │──── SELECT questions ───>│
|
|
│ │ (5 random cats) │
|
|
│ │<─── questions ──────────│
|
|
│ │ │
|
|
│ │──── SELECT 1 per diff ──>│
|
|
│ │<─── board data ─────────│
|
|
│ │ │
|
|
│<─── game_started ────────│ │
|
|
│ (with board) │ │
|
|
```
|
|
|
|
### 4. Responder Pregunta
|
|
|
|
```
|
|
Cliente Backend Claude API
|
|
│ │ │
|
|
│──── submit_answer ──────>│ │
|
|
│ │ │
|
|
│ │──── validate_answer ────>│
|
|
│ │ (question, answer) │
|
|
│ │ │
|
|
│ │<─── {valid, reason} ────│
|
|
│ │ │
|
|
│<─── answer_result ───────│ │
|
|
│ │ │
|
|
```
|
|
|
|
## Estado del Juego
|
|
|
|
### Redis (Estado en Tiempo Real)
|
|
|
|
```json
|
|
{
|
|
"room:ABC123": {
|
|
"code": "ABC123",
|
|
"status": "playing",
|
|
"host": "Player1",
|
|
"teams": {
|
|
"A": [{"name": "Player1", "team": "A", "socket_id": "..."}],
|
|
"B": [{"name": "Player2", "team": "B", "socket_id": "..."}]
|
|
},
|
|
"current_team": "A",
|
|
"current_player_index": {"A": 0, "B": 0},
|
|
"current_question": null,
|
|
"can_steal": false,
|
|
"scores": {"A": 500, "B": 300},
|
|
"board": {
|
|
"1": [/* preguntas categoría 1 */],
|
|
"3": [/* preguntas categoría 3 */]
|
|
}
|
|
},
|
|
"player:socket_id": {
|
|
"room": "ABC123",
|
|
"name": "Player1",
|
|
"team": "A"
|
|
}
|
|
}
|
|
```
|
|
|
|
### PostgreSQL (Datos Persistentes)
|
|
|
|
```sql
|
|
-- Categorías
|
|
categories (id, name, icon, color)
|
|
|
|
-- Preguntas (pool de 200)
|
|
questions (
|
|
id, category_id, question_text, correct_answer,
|
|
alt_answers[], difficulty, points, time_seconds,
|
|
date_active, status, fun_fact
|
|
)
|
|
|
|
-- Sesiones de juego (historial)
|
|
game_sessions (
|
|
id, room_code, status,
|
|
team_a_score, team_b_score,
|
|
questions_used[], created_at, finished_at
|
|
)
|
|
|
|
-- Eventos de juego (analytics)
|
|
game_events (
|
|
id, session_id, event_type, player_name,
|
|
question_id, data, created_at
|
|
)
|
|
|
|
-- Administradores
|
|
admins (id, username, password_hash)
|
|
```
|
|
|
|
## Validación de Respuestas con IA
|
|
|
|
El sistema usa Claude para validación flexible de respuestas:
|
|
|
|
```python
|
|
# ai_validator.py
|
|
async def validate_answer(
|
|
question: str,
|
|
correct_answer: str,
|
|
alt_answers: list,
|
|
player_answer: str
|
|
) -> dict:
|
|
prompt = f"""
|
|
Pregunta: {question}
|
|
Respuesta correcta: {correct_answer}
|
|
Respuestas alternativas: {alt_answers}
|
|
Respuesta del jugador: {player_answer}
|
|
|
|
¿La respuesta del jugador es correcta?
|
|
Considera sinónimos, abreviaciones, errores menores de ortografía.
|
|
|
|
Responde en JSON: {{"valid": bool, "reason": "explicación"}}
|
|
"""
|
|
|
|
response = await anthropic.messages.create(
|
|
model="claude-3-haiku-20240307",
|
|
messages=[{"role": "user", "content": prompt}]
|
|
)
|
|
|
|
return json.loads(response.content[0].text)
|
|
```
|
|
|
|
## Selección de Preguntas
|
|
|
|
Cada partida selecciona aleatoriamente:
|
|
|
|
1. **5 categorías** de las 8 disponibles
|
|
2. **1 pregunta por dificultad** de las 5 opciones disponibles
|
|
|
|
```python
|
|
# question_service.py
|
|
async def get_board_for_game(db, target_date):
|
|
# 1. Obtener todas las preguntas del día
|
|
full_board = await get_daily_questions(db, target_date)
|
|
|
|
# 2. Seleccionar 5 categorías aleatorias
|
|
selected_cats = random.sample(list(full_board.keys()), 5)
|
|
|
|
# 3. Para cada categoría, seleccionar 1 pregunta por dificultad
|
|
game_board = {}
|
|
for cat_id in selected_cats:
|
|
questions_by_diff = group_by_difficulty(full_board[cat_id])
|
|
selected = []
|
|
for diff in range(1, 6):
|
|
if diff in questions_by_diff:
|
|
selected.append(random.choice(questions_by_diff[diff]))
|
|
game_board[cat_id] = selected
|
|
|
|
return game_board
|
|
```
|
|
|
|
## Temas y Sonidos
|
|
|
|
### Estructura de Temas
|
|
|
|
```typescript
|
|
// themes/drrr.ts
|
|
export const drrrTheme: ThemeConfig = {
|
|
name: 'drrr',
|
|
displayName: 'DRRR Style',
|
|
colors: {
|
|
primary: '#FFD93D',
|
|
secondary: '#6BCB77',
|
|
accent: '#FF6B6B',
|
|
bg: '#1a1a2e',
|
|
text: '#EAEAEA',
|
|
textMuted: '#888888'
|
|
},
|
|
fonts: {
|
|
heading: '"Press Start 2P", monospace',
|
|
body: 'Inter, sans-serif'
|
|
}
|
|
}
|
|
```
|
|
|
|
### Estructura de Sonidos
|
|
|
|
```
|
|
public/sounds/
|
|
├── drrr/
|
|
│ ├── correct.mp3
|
|
│ ├── incorrect.mp3
|
|
│ ├── select.mp3
|
|
│ └── ...
|
|
├── retro/
|
|
├── minimal/
|
|
├── rgb/
|
|
└── anime/
|
|
```
|
|
|
|
Cada tema tiene sus propios archivos de audio, con fallback a sonidos generados por Web Audio API si no existen.
|
|
|
|
## Escalabilidad
|
|
|
|
### Horizontal Scaling
|
|
|
|
```
|
|
┌─────────────┐
|
|
│ NGINX │
|
|
│ (LB) │
|
|
└──────┬──────┘
|
|
│
|
|
┌─────────────────┼─────────────────┐
|
|
│ │ │
|
|
┌─────▼─────┐ ┌─────▼─────┐ ┌─────▼─────┐
|
|
│ Backend │ │ Backend │ │ Backend │
|
|
│ Node 1 │ │ Node 2 │ │ Node 3 │
|
|
└─────┬─────┘ └─────┬─────┘ └─────┬─────┘
|
|
│ │ │
|
|
└────────────────┼────────────────┘
|
|
│
|
|
┌───────────┴───────────┐
|
|
│ │
|
|
┌─────▼─────┐ ┌─────▼─────┐
|
|
│ Redis │ │ PostgreSQL│
|
|
│ Cluster │ │ Primary │
|
|
└───────────┘ └───────────┘
|
|
```
|
|
|
|
### Consideraciones
|
|
|
|
1. **Socket.IO con Redis Adapter** - Para sincronizar eventos entre múltiples instancias
|
|
2. **Sticky Sessions** - Para mantener conexiones WebSocket
|
|
3. **PostgreSQL Read Replicas** - Para queries de lectura
|
|
4. **Redis Cluster** - Para alta disponibilidad del estado
|
|
|
|
## Seguridad
|
|
|
|
1. **CORS** - Configurado para dominios permitidos
|
|
2. **Rate Limiting** - En endpoints REST
|
|
3. **Input Validation** - Pydantic schemas
|
|
4. **SQL Injection** - Prevenido por SQLAlchemy ORM
|
|
5. **XSS** - React escapa contenido por defecto
|
|
6. **WebSocket Auth** - Validación de sala/jugador en cada evento
|