prisma-deploy.yml: main va a producción y las demás ramas crean un preview con su propia base de datos.
Stack: Next.js 16 (App Router), React 19, TypeScript, Tailwind 4, componentes de shadcn/ui, Zustand, Prisma ORM 8 con PostgreSQL (el contrato vive en src/prisma/contract.prisma y la conexión en prisma.config.ts), zod para validar las entradas de la API y Vitest para los tests. La versión del paquete es la 0.2.0.
Riot reserva los datos de partidas de Valorant (VAL-MATCH-V1) para claves de producción. Con una clave de desarrollo la app verifica cuentas con
account-v1 y explica el bloqueo; sin clave solo funciona el modo demo.Qué incluye
- Modo demo: busca
Demo#VANTSsin ninguna clave. Son 8 partidas simuladas de forma determinista, con estadísticas agregadas (winrate, K/D, ACS, ADR y porcentaje de headshots) y scoreboard por partida. - Valorant con la API de Riot: resuelve Riot IDs reales (
Nombre#TAG) y, con clave de producción, guarda y agrega las últimas 8 partidas del jugador. - Steam:
GET /api/steam/resolvedevuelve un perfil de Steam y sus 10 juegos con más horas. - Plataforma competitiva: torneos con estados (borrador, inscripción, en juego, completado o cancelado), equipos con plantilla, inscripciones sin duplicados, partidas con resultados validados y un registro de auditoría de cada cambio. Las lecturas son públicas y los cambios exigen el token de administración.
- Discord y FACEIT: vinculación de cuentas de Discord con OAuth2 y consulta de jugadores de FACEIT. Sin credenciales responden
503con el códigoNOT_CONFIGUREDen lugar de fallar. - Estado del servicio:
GET /api/healthy la pestaña «Estado» indican si la base de datos responde y qué integraciones están configuradas, sin mostrar ninguna clave.
Interfaz
Todo está en una sola página (/) que cambia de vista sin recargar:
- Buscador: un Riot ID y una región. El botón «API key» permite pegar una clave de Riot propia (
RGAPI-...). Se guarda en el navegador y se envía al backend en la cabecerax-riot-key, que tiene prioridad sobreRIOT_API_KEY. - Perfil y partida: estadísticas agregadas, resumen por agente, últimas partidas y scoreboard.
- Competitivo (botón con el trofeo en la cabecera): pestañas Torneos, Equipos y Estado, con datos de la base de datos. El detalle de un torneo muestra sus inscripciones y partidas.
?auth=linked o ?auth=error) y limpia la URL.
Estructura
Puesta en marcha
Demo#VANTS para probar la app sin claves. Para validar un cambio antes de subirlo:
Variables de entorno
La referencia completa está endocs/ENVIRONMENT.md.
En
.env.example todas las claves están vacías. Si no tienes una, déjala así: la API responde con el aviso de que falta la clave en lugar de llamar al servicio externo.
.env, la carpeta db/ y los archivos *.db están en .gitignore.
Scripts
Endpoints
Todos responden en JSON. Los errores tienen la forma{ "error": "<mensaje en español>", "code": "<código>" }; algunos solo traen error.
Estadísticas de Valorant y Steam
SonGET y públicos.
# del Riot ID va codificado como %23.
Demo#VANTS, sin distinguir mayúsculas, siempre devuelve datos demo, haya o no clave.- Los IDs de partida
demo-match-<n>devuelven el scoreboard demo. - Con clave, una partida que ya está guardada se sirve desde la base de datos sin volver a llamar a Riot.
- Si los detalles de juego de un perfil de Steam son privados, la respuesta trae el perfil con la lista de juegos vacía.
riotId, una region o una query ausentes o con formato inválido devuelven 400 solo con error, y un fallo inesperado devuelve 500. El resto de errores:
Con
FORBIDDEN_PRODUCT, la respuesta del perfil incluye verifiedAccount con la cuenta que account-v1 ya verificó.
Plataforma competitiva
Las rutas marcadas como admin exigen la cabeceraX-Admin-Token con el valor de ADMIN_TOKEN, y cada cambio queda en el registro de auditoría.
El juego por defecto es
valorant y un torneo admite de 2 a 128 equipos (8 por defecto).
Reglas que aplica la API:
- Solo se inscriben equipos en torneos en estado
registrationy antes deregistrationDeadline. Si no, responde409. - Un torneo completo responde
409, y un equipo ya inscrito,409conDUPLICATE_REGISTRATION. - Para completar una partida hay que enviar
homeScoreyawayScore, y se guarda la hora enplayedAt. - Una partida
completedoforfeitya no se puede modificar (409). Para corregir un resultado se crea otra partida. - El nombre de un equipo es único por juego (
409conDUPLICATE_TEAM) y el apodo, único dentro del equipo (409conDUPLICATE_MEMBER). - Si el slug de un torneo ya existe, se le añade un sufijo:
-2,-3…
ADMIN_TOKEN en el servidor, las rutas admin responden 503 con ADMIN_NOT_CONFIGURED; con un token ausente o incorrecto, 401 con ADMIN_INVALID. Una entrada inválida devuelve 400 con el primer error de validación y el campo afectado, y un torneo, equipo, inscripción o partida inexistentes devuelven 404.
Discord y FACEIT
- La vinculación con Discord guarda la cuenta en
ProviderIdentityy crea un usuario interno si no existía. Repetirla actualiza los datos en lugar de duplicarlos. - El parámetro
statedel flujo va firmado y caduca a los 10 minutos. - Al desvincular una cuenta, también se borra el usuario interno si se queda sin otras cuentas vinculadas ni equipos.
- Si falla la consulta del historial de FACEIT, la respuesta trae el perfil con la lista de partidas vacía.
400 con BAD_INPUT si el nickname tiene menos de 2 caracteres, 503 con NOT_CONFIGURED sin clave, 404 con NOT_FOUND, 429 con RATE_LIMIT, y 502 con INVALID_KEY o NETWORK.
Publicación en VANTS-BUS
Las mutaciones de la plataforma competitiva publican sus eventos al bus del ecosistema con identidadvantsnet, mediante el cliente de src/lib/bus/ y el mismo contrato de firma que los demás proyectos. La tabla muestra qué endpoint publica qué evento:
El envío no bloquea la respuesta y un fallo del bus solo deja una línea en el log: la mutación ya está guardada. Si faltan
VANTS_BUS_URL o VANTS_BUS_SECRET, la publicación queda apagada y la API funciona igual. Los consumidores son flask-agents y VANTS HUB, y vants-consumer-playvants copia los eventos a vants_feed.
Estado del servicio
GET /api/health responde 200 con "status": "healthy" si la base de datos responde, y 503 con "status": "degraded" si no. checks.integrations indica con true o false si Riot, Steam, Discord y FACEIT están configurados, y checks.admin, si lo está ADMIN_TOKEN. Nunca devuelve las claves.
Cómo se obtienen los datos de Riot
1
Resolver la cuenta
account-v1 convierte Nombre#TAG en un puuid y guarda o actualiza al jugador en Player.2
Pedir el historial
Se piden a VAL-MATCH-V1 los IDs de las últimas 8 partidas del jugador (
MATCH_COUNT).3
Guardar cada partida una sola vez
Las partidas que no están en
Match se descargan y se guardan con las estadísticas de cada jugador en MatchPlayerStat, y cada jugador visto se registra en Player. Los nombres de mapas y agentes salen de VAL-CONTENT-V1 (locale=es-ES), que se guarda en memoria 24 horas.4
Agregar desde la base de datos
buildProfile calcula winrate, K/D, ACS, ADR, porcentaje de headshots y el resumen por agente a partir de lo guardado.X-Riot-Token.
Base de datos
src/prisma/contract.prisma define once modelos:
Los estados, formatos y roles se guardan como texto y los valida zod en la frontera de la API. Con
onDelete: Cascade, borrar una partida de Riot borra sus estadísticas; borrar un torneo, sus inscripciones y partidas; borrar un equipo, sus miembros e inscripciones, y borrar un usuario, sus cuentas vinculadas.
El contrato no declara bloques
generator ni datasource: la conexión vive en prisma.config.ts y la base de datos es PostgreSQL (Prisma Postgres, Neon o cualquier PostgreSQL). Los artefactos contract.json y contract.d.ts se generan con db:emit y se commitean. El esquema SQL de docs/ARQUITECTURA.md, con la tabla leaderboards, es el diseño planificado y no el que usa el prototipo.Calidad y CI
Dependabot revisa cada semana las dependencias de npm y las GitHub Actions, con un máximo de 5 pull requests abiertas de cada tipo.
.github/workflows/ contiene solo los cuatro workflows de la tabla anterior.
tests/unit.test.ts tiene 14 tests: el state firmado de Discord, los errores de FACEIT, los validadores de la API, las reglas de resultados de partidas y la generación de slugs. No hay tests de extremo a extremo automatizados; la verificación manual está en docs/TESTING.md.
Integraciones
Estado segúndocs/INTEGRATIONS.md: