Files
Trivy/docs/API.md
consultoria-as 6248037b47 docs: añade documentación completa del proyecto
- 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>
2026-01-26 23:50:34 +00:00

7.7 KiB

API Reference - Trivy

Base URL

http://localhost:8000/api

Autenticación

El panel de administración usa autenticación básica HTTP.

# 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:

[
  {
    "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:

{
  "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:

[
  {
    "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:

{
  "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:

{
  "name": "Ciencia",
  "icon": "🔬",
  "color": "#00BCD4"
}

Admin - Salas

GET /admin/rooms/active

Obtiene las salas activas en Redis.

Response:

[
  {
    "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

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.

socket.emit('create_room', {
  player_name: 'MiNombre'
});

join_room

Unirse a una sala existente.

socket.emit('join_room', {
  room_code: 'ABC123',
  player_name: 'MiNombre',
  team: 'A' // o 'B'
});

change_team

Cambiar de equipo en el lobby.

socket.emit('change_team', {
  team: 'B'
});

start_game

Iniciar el juego (solo host).

socket.emit('start_game', {});

select_question

Seleccionar una pregunta del tablero.

socket.emit('select_question', {
  question_id: 1,
  category_id: 1
});

submit_answer

Enviar respuesta a la pregunta actual.

socket.emit('submit_answer', {
  answer: 'Mi respuesta',
  question: { /* objeto pregunta */ },
  is_steal: false
});

steal_decision

Decidir si intentar robar.

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.

socket.emit('chat_message', {
  message: 'Hola a todos!'
});

team_message

Enviar mensaje al chat de equipo.

socket.emit('team_message', {
  room_code: 'ABC123',
  team: 'A',
  player_name: 'MiNombre',
  message: 'Mensaje privado del equipo'
});

send_reaction

Enviar reacción emoji.

socket.emit('send_reaction', {
  emoji: '🎉',
  room_code: 'ABC123',
  player_name: 'MiNombre'
});

Eventos Servidor → Cliente

room_created

Confirmación de sala creada.

socket.on('room_created', (data) => {
  console.log(data.room.code); // 'ABC123'
});

Payload:

{
  "room": {
    "code": "ABC123",
    "status": "waiting",
    "host": "Player1",
    "teams": {"A": [...], "B": []},
    "scores": {"A": 0, "B": 0}
  }
}

player_joined

Un jugador se unió a la sala.

socket.on('player_joined', (data) => {
  console.log('Nuevo jugador:', data.room.teams);
});

player_left

Un jugador abandonó la sala.

socket.on('player_left', (data) => {
  console.log('Jugador salió');
});

game_started

El juego ha comenzado.

socket.on('game_started', (data) => {
  // data.room.board contiene el tablero con las preguntas
  console.log('Categorías:', Object.keys(data.room.board));
});

Payload:

{
  "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.

socket.on('question_selected', (data) => {
  console.log('Pregunta:', data.question_id);
});

answer_result

Resultado de una respuesta.

socket.on('answer_result', (data) => {
  if (data.valid) {
    console.log('¡Correcto! +', data.points_earned);
  } else {
    console.log('Incorrecto:', data.reason);
  }
});

Payload:

{
  "valid": true,
  "reason": "Respuesta correcta",
  "points_earned": 100,
  "was_steal": false,
  "room": { /* estado actualizado */ }
}

steal_prompt

Oportunidad de robo disponible.

socket.on('steal_prompt', (data) => {
  console.log('¡Puedes robar!');
});

game_finished

El juego ha terminado.

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.

socket.on('new_reaction', (data) => {
  console.log(data.player_name, 'reaccionó con', data.emoji);
});

team_message

Mensaje de chat de equipo.

socket.on('team_message', (data) => {
  console.log(`[Equipo] ${data.player_name}: ${data.message}`);
});

error

Error del servidor.

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)