> ## Documentation Index
> Fetch the complete documentation index at: https://vantsgg.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Documentación interna de VANTS, la plataforma de esports, escrita en español. Cubre siete repositorios de la organización de GitHub VANTSNET y dos de la cuenta feispla: el prototipo VANTSNET, que se llama igual que la organización, y vantsoport.
> Empieza por /mapa-del-codigo para saber qué repositorio y qué carpeta tocar, y consulta /glosario para los términos propios como pt_, mm_, RR, VANTS-BUS o VANTCALL.
> Cuando un componente existe en varios repositorios, estas páginas describen la versión de Vantsgames salvo que digan otra cosa. El esquema de la base de datos se versiona en VantsBasededatos.
> La web y la API pública están en https://vants.vants.workers.dev. Rutas y respuestas: /referencia/api-publica. Tablas y estados: /referencia/esquema-base-de-datos.
> No inventes rutas, tablas, comandos ni variables de entorno: si algo no aparece aquí, revisa el código del repositorio correspondiente.

# Cómo se conectan los repositorios

> Qué conecta cada repositorio de VANTS y cómo circulan los eventos por VANTS-BUS: quién publica y consume cada tipo, firma de las peticiones y clientes.

Varios repositorios de VANTS usan la misma base de datos Neon y se avisan de lo que pasa a través de **VANTS-BUS**, un bus de eventos. Esta página muestra qué conecta con qué, qué eventos circulan por el bus y quién los publica y los consume. Las piezas internas de cada repositorio están en [Arquitectura general](https://vantsgg.mintlify.site/arquitectura.md) y en la página de cada repositorio.

## Mapa de conexiones

Los recuadros agrupan las piezas que viven en un mismo repositorio.

```mermaid actions={false} theme={null}
flowchart LR
  F[FACEIT] -->|webhooks| FW[vants-faceit-webhook]
  FW -->|guarda cada webhook| N[(Neon Postgres)]
  FW -->|publica eventos de partidos| B[VANTS-BUS]
  B -->|guarda los eventos| N
  B -->|eventos| FA[flask-agents]
  FA -->|publica post.generated| B
  subgraph PW [Vantsgames]
    W[Worker vants]
    CW[vants-consumer-playvants]
  end
  subgraph LAU [vants-dev-launcher]
    H[VANTS HUB]
  end
  B -->|eventos| H
  B -->|eventos| CW
  CW -->|copia a vants_feed| N
  W -->|publica inscripciones, clips y resultados| B
  W -->|lee y escribe| N
  W -->|valida la sesión| A[Auth0]
  W -->|publica clips| C[vants-clips]
  DB[VantsBasededatos] -->|migraciones| N
  S[Vants-Proteger] -->|login| A
  S -->|datos| N
  subgraph VN [VANTSNET]
    T[API de torneos]
  end
  T -->|publica torneos, inscripciones y resultados| B
  subgraph IND [Independientes]
    VS[vantsoport]
  end
```

## Qué conecta cada repositorio

| Repositorio | Se conecta con | Cómo |
| - | - | - |
| [Vantsgames](https://vantsgg.mintlify.site/repos/vantsgames.md) | Neon, Auth0, vants-clips y VANTS-BUS | El Worker `vants` lee y escribe en Neon, valida la sesión con Auth0 y publica los clips destacados en vants-clips. En `vants-bus/` están el cliente del bus y el Worker `vants-consumer-playvants`, que copia los eventos a `vants_feed`. El Worker `vants` también publica al bus: `inscription.created` al inscribir un equipo, `clip.published` al destacar un clip y `match.finished` al cerrar una partida de bracket |
| [vants-dev-launcher](https://vantsgg.mintlify.site/repos/vants-dev-launcher.md) | Vantsgames y VANTS-BUS | Comparte con Vantsgames el Worker, las Neon Functions, el bot y el agente. Su launcher de escritorio, VANTS HUB, escucha el bus y muestra notificaciones |
| [Vants-Proteger](https://vantsgg.mintlify.site/repos/vants-proteger.md) | Auth0 y Neon | Sitio estático con autenticación Auth0 y datos en Neon |
| [VantsBasededatos](https://vantsgg.mintlify.site/repos/vants-base-de-datos.md) | Neon | Sus migraciones definen el esquema de Neon que usan los demás repositorios |
| [vants-clips](https://vantsgg.mintlify.site/repos/vants-clips.md) | Vantsgames | El Worker `vants` crea un release por cada clip y actualiza `manifest.json` |
| [vants-faceit-webhook](https://vantsgg.mintlify.site/repos/vants-faceit-webhook.md) | FACEIT, Neon y VANTS-BUS | Guarda en Neon cada webhook de FACEIT y publica en el bus los eventos de partidos |
| [flask-agents](https://vantsgg.mintlify.site/repos/flask-agents.md) | VANTS-BUS | Escucha el bus y publica `post.generated` con los textos que redactan los agentes |
| [VANTSNET](https://vantsgg.mintlify.site/repos/vantsnet.md) | VANTS-BUS | Publica los eventos de su prototipo (torneos, inscripciones y resultados) con el cliente TypeScript de `src/lib/bus/`. No comparte base de datos ni código con los demás repositorios |
| [vantsoport](https://vantsgg.mintlify.site/repos/vantsoport.md) | Ninguno | Soporte de la extensión Vants para GitHub Copilot Chat, que trabaja con GitHub y n8n. No usa el bus |

## VANTS-BUS

VANTS-BUS es un Worker de Cloudflare (`vants-bus`) disponible en `https://vants-bus.vants.workers.dev`. Recibe los eventos que publican los proyectos, los guarda en Neon y los entrega a quien los pida. Los eventos no se modifican y se conservan 30 días; sus tablas están en [Bus de eventos](https://vantsgg.mintlify.site/referencia/esquema-base-de-datos.md#bus-de-eventos). Estos repositorios contienen los clientes del bus, no el código del Worker `vants-bus`.

Cada evento tiene un `id` (`evt_` más un ULID, ordenable), un `type`, el `source` (el proyecto que lo publicó), la fecha `ts` y los datos en `data`.

### Dónde corre cada pieza

El diagrama agrupa las piezas según dónde se ejecutan y muestra en qué tablas escribe cada una. Todas las tablas están en una sola base de datos de Neon; [VantsBasededatos](https://vantsgg.mintlify.site/repos/vants-base-de-datos.md) crea una rama por cada PR para probar las migraciones.

```mermaid theme={null}
flowchart TB
  subgraph EXT ["Servicios externos"]
    AU[Auth0]
    FC[FACEIT]
  end
  subgraph CF ["Workers de Cloudflare"]
    WV[Worker vants]
    FW[vants-faceit-webhook]
    BUS[VANTS-BUS]
    CP[vants-consumer-playvants]
  end
  subgraph APPS ["Aplicaciones"]
    WEB[Web de Vantsgames]
    HUB["VANTS HUB (Electron)"]
    FA["flask-agents (Python)"]
  end
  subgraph NEON ["Neon PostgreSQL: una base de datos, con una rama por PR"]
    DOM[("Tablas del dominio: jugadores, torneos, matchmaking...")]
    EV[(vants_events)]
    FEED[(vants_feed)]
    DEDUP[(vants_bus_dedup)]
    FWE[(faceit_webhook_events)]
  end

  WEB -->|API| WV
  WV -->|valida la sesión| AU
  WV -->|lee y escribe| DOM
  WV -->|"publica inscription.created, clip.published y match.finished de brackets"| BUS
  FC -->|"POST /api/webhooks/faceit con su secreto"| FW
  FW -->|guarda cada webhook| FWE
  FW -->|"evita duplicados por event_id"| DEDUP
  FW -->|"publica match.finished, match.scheduled y match.canceled"| BUS
  BUS -->|guarda los eventos| EV
  BUS -->|"long-polling de 20 s: 7 tipos"| HUB
  BUS -->|"long-polling de 20 s: 9 tipos"| FA
  FA -->|publica post.generated| BUS
  BUS -->|"cada 15 min: todos los tipos"| CP
  CP -->|copia los eventos| FEED
```

En `main`, la web todavía no lee `vants_feed`.

### Proyectos conectados

Cada proyecto se identifica con su nombre en la cabecera `X-VANTS-Project` y firma con su propio secreto.

| Proyecto | Repositorio | Qué hace en el bus |
| - | - | - |
| `faceit-webhook` | vants-faceit-webhook | Publica los eventos de partidos que llegan de FACEIT |
| `flask-agents` | flask-agents | Escucha nueve tipos de evento y publica `post.generated` |
| `vants-launcher` | vants-dev-launcher | VANTS HUB escucha siete tipos de evento y muestra notificaciones |
| `playvants-web` | Vantsgames | El Worker publica inscripciones, clips y resultados de bracket, y `vants-consumer-playvants` copia todos los eventos a `vants_feed` cada 15 minutos |
| `vantsnet` | VANTSNET | Publica los eventos de torneos, inscripciones y partidas de su prototipo |

Todos se configuran con `VANTS_BUS_URL` y `VANTS_BUS_SECRET`. Si faltan, la conexión con el bus queda apagada y el resto del proyecto funciona igual.

### Quién publica y quién consume cada evento

El contrato tiene 12 tipos de evento, definidos en la constante `EVENTS` de cada cliente. El diagrama y la tabla indican qué código los publica y los consume hoy. Además de los consumidores de la tabla, `vants-consumer-playvants` copia todos los tipos a `vants_feed`.

En el diagrama, el color de cada tipo indica quién lo consume: azul, flask-agents y VANTS HUB; verde, solo flask-agents; amarillo, solo VANTS HUB, y gris, nadie todavía.

```mermaid theme={null}
flowchart LR
  subgraph PUB ["1 · Publican"]
    P1[VANTSNET]
    P2[vants-faceit-webhook]
    P3["Vantsgames (Worker vants)"]
    P4[flask-agents]
  end
  subgraph BUS ["2 · VANTS-BUS: contrato de 12 tipos"]
    E1[tournament.inscriptions_opened]
    E2[match.scheduled]
    E3[match.finished]
    E4[match.canceled]
    E5[alert.detected]
    E6[clip.highlights_ready]
    E7[game.session_started]
    E8[inscription.created]
    E9[clip.published]
    E10[post.generated]
    E11[game.session_ended]
    E12[anticheat.passed]
  end
  subgraph CON ["3 · Consumen"]
    C1["flask-agents: escucha 9 tipos"]
    C2["VANTS HUB: escucha 7 tipos"]
    C3["vants-consumer-playvants: copia los 12 tipos a vants_feed cada 15 min"]
  end
  P1 --> E1 & E2 & E3 & E8
  P2 --> E2 & E3 & E4
  P3 --> E3 & E8 & E9
  P4 --> E10
  BUS -->|"los 6 azules y los 3 verdes"| C1
  BUS -->|"los 6 azules y post.generated"| C2
  BUS -.->|todos| C3
  classDef ambos fill:#dbeafe,stroke:#2563eb,color:#1e3a8a
  classDef agentes fill:#dcfce7,stroke:#16a34a,color:#14532d
  classDef hub fill:#fef9c3,stroke:#ca8a04,color:#713f12
  classDef nadie fill:#f3f4f6,stroke:#9ca3af,color:#4b5563
  class E1,E2,E3,E4,E5,E6 ambos
  class E7,E8,E9 agentes
  class E10 hub
  class E11,E12 nadie
```

| Evento | Lo publica | Lo consumen |
| - | - | - |
| `match.finished` | vants-faceit-webhook, Vantsgames (brackets de torneos) y VANTSNET | flask-agents y VANTS HUB |
| `match.scheduled` | vants-faceit-webhook y VANTSNET | flask-agents y VANTS HUB |
| `match.canceled` | vants-faceit-webhook | flask-agents y VANTS HUB |
| `post.generated` | flask-agents | VANTS HUB |
| `tournament.inscriptions_opened` | VANTSNET | flask-agents y VANTS HUB |
| `clip.highlights_ready` | Ninguno todavía | flask-agents y VANTS HUB |
| `alert.detected` | Ninguno todavía | flask-agents y VANTS HUB |
| `inscription.created` | Vantsgames y VANTSNET | flask-agents |
| `clip.published` | Vantsgames | flask-agents |
| `game.session_started` | Ninguno todavía | flask-agents |
| `game.session_ended` | Ninguno todavía | Ninguno todavía |
| `anticheat.passed` | Ninguno todavía | Ninguno todavía |

El Worker `vants` publica desde `vants-server/worker.js` con el cliente de `vants-bus/`, y la API de torneos de VANTSNET publica desde `src/lib/bus/`. Los dos envían sin bloquear la respuesta y quedan apagados si falta su secreto. El ejemplo `snippet-worker.js` sigue en `vants-bus/` como referencia.

### Datos de los eventos publicados

| Evento | Campos de `data` |
| - | - |
| `match.finished` | `matchId`, `game`, `a` y `b` (equipos), `sa` y `sb` (marcador de cada equipo) y `finishedAt` |
| `match.scheduled` | `matchId`, `game` y `startsAt` |
| `match.canceled` | `matchId`, `game` y `canceledAt` |
| `post.generated` | `channel` (`discord`), `kind` (`match_result` o `tournament_open`), `text` y, en los resultados, `matchId` |
| `tournament.inscriptions_opened` | `tournamentId` (slug del torneo), `nombre` y `game` |
| `inscription.created` | `tournamentId` (slug del torneo), `teamId` (equipo inscrito) y `game` |
| `clip.published` | `clipId`, `titulo`, `juego`, `nick`, `video` y `thumb` |

En los eventos de partidos, `game` va en minúsculas y `csgo` se convierte en `cs2`. La correspondencia con los webhooks de FACEIT está en [vants-faceit-webhook](https://vantsgg.mintlify.site/repos/vants-faceit-webhook.md).

### Recorrido de un resultado

```mermaid theme={null}
sequenceDiagram
  autonumber
  actor F as FACEIT
  participant W as vants-faceit-webhook<br/>(Worker de Cloudflare)
  participant N as Neon<br/>(PostgreSQL)
  participant B as VANTS-BUS<br/>(vants-bus.vants.workers.dev)
  participant A as flask-agents<br/>(Python)
  participant H as VANTS HUB<br/>(Electron, escritorio)
  participant C as vants-consumer-playvants<br/>(Worker, cron cada 15 min)

  F->>W: POST /api/webhooks/faceit<br/>match_status_finished + secreto
  W->>N: INSERT INTO faceit_webhook_events<br/>(event_type, payload)
  W-->>F: 200 {ok: true, stored: true}

  rect rgba(37, 99, 235, 0.1)
    Note over W,B: Publicación en segundo plano (ctx.waitUntil),<br/>sin duplicados por event_id y hasta 3 intentos con backoff
    W->>B: publishBatch match.finished<br/>{matchId, game, a, b, sa, sb, finishedAt}
    B->>B: guarda el evento en vants_events
  end

  rect rgba(22, 163, 74, 0.1)
    Note over A,H: flask-agents y VANTS HUB mantienen la lectura<br/>abierta hasta 20 segundos (long-polling)
    B-->>A: match.finished
    B-->>H: match.finished
    H->>H: notificación Resultado verificado
    A->>A: redacta Resultado verificado:<br/>equipo A marcador - marcador equipo B
    A->>B: publish post.generated (channel: discord)
    B-->>H: post.generated
    H->>H: notificación Nueva publicación
  end

  rect rgba(234, 88, 12, 0.1)
    Note over N,C: El cursor se deriva de max(id) de vants_feed<br/>INSERT con ON CONFLICT DO NOTHING (idempotente)
    C->>B: GET /events?since=cursor&limit=200
    B-->>C: eventos nuevos del bus
    C->>N: INSERT INTO vants_feed (id, type, source, ts, data)
  end
```

* **Pasos 1 a 5.** vants-faceit-webhook guarda el webhook en Neon, responde `200` a FACEIT y publica `match.finished` en segundo plano. Si FACEIT repite el webhook, no se publica dos veces. Si el bus falla, lo intenta hasta 3 veces; si aun así no lo consigue, libera el `event_id` para que un nuevo envío de FACEIT pueda publicarlo.
* **Pasos 6 a 12.** VANTS HUB muestra una notificación por cada evento. flask-agents redacta el resumen (`Resultado verificado: <equipo A> <marcador> - <marcador> <equipo B>`) y lo publica como `post.generated`.
* **Pasos 13 a 15.** Cada 15 minutos, `vants-consumer-playvants` copia los eventos nuevos a `vants_feed`. Su cursor es el `id` más alto de esa tabla, así que si una vuelta falla, la siguiente vuelve a leer los mismos eventos sin duplicarlos. La web todavía no lee `vants_feed`.

flask-agents y VANTS HUB mantienen abierta cada lectura hasta 20 segundos (long-polling), así que reciben los eventos casi en tiempo real.

## Leer y publicar eventos

### Firma de las peticiones

Cada petición al bus lleva tres cabeceras:

| Cabecera | Valor |
| - | - |
| `X-VANTS-Project` | Nombre del proyecto, por ejemplo `flask-agents` |
| `X-VANTS-Timestamp` | Hora actual en segundos Unix |
| `X-VANTS-Signature` | `hex(hmac_sha256(secreto, "<timestamp>.<cuerpo>"))`. En `GET` el cuerpo está vacío y se firma `<timestamp>.` (con el punto final) |

### Rutas

| Método | Ruta | Uso |
| - | - | - |
| `POST` | `/events` | Publica un evento (`{"type": "...", "data": {...}}`) o varios a la vez (`{"events": [...]}`) |
| `GET` | `/events` | Devuelve los eventos posteriores al cursor `since`. Admite `types` y `sources` (separados por comas), `limit` y `wait` (segundos de espera para long-polling). La respuesta trae `events`, el `cursor` para la siguiente lectura y `hasMore` si quedan más |

### Clientes

| Cliente | Dónde está | Para |
| - | - | - |
| `vants-bus-client.js` | `vants-bus/` de Vantsgames y de vants-dev-launcher, `src/` de vants-faceit-webhook | Cloudflare Workers, Node 18 o superior y el proceso principal de Electron |
| `vants_bus_client.py` | `vants_bus/` de flask-agents | Python, solo con la biblioteca estándar |
| `vants-bus.ts` | `src/lib/bus/` de VANTSNET | TypeScript en la API de Next.js del prototipo |

```js theme={null}
import { VantsBus, EVENTS } from './vants-bus-client.js';

const bus = new VantsBus({
  busUrl: env.VANTS_BUS_URL,
  projectId: 'playvants-web',
  secret: env.VANTS_BUS_SECRET,
});

// Publicar un evento
await bus.publish(EVENTS.CLIP_PUBLISHED, { clipId: 'abc' });

// Leer los eventos nuevos desde el último cursor guardado
const { events, cursor } = await bus.fetchEvents({
  since: cursorGuardado,
  types: [EVENTS.MATCH_FINISHED],
  limit: 100,
});
```

> [!WARNING]
>
> El secreto solo se usa del lado del servidor: en un Worker, en el proceso principal de Electron o en un backend. No lo incluyas en el código que se envía al navegador.

> [!NOTE]
>
> Cloudflare rechaza con `403` (error 1010) los `User-Agent` genéricos, como el de `Python-urllib`. Envía uno propio, como hacen los clientes.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.