Saltar a contenido

⚔️ VERSUS — Guía de coordinación técnica

Este documento es la referencia central del equipo. Si no sabes por dónde empezar, empieza aquí. Si tienes dudas de qué hace un endpoint, búscalo aquí. Si vas a crear algo nuevo, comprueba que no existe ya aquí.


🧠 Qué es Versus (versión rápida)

Un juego de preguntas multijugador con 5 modos de juego. Las preguntas se extraen automáticamente de la web con scrapers. Hay matchmaking, ranking global, perfiles y sistema de vidas.

Stack: - Backend: Spring Boot — API REST + WebSockets - Frontend: Angular — SPA - Base de datos: PostgreSQL - Scraping: Scrapy (Python) - Infraestructura: Docker


🗺️ Módulos del sistema

El proyecto se divide en 9 módulos. Cada issue pertenece a uno.

┌─────────────────────────────────────────────────────────┐
│                        VERSUS                           │
│                                                         │
│  [AUTH]  [USERS]  [QUESTIONS]  [GAME]  [MATCH]         │
│  [STATS]  [ACHIEVEMENTS]  [SCRAPING]  [ADMIN]          │
└─────────────────────────────────────────────────────────┘

📦 Módulo 1 — AUTH

Issues: #39, #40, #84, #85

Autenticación con JWT. Login, registro, refresco de token y roles.

Endpoints

Método Ruta Descripción Issues
POST /api/auth/register Registro de nuevo usuario #85
POST /api/auth/login Login → devuelve accessToken + refreshToken #84
POST /api/auth/refresh Renueva el accessToken con el refreshToken #84
POST /api/auth/logout Invalida el refreshToken #84

Contrato de login

Request:

POST /api/auth/login
{
  "email": "raul@versus.com",
  "password": "mipassword"
}

Response:

{
  "accessToken": "eyJhbGci...",
  "refreshToken": "eyJhbGci...",
  "user": {
    "id": "uuid",
    "username": "Raúl",
    "role": "PLAYER",
    "avatarUrl": "https://..."
  }
}

Roles disponibles

Rol Valor Acceso
Jugador PLAYER Todo lo de juego
Moderador MODERATOR + gestión de reportes
Admin ADMIN + gestión de usuarios y spiders

Para el frontend (#40)

  • Implementar AuthInterceptor que adjunte el Bearer token en cada petición
  • Implementar AuthGuard para rutas protegidas
  • Guardar tokens en localStorage o sessionStorage

👤 Módulo 2 — USERS

Issues: #85

Gestión del perfil de usuario.

Endpoints

Método Ruta Descripción
GET /api/users/me Perfil del usuario autenticado
PUT /api/users/me Actualizar username o avatar
PUT /api/users/me/password Cambiar contrasena (requiere contrasena actual)
PUT /api/users/me/avatar Seleccionar avatar por URL o subir imagen multipart
DELETE /api/users/me Eliminar cuenta con soft delete y anonimizacion
GET /api/users/:id Perfil público de cualquier usuario

Contrato de perfil

GET /api/users/me
 200
{
  "id": "uuid",
  "username": "Raúl",
  "email": "raul@versus.com",
  "avatarUrl": "https://...",
  "role": "PLAYER",
  "createdAt": "2025-01-01T00:00:00Z"
}

Contratos de ajustes de cuenta

La pantalla /settings centraliza cuenta, avatar, notificaciones, audio y zona de peligro.

Cambiar password:

PUT /api/users/me/password
{
  "currentPassword": "actual123",
  "newPassword": "nueva1234"
}
Respuesta: 204 No Content. La nueva contrasena debe tener minimo 8 caracteres.

Seleccionar avatar predefinido:

PUT /api/users/me/avatar
Content-Type: application/json
{
  "avatarUrl": "https://api.dicebear.com/..."
}

Subir avatar propio:

PUT /api/users/me/avatar
Content-Type: multipart/form-data

file=<png|jpeg|max 2MB>

El backend devuelve UserMeResponse y el frontend actualiza topbar/perfil inmediatamente. Hasta que exista el modulo de almacenamiento, el upload se guarda como data:image/...;base64 en users.avatar_url.

Eliminar cuenta:

DELETE /api/users/me
Respuesta: 204 No Content. En frontend se exige doble confirmacion escribiendo el username exacto. En backend se aplica soft delete con status=DELETED, is_active=false y anonimizacion de username/email/avatar/password.

Pantalla /settings (frontend)

  • Cuenta: username editable; email visible pero dependiente del modulo de email para cambio real.
  • Password: requiere password actual y confirmacion visual en el formulario.
  • Avatar: galeria de avatares predefinidos con confirmacion Aceptar/Cancelar; upload PNG/JPEG con crop basico y boton de subida.
  • Notificaciones: preferencias de solicitudes de amistad, invitaciones y logros guardadas en localStorage.
  • Audio: controles Efectos de sonido, Musica de fondo, silenciar todo y feedback reducido guardados en localStorage.
  • Zona de peligro: borrar cuenta exige escribir el username.
  • Topbar: muestra username/avatar reales y XP calculado desde /api/stats/me mientras no exista campo xp dedicado.

❓ Módulo 3 — QUESTIONS

Issues: #41, #42, #43, #44, #52

Preguntas binarias y numéricas que alimentan los modos de juego.

Tipos de pregunta

Tipo Descripción Ejemplo
BINARY Dos opciones, una correcta ¿Quién tiene más goles: Messi o Cristiano?
NUMERIC Respuesta numérica libre ¿Cuántos seguidores tiene Cristiano en Instagram?

Endpoints

Método Ruta Descripción Issues
GET /api/questions/random Pregunta aleatoria (opcionalmente por categoría o tipo) #42
GET /api/questions/random?type=BINARY&category=football Filtros opcionales #42, #43
GET /api/questions/:id Pregunta por ID #41
GET /api/questions/categories Lista de categorías disponibles #43

Contrato de pregunta BINARY

{
  "id": "uuid",
  "type": "BINARY",
  "text": "¿Quién tiene más seguidores en Instagram?",
  "category": "football",
  "options": [
    { "id": "uuid-a", "text": "Cristiano Ronaldo" },
    { "id": "uuid-b", "text": "Lionel Messi" }
  ],
  "scrapedAt": "2025-04-01T00:00:00Z"
}

⚠️ La opción correcta NO se envía al frontend hasta que el jugador responde. El backend la guarda internamente y la valida al recibir la respuesta.

Contrato de pregunta NUMERIC

{
  "id": "uuid",
  "type": "NUMERIC",
  "text": "¿Cuántos seguidores tiene Cristiano Ronaldo en Instagram?",
  "category": "football",
  "unit": "millones",
  "scrapedAt": "2025-04-01T00:00:00Z"
}

🎮 Módulo 4 — GAME (modos en solitario)

Issues: #53, #54, #55, #56, #57, #58, #59, #60, #61, #62

Los dos modos de un solo jugador: Supervivencia y Precisión.

Flujo general de partida

Frontend                          Backend
   │                                 │
   ├─ POST /api/game/start ─────────►│ Crea sesión de partida
   │◄── { sessionId, question } ─────┤ Devuelve 1ª pregunta
   │                                 │
   ├─ POST /api/game/answer ────────►│ Valida respuesta
   │◄── { correct, lifeDelta,  ──────┤ Devuelve resultado
   │      nextQuestion | gameOver }  │
   │                                 │
   └─ (si gameOver) ─────────────────┤ Guarda historial

Endpoints modo Supervivencia

Método Ruta Descripción Issues
POST /api/game/survival/start Inicia partida, devuelve sesión + 1ª pregunta #53
POST /api/game/survival/answer Envía respuesta, recibe resultado y siguiente pregunta #53, #55

Request answer:

{
  "sessionId": "uuid",
  "questionId": "uuid",
  "optionId": "uuid-a"
}

Response answer:

{
  "correct": true,
  "livesRemaining": 3,
  "lifeDelta": 0,
  "streak": 4,
  "scoreDelta": 150,
  "nextQuestion": { ... },
  "gameOver": false,
  "achievementsUnlocked": []
}

Endpoints modo Precisión

Método Ruta Descripción Issues
POST /api/game/precision/start Inicia partida numérica #60
POST /api/game/precision/answer Envía número, recibe desviación y daño/curación #59, #60

Request answer:

{
  "sessionId": "uuid",
  "questionId": "uuid",
  "value": 650000000
}

Response answer:

{
  "correctValue": 640000000,
  "deviation": 1.56,
  "deviationPercent": 1.56,
  "lifeDelta": 5,
  "livesRemaining": 105,
  "nextQuestion": { ... },
  "gameOver": false,
  "achievementsUnlocked": []
}

📐 lifeDelta positivo = curación, negativo = daño. El algoritmo de daño/curación se define en #59.


⚡ Módulo 5 — MATCH (modos multijugador)

Issues: #63, #64, #65, #66, #67, #68, #69, #70, #71, #72, #73, #74

Los tres modos PvP en tiempo real: Duelo binario, Duelo de precisión y Sabotaje.

⚠️ Este módulo usa WebSockets, no REST

La comunicación durante la partida es por WebSocket (STOMP sobre SockJS es el estándar en Spring Boot).

Conexión WebSocket

Frontend conecta a:  ws://localhost:8080/ws

Suscripciones del cliente:
  /user/queue/match          → eventos de la partida (respuestas, vidas, resultado)
  /topic/match/{matchId}     → estado compartido de la sala

Envíos del cliente:
  /app/match/answer          → enviar respuesta
  /app/match/ready           → confirmar que está listo para empezar

Flujo de sala de espera → partida

1. POST /api/match/queue        → Entrar en cola de matchmaking
2. Backend empareja dos jugadores → emite evento "MATCH_FOUND"
3. Frontend redirige a /sala/:matchId
4. Cada jugador envía /app/match/ready
5. Cuando ambos están listos → emite "MATCH_START" con 1ª pregunta
6. Cada pregunta → jugadores responden → backend procesa → emite resultado
7. Al terminar → emite "MATCH_END" con ganador y stats

Endpoints REST de sala (previos a la partida)

Método Ruta Descripción Issues
POST /api/match/queue Entrar en cola de matchmaking #66
DELETE /api/match/queue Salir de la cola #66
POST /api/match/room Crear sala privada con código #65
POST /api/match/room/join Unirse a sala privada por código #65
GET /api/match/:matchId Estado actual de una sala #65

Eventos WebSocket (backend → frontend)

Evento Canal Payload
MATCH_FOUND /user/queue/match { matchId, opponent }
MATCH_START /topic/match/{id} { question, mode }
QUESTION /topic/match/{id} { question, timeLimit }
ROUND_RESULT /topic/match/{id} { player1Lives, player2Lives, correct }
MATCH_END /topic/match/{id} { winner, stats }

Lógica de daño por modo

Modo Quién pierde vida Cuándo
Duelo binario El que falla Al responder
Duelo de precisión El que se desvía más Al responder ambos
Sabotaje El rival Cuando el otro acierta mejor

El algoritmo de daño al rival (Sabotaje) se implementa en #74.


📊 Módulo 6 — STATS & RANKING

Issues: #54, #76, #77, #78, #79

Historial de partidas, estadísticas personales y ranking global.

Endpoints

Método Ruta Descripción Issues
GET /api/stats/me Estadísticas del usuario autenticado #77
GET /api/stats/me?mode=SURVIVAL Filtradas por modo #77
GET /api/stats/me/history Historial de partidas #76
GET /api/ranking/:mode Top 100 de un modo #78
GET /api/ranking/:mode/me Posición propia en el ranking #78

Contrato de stats

GET /api/stats/me?mode=SURVIVAL
 200
{
  "mode": "SURVIVAL",
  "gamesPlayed": 42,
  "gamesWon": 28,
  "winRate": 66.6,
  "bestStreak": 12,
  "currentStreak": 3,
  "avgDeviation": null
}

avgDeviation solo aplica a modos numéricos (PRECISION, PRECISION_DUEL).


Modulo 7 - ACHIEVEMENTS

Sistema de logros y emblemas visibles en perfil/topbar.

Los logros se evaluan al terminar una partida singleplayer desde GameService. El catalogo inicial se siembra en arranque y cada logro solo puede desbloquearse una vez por usuario.

Endpoints

Metodo Ruta Descripcion
GET /api/achievements Catalogo completo con estado para el usuario autenticado

Contrato de logro

{
  "id": "uuid",
  "key": "first_game",
  "name": "Primeros pasos",
  "description": "Juega tu primera partida.",
  "iconKey": "first",
  "category": "Primeros pasos",
  "unlocked": true,
  "unlockedAt": "2026-05-07T15:00:00Z"
}

Si un logro esta bloqueado, el backend devuelve name: "???", description: "???" e iconKey: "lock" para no revelar como desbloquearlo.

Eventos WebSocket

Evento Canal Payload
ACHIEVEMENT_UNLOCKED /user/queue/achievements { "type": "ACHIEVEMENT_UNLOCKED", "achievement": { ... } }

Frontend

  • Toast global no bloqueante al desbloquear un logro.
  • Perfil: seccion Logros con contador desbloqueados/total, grid de catalogo y fecha si esta desbloqueado.
  • Topbar/avatar: muestra como emblema el logro desbloqueado mas reciente.

🕷️ Módulo 8 — SCRAPING

Scrapers en Scrapy que extraen datos reales y los insertan en PostgreSQL como preguntas.

Spiders planificadas

Spider Fuente Tipo pregunta Issue
RRSS (YouTube/TikTok/Twitch) SocialBlade NUMERIC #97 ✅
Estadísticas fútbol FBref / Transfermarkt NUMERIC + BINARY #98
Taquilla de cine Box Office Mojo NUMERIC #98
Capitales y geografía Wikipedia BINARY #98
Récords varios Wikipedia / Guinness NUMERIC #98

Pipeline de datos

Spider (Scrapy)
    │  yield QuestionItem(text, type, category, ...)
    ▼
DeerdaysScraperPipeline (#97 ✅)
    ├── Validación de calidad mínima
    ├── Deduplicación por SHA-256(text) → campo text_hash en questions
    ├── INSERT questions + question_options
    └── UPDATE spider_runs (questionsInserted, errors, finishedAt)
    │
    ▼
PostgreSQL → tabla questions (estado: PENDING_REVIEW)
    │
    ▼
Moderador revisa → estado: ACTIVE

Ver documentación detallada del pipeline en docs/scraping-pipeline.md.

Endpoints de gestión (solo ADMIN)

Implementados en #97. El identificador de ruta es el nombre del spider, no su UUID.

Método Ruta Descripción Issue
GET /api/admin/spiders Lista de spiders con estado actual y último run #97 ✅
POST /api/admin/spiders/{name}/run Lanza el spider por nombre. 202 con el SpiderRun creado. 404 si no existe, 409 si ya está en ejecución #97 ✅
GET /api/admin/spiders/{name}/runs Historial de runs del spider ordenados por fecha descendente #97 ✅

🛡️ Módulo 9 — ADMIN & MODERACIÓN

Issues: #80, #81, #82

Gestión de preguntas reportadas y administración de la plataforma.

Endpoints de moderación

Método Ruta Descripción Issues
POST /api/questions/{id}/report Reportar una pregunta (PLAYER autenticado) #100 ✅
GET /api/moderation/reports Lista de reportes, filtrable por ?status= (MODERATOR+) #100 ✅
PUT /api/moderation/reports/{id}/resolve Resolver reporte: DISMISS / EDIT_QUESTION / DELETE_QUESTION (MODERATOR+) #100 ✅

Contrato de reporte (POST /api/questions/{id}/report)

Request:

{ "reason": "WRONG_ANSWER", "comment": "Texto opcional" }
Valores de reason: WRONG_ANSWER, OUTDATED, OFFENSIVE, OTHER

Response 201: ReportResponse — ver docs/moderation.md

Contrato de resolución (PUT /api/moderation/reports/{id}/resolve)

Request:

{ "action": "DELETE_QUESTION" }
Valores de action: DISMISS, EDIT_QUESTION, DELETE_QUESTION

Response 200: ReportResponse con status, resolvedBy, resolvedAt y action rellenos.

Auto-flagging

Cuando una pregunta acumula 5 reportes PENDING, su estado cambia automáticamente a FLAGGED y deja de servirse en partidas hasta que un moderador la revisa.

Ver documentación completa en docs/moderation.md.

Endpoints de administración

Método Ruta Descripción Issues
GET /api/admin/users Lista de usuarios #82
PUT /api/admin/users/:id/role Cambiar rol de usuario #82
DELETE /api/admin/users/:id Eliminar usuario #82
PUT /api/admin/questions/:id/status Activar/desactivar pregunta #82

🔗 Orden de implementación recomendado

Para evitar bloqueos, seguir este orden. Cada fase desbloquea la siguiente.

FASE 1 — Base (sin esto nada funciona)
  ✦ #41 Modelo de preguntas (DB)
  ✦ #39 Middleware JWT y Roles (Backend)
  ✦ #84 Login (Backend)
  ✦ #40 Guard + AuthInterceptor (Frontend)
  ✦ #44 Seed inicial de preguntas

FASE 2 — Juego en solitario
  ✦ #42 Endpoint pregunta aleatoria
  ✦ #53 Lógica partida binaria individual
  ✦ #55 Lógica de vidas
  ✦ #56 Endpoints partida binaria
  ✦ #57 Vista juego Supervivencia (Frontend)
  ✦ #59 Algoritmo daño/curación por precisión
  ✦ #60 Endpoints partida numérica
  ✦ #61 Vista juego Precisión (Frontend)

FASE 3 — Multijugador
  ✦ #63 WebSockets Backend
  ✦ #64 WebSockets Frontend
  ✦ #65 Sistema de salas
  ✦ #66 Matchmaking
  ✦ #67 Sincronización de preguntas
  ✦ #68 Lógica duelo binario
  ✦ #71 Lógica duelo de precisión
  ✦ #73 Lógica modo Versus (Sabotaje)

FASE 4 — Stats, ranking y scraping
  ✦ #76 Historial de partidas
  ✦ #77 Estadísticas personales
  ✦ #78 Ranking global
  ✦ #45 Pipeline Scrapy → PostgreSQL
  ✦ #46–#50 Spiders individuales

FASE 5 — Moderación y admin
  ✦ #80 Reporte de preguntas
  ✦ #81 Panel de moderador
  ✦ #82 Panel de administrador

🤝 Normas de coordinación

Contrato entre backend y frontend

  • El backend define el contrato (URL, método, request, response) en este documento antes de implementarlo.
  • El frontend no espera a que el backend esté listo: usa datos mock con la misma estructura del contrato.
  • Si el contrato cambia, se actualiza este documento y se avisa al equipo.

Códigos de error estándar

Todos los errores siguen este formato:

{
  "error": "UNAUTHORIZED",
  "message": "Token expirado o inválido",
  "status": 401
}
Código Cuándo usarlo
400 Request malformado o datos inválidos
401 No autenticado
403 Autenticado pero sin permisos
404 Recurso no encontrado
409 Conflicto (usuario ya existe, sala llena...)
500 Error interno del servidor

Variables de entorno

# Backend (application.properties / .env)
DB_URL=jdbc:postgresql://localhost:5432/versus
DB_USER=versus
DB_PASS=versus
JWT_SECRET=cambiame_en_produccion
JWT_EXPIRY=900          # 15 minutos
JWT_REFRESH_EXPIRY=604800  # 7 días

# Frontend (environment.ts)
API_URL=http://localhost:8080/api
WS_URL=ws://localhost:8080/ws

Última actualización: 23-04-2026 — Si modificas algo del contrato de API, actualiza este documento.