Skip to main content
Prototipo de una red de estadísticas de videojuegos y plataforma competitiva: perfiles de jugadores, historial de partidas y scoreboards, con Valorant como primer juego, más torneos, equipos, inscripciones y resultados. Está en la cuenta de GitHub feispla (feispla/VANTSNET), no en la organización VANTSNET aunque se llame igual, y no comparte código ni servicios con el resto de repositorios: no usa Auth0. Cada push a una rama se despliega en Prisma Cloud con el workflow 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#VANTS sin 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/resolve devuelve 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 503 con el código NOT_CONFIGURED en lugar de fallar.
  • Estado del servicio: GET /api/health y 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 cabecera x-riot-key, que tiene prioridad sobre RIOT_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.
Con «Soy admin» se introduce el token de administración, que solo se recuerda en esa pestaña del navegador. Con él aparecen las acciones de gestión: crear torneos (empiezan en borrador) y equipos, cambiar el estado de un torneo, inscribir equipos, aprobar o rechazar inscripciones, programar partidas y reportar resultados. Discord y FACEIT todavía no tienen botones en la interfaz: se usan por API. Al volver de Discord, la página muestra el resultado de la vinculación (?auth=linked o ?auth=error) y limpia la URL.

Estructura

Puesta en marcha

Busca Demo#VANTS para probar la app sin claves. Para validar un cambio antes de subirlo:

Variables de entorno

La referencia completa está en docs/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

Son GET y públicos.
El # 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.
Un 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 cabecera X-Admin-Token con el valor de ADMIN_TOKEN, y cada cambio queda en el registro de auditoría.
Valores admitidos: 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 registration y antes de registrationDeadline. Si no, responde 409.
  • Un torneo completo responde 409, y un equipo ya inscrito, 409 con DUPLICATE_REGISTRATION.
  • Para completar una partida hay que enviar homeScore y awayScore, y se guarda la hora en playedAt.
  • Una partida completed o forfeit ya no se puede modificar (409). Para corregir un resultado se crea otra partida.
  • El nombre de un equipo es único por juego (409 con DUPLICATE_TEAM) y el apodo, único dentro del equipo (409 con DUPLICATE_MEMBER).
  • Si el slug de un torneo ya existe, se le añade un sufijo: -2, -3…
Sin 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 ProviderIdentity y crea un usuario interno si no existía. Repetirla actualiza los datos en lugar de duplicarlos.
  • El parámetro state del 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.
Errores de FACEIT: 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 identidad vantsnet, 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.
Las llamadas a Riot y a Steam se hacen desde el servidor, sin caché HTTP y con un timeout de 12 segundos; las de FACEIT, con un timeout de 10 segundos. Riot recibe la clave en la cabecera 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ún docs/INTEGRATIONS.md:

Documentos del repositorio

Hoja de ruta

Estado de cada fase según el README: