- 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>
493 lines
7.7 KiB
Markdown
493 lines
7.7 KiB
Markdown
# API Reference - Trivy
|
|
|
|
## Base URL
|
|
|
|
```
|
|
http://localhost:8000/api
|
|
```
|
|
|
|
## Autenticación
|
|
|
|
El panel de administración usa autenticación básica HTTP.
|
|
|
|
```bash
|
|
# Ejemplo con curl
|
|
curl -u admin:admin123 http://localhost:8000/api/admin/questions
|
|
```
|
|
|
|
---
|
|
|
|
## Endpoints REST
|
|
|
|
### Game
|
|
|
|
#### GET /game/categories
|
|
|
|
Obtiene todas las categorías disponibles.
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"name": "Nintendo",
|
|
"icon": "🍄",
|
|
"color": "#E60012"
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "Xbox",
|
|
"icon": "🎮",
|
|
"color": "#107C10"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### GET /game/today-questions
|
|
|
|
Obtiene las preguntas activas para hoy (sin respuestas).
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"1": [
|
|
{
|
|
"id": 1,
|
|
"category_id": 1,
|
|
"question_text": "¿En qué año se lanzó la NES?",
|
|
"difficulty": 1,
|
|
"points": 100,
|
|
"time_seconds": 15
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Admin - Preguntas
|
|
|
|
#### GET /admin/questions
|
|
|
|
Lista todas las preguntas.
|
|
|
|
**Query Parameters:**
|
|
- `status` (optional): `pending`, `approved`, `used`
|
|
- `category_id` (optional): ID de categoría
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"id": 1,
|
|
"category_id": 1,
|
|
"question_text": "¿En qué año se lanzó la NES?",
|
|
"correct_answer": "1983",
|
|
"alt_answers": ["1984"],
|
|
"difficulty": 1,
|
|
"points": 100,
|
|
"time_seconds": 15,
|
|
"date_active": "2024-01-26",
|
|
"status": "approved",
|
|
"fun_fact": "La NES salvó la industria del videojuego",
|
|
"created_at": "2024-01-25T10:00:00Z"
|
|
}
|
|
]
|
|
```
|
|
|
|
#### POST /admin/questions
|
|
|
|
Crea una nueva pregunta.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"category_id": 1,
|
|
"question_text": "¿Cuál es el nombre del fontanero de Nintendo?",
|
|
"correct_answer": "Mario",
|
|
"alt_answers": ["Super Mario"],
|
|
"difficulty": 1,
|
|
"points": 100,
|
|
"time_seconds": 15,
|
|
"date_active": "2024-01-26",
|
|
"status": "approved",
|
|
"fun_fact": "Mario apareció por primera vez en Donkey Kong"
|
|
}
|
|
```
|
|
|
|
**Response:** `201 Created`
|
|
|
|
#### PUT /admin/questions/{id}
|
|
|
|
Actualiza una pregunta existente.
|
|
|
|
#### DELETE /admin/questions/{id}
|
|
|
|
Elimina una pregunta.
|
|
|
|
---
|
|
|
|
### Admin - Categorías
|
|
|
|
#### GET /admin/categories
|
|
|
|
Lista todas las categorías.
|
|
|
|
#### POST /admin/categories
|
|
|
|
Crea una nueva categoría.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"name": "Ciencia",
|
|
"icon": "🔬",
|
|
"color": "#00BCD4"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### Admin - Salas
|
|
|
|
#### GET /admin/rooms/active
|
|
|
|
Obtiene las salas activas en Redis.
|
|
|
|
**Response:**
|
|
```json
|
|
[
|
|
{
|
|
"code": "ABC123",
|
|
"status": "playing",
|
|
"host": "Player1",
|
|
"teams": {
|
|
"A": [{"name": "Player1", "team": "A"}],
|
|
"B": [{"name": "Player2", "team": "B"}]
|
|
},
|
|
"scores": {"A": 500, "B": 300},
|
|
"current_team": "A"
|
|
}
|
|
]
|
|
```
|
|
|
|
---
|
|
|
|
## WebSocket Events
|
|
|
|
### Conexión
|
|
|
|
```javascript
|
|
import { io } from 'socket.io-client';
|
|
|
|
const socket = io('http://localhost:8000', {
|
|
transports: ['websocket', 'polling']
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### Eventos Cliente → Servidor
|
|
|
|
#### create_room
|
|
|
|
Crea una nueva sala de juego.
|
|
|
|
```javascript
|
|
socket.emit('create_room', {
|
|
player_name: 'MiNombre'
|
|
});
|
|
```
|
|
|
|
#### join_room
|
|
|
|
Unirse a una sala existente.
|
|
|
|
```javascript
|
|
socket.emit('join_room', {
|
|
room_code: 'ABC123',
|
|
player_name: 'MiNombre',
|
|
team: 'A' // o 'B'
|
|
});
|
|
```
|
|
|
|
#### change_team
|
|
|
|
Cambiar de equipo en el lobby.
|
|
|
|
```javascript
|
|
socket.emit('change_team', {
|
|
team: 'B'
|
|
});
|
|
```
|
|
|
|
#### start_game
|
|
|
|
Iniciar el juego (solo host).
|
|
|
|
```javascript
|
|
socket.emit('start_game', {});
|
|
```
|
|
|
|
#### select_question
|
|
|
|
Seleccionar una pregunta del tablero.
|
|
|
|
```javascript
|
|
socket.emit('select_question', {
|
|
question_id: 1,
|
|
category_id: 1
|
|
});
|
|
```
|
|
|
|
#### submit_answer
|
|
|
|
Enviar respuesta a la pregunta actual.
|
|
|
|
```javascript
|
|
socket.emit('submit_answer', {
|
|
answer: 'Mi respuesta',
|
|
question: { /* objeto pregunta */ },
|
|
is_steal: false
|
|
});
|
|
```
|
|
|
|
#### steal_decision
|
|
|
|
Decidir si intentar robar.
|
|
|
|
```javascript
|
|
socket.emit('steal_decision', {
|
|
attempt: true, // o false para pasar
|
|
question_id: 1,
|
|
answer: 'Mi respuesta' // solo si attempt=true
|
|
});
|
|
```
|
|
|
|
#### chat_message
|
|
|
|
Enviar mensaje al chat general.
|
|
|
|
```javascript
|
|
socket.emit('chat_message', {
|
|
message: 'Hola a todos!'
|
|
});
|
|
```
|
|
|
|
#### team_message
|
|
|
|
Enviar mensaje al chat de equipo.
|
|
|
|
```javascript
|
|
socket.emit('team_message', {
|
|
room_code: 'ABC123',
|
|
team: 'A',
|
|
player_name: 'MiNombre',
|
|
message: 'Mensaje privado del equipo'
|
|
});
|
|
```
|
|
|
|
#### send_reaction
|
|
|
|
Enviar reacción emoji.
|
|
|
|
```javascript
|
|
socket.emit('send_reaction', {
|
|
emoji: '🎉',
|
|
room_code: 'ABC123',
|
|
player_name: 'MiNombre'
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
### Eventos Servidor → Cliente
|
|
|
|
#### room_created
|
|
|
|
Confirmación de sala creada.
|
|
|
|
```javascript
|
|
socket.on('room_created', (data) => {
|
|
console.log(data.room.code); // 'ABC123'
|
|
});
|
|
```
|
|
|
|
**Payload:**
|
|
```json
|
|
{
|
|
"room": {
|
|
"code": "ABC123",
|
|
"status": "waiting",
|
|
"host": "Player1",
|
|
"teams": {"A": [...], "B": []},
|
|
"scores": {"A": 0, "B": 0}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### player_joined
|
|
|
|
Un jugador se unió a la sala.
|
|
|
|
```javascript
|
|
socket.on('player_joined', (data) => {
|
|
console.log('Nuevo jugador:', data.room.teams);
|
|
});
|
|
```
|
|
|
|
#### player_left
|
|
|
|
Un jugador abandonó la sala.
|
|
|
|
```javascript
|
|
socket.on('player_left', (data) => {
|
|
console.log('Jugador salió');
|
|
});
|
|
```
|
|
|
|
#### game_started
|
|
|
|
El juego ha comenzado.
|
|
|
|
```javascript
|
|
socket.on('game_started', (data) => {
|
|
// data.room.board contiene el tablero con las preguntas
|
|
console.log('Categorías:', Object.keys(data.room.board));
|
|
});
|
|
```
|
|
|
|
**Payload:**
|
|
```json
|
|
{
|
|
"room": {
|
|
"status": "playing",
|
|
"board": {
|
|
"1": [
|
|
{
|
|
"id": 1,
|
|
"question_text": "...",
|
|
"difficulty": 1,
|
|
"points": 100,
|
|
"answered": false
|
|
}
|
|
]
|
|
},
|
|
"current_team": "A",
|
|
"current_player_index": {"A": 0, "B": 0}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### question_selected
|
|
|
|
Se seleccionó una pregunta.
|
|
|
|
```javascript
|
|
socket.on('question_selected', (data) => {
|
|
console.log('Pregunta:', data.question_id);
|
|
});
|
|
```
|
|
|
|
#### answer_result
|
|
|
|
Resultado de una respuesta.
|
|
|
|
```javascript
|
|
socket.on('answer_result', (data) => {
|
|
if (data.valid) {
|
|
console.log('¡Correcto! +', data.points_earned);
|
|
} else {
|
|
console.log('Incorrecto:', data.reason);
|
|
}
|
|
});
|
|
```
|
|
|
|
**Payload:**
|
|
```json
|
|
{
|
|
"valid": true,
|
|
"reason": "Respuesta correcta",
|
|
"points_earned": 100,
|
|
"was_steal": false,
|
|
"room": { /* estado actualizado */ }
|
|
}
|
|
```
|
|
|
|
#### steal_prompt
|
|
|
|
Oportunidad de robo disponible.
|
|
|
|
```javascript
|
|
socket.on('steal_prompt', (data) => {
|
|
console.log('¡Puedes robar!');
|
|
});
|
|
```
|
|
|
|
#### game_finished
|
|
|
|
El juego ha terminado.
|
|
|
|
```javascript
|
|
socket.on('game_finished', (data) => {
|
|
console.log('Ganador:', data.winner);
|
|
console.log('Puntuación final:', data.room.scores);
|
|
});
|
|
```
|
|
|
|
#### new_reaction
|
|
|
|
Nueva reacción de un jugador.
|
|
|
|
```javascript
|
|
socket.on('new_reaction', (data) => {
|
|
console.log(data.player_name, 'reaccionó con', data.emoji);
|
|
});
|
|
```
|
|
|
|
#### team_message
|
|
|
|
Mensaje de chat de equipo.
|
|
|
|
```javascript
|
|
socket.on('team_message', (data) => {
|
|
console.log(`[Equipo] ${data.player_name}: ${data.message}`);
|
|
});
|
|
```
|
|
|
|
#### error
|
|
|
|
Error del servidor.
|
|
|
|
```javascript
|
|
socket.on('error', (data) => {
|
|
console.error('Error:', data.message);
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Códigos de Error
|
|
|
|
| Código | Mensaje | Descripción |
|
|
|--------|---------|-------------|
|
|
| `room_not_found` | Room not found | La sala no existe |
|
|
| `room_full` | Room is full | La sala está llena |
|
|
| `not_host` | Only the host can start | Solo el host puede iniciar |
|
|
| `need_players` | Both teams need players | Ambos equipos necesitan jugadores |
|
|
| `not_your_turn` | Not your turn | No es tu turno |
|
|
| `question_answered` | Question already answered | Pregunta ya contestada |
|
|
|
|
---
|
|
|
|
## Rate Limiting
|
|
|
|
- API REST: 100 requests/minuto por IP
|
|
- WebSocket: Sin límite específico (controlado por lógica de juego)
|
|
|
|
## Timeouts
|
|
|
|
- Preguntas: 15-35 segundos según dificultad
|
|
- Robo: 50% del tiempo original
|
|
- Sala inactiva: 3 horas (configurable)
|