torneo-bdmnt

Torneo en directo

Web para seguir desde el móvil cualquier torneo de badminton.es: tu próximo partido, qué se juega en cada pista, horario, cuadros y liguillas, clubes, medallero y estadísticas. Los datos llegan con unos 2 minutos de retraso como mucho respecto a badminton.es.

La web no tiene datos propios: lee en cada momento los de badminton.es, los reorganiza pensando en el jugador que está en el pabellón con el móvil, y calcula lo que badminton.es no enseña (clasificaciones de grupo, medallas, estadísticas, horas estimadas). Es una web no oficial: ante cualquier duda, manda la organización.

Idea original: ESC26.

Cómo funciona

 móvil (navegador)  ──►  Cloudflare Worker  ──►  badminton.es
   public/                 src/index.js           (Tournament Software)
   pinta la web y          sirve la web y
   lee el HTML             guarda copias unos
                           segundos
Pieza Dónde Qué hace
La web public/ Lo que se ve. Pide las páginas de badminton.es a través del Worker, las convierte en datos y dibuja las pantallas. HTML, CSS y JavaScript sin dependencias.
El Worker src/index.js y wrangler.toml Un programa pequeño en Cloudflare. Entrega los archivos de la web y hace de intermediario con badminton.es.
La publicación .github/workflows/deploy.yml Cada cambio que se sube a main pasa los tests y se publica solo en Cloudflare.

Por qué hace falta el Worker: un navegador no puede leer páginas de otra web (badminton.es) desde la nuestra; el navegador lo bloquea. El Worker está en el mismo dominio que la web, así que la web le pide los datos a él y él los pide a badminton.es.

Por qué la web procesa el HTML y no el Worker: el plan gratuito de Cloudflare deja muy poco tiempo de cálculo por petición. Así el Worker solo reenvía páginas y el móvil hace el trabajo.

El Worker (src/index.js)

Todo lo que no empieza por /api/ son los archivos de la web, que se sirven tal cual (la versión compactada de public/ que prepara scripts/build.mjs en dist/). Las rutas /api/, que solo responden a la propia web (ver «Protección del código»), son estas:

Ruta Qué pide a badminton.es Se guarda
/api/config Nada: devuelve la lista de torneos de wrangler.toml —
/api/tournament/<id> Página del torneo: nombre, días, lugar y nota del organizador 10 min
/api/clubs/<id> Lista de clubes 10 min
/api/draws/<id> Lista de cuadros 5 min
/api/league/<liga> Liga: nombre, temporada y todas las categorías 1 día
/api/standings/<liga>/<cuadro> Liga: clasificación de un grupo 5 min en jornada, 1 día si no
/api/fixtures/<liga>/<cuadro> Liga: todas las eliminatorias de un grupo o de un play-off 5 min en jornada, 1 día si no
/api/tie/<liga>/<eliminatoria> Liga: los partidos de una eliminatoria entre dos clubes 5 min si es de hoy o de ayer, 1 día si no
/api/clubplayers/<id>/<club> Jugadores de un club en el torneo; antes de los cuadros es lo que dice de qué club es cada inscrito 10 min
/api/day/<id>/<AAAAMMDD> Todos los partidos de ese día 30 s (1 h si el día ya pasó)
/api/draw/<id>/<n> Un cuadro de eliminación 45 s
/api/players/<id> Inscritos, reducidos a una lista [id, nombre, apellidos] 10 min
/api/player/<id>/<n> Perfil, licencia y nombre completo de un jugador; para eso entra también en su ranking 1 día
/api/h2h/<id>/<socios> Historial entre los dos lados de un partido, de todos los torneos (la página «H2H» de badminton.es). <socios> son los números de socio del partido, T1P1-T1P2-T2P1-T2P2 (0 si no hay segundo jugador), que salen de su enlace H2H 6 h
/api/career/<id>/<perfil> La «Visión general» del perfil de un jugador en badminton.es: partidos jugados, ganados y perdidos de toda su carrera y del año, en individual, dobles y mixto. <perfil> es el id de perfil que se guarda con «Soy yo» 1 h
/api/live/<id> Solo en los torneos con botón «LiveScore»: el tanteo punto a punto de cada pista. No sale de badminton.es sino del servidor de LiveScore de Tournament Software (src/livescore.js); se entrega ya como JSON pequeño. La web lo pega a cada partido en juego 10 s
/api/batch?p=<ruta>,<ruta>,... Varias de las anteriores (hasta 12, sin la ficha de jugador) en una sola petición; cada ruta sin /api/, por ejemplo day/<id>/20261004 lo de cada una

Reglas del Worker:

La web (public/)

index.html es el esqueleto: cabecera, contenido y barra de pestañas. css/app.css tiene los estilos, con los colores como variables al principio y modo oscuro automático. La lógica está en cuatro archivos de js/:

parse.js: del HTML de badminton.es a datos

Lee las páginas buscando cada pieza por su clase CSS, igual que la vería una persona:

data.js: carga, refresco y modelo

logic.js: los cálculos

Clasificaciones de liguilla, medallas, números de cada jugador y estadísticas del torneo. Las reglas están en Reglas que calcula la web.

app.js: las pantallas

Elige la pantalla según lo que va detrás de # en la dirección: #inicio, #horario, #cuadros, #clubes, #datos, #perfil, #tv (pantalla para el pabellón), y las fichas #j=<jugador>, #c=<club> y #d=<cuadro>. Cada vez que cambia algo vuelve a pintar la pantalla entera, conservando dónde estabas en la página, el control que tenía el foco y lo que estuvieras escribiendo en el buscador.

La liga (liga.js)

La Liga Nacional de Clubes tiene su propio apartado, ?liga, al que se llega desde la tarjeta «Liga Nacional de Clubes 2026-2027» de arriba del selector. Ahí salen todas las categorías de la liga, leídas de su página de badminton.es (/api/league/<liga>, una vez al día). Por ahora solo se puede abrir la División de Honor (?liga=division-de-honor); las demás (Primera Oro, Plata y Bronce, Segunda Oro) empiezan en enero de 2027 y salen como «Próximamente» hasta que se activen.

Peticiones: cada vez se pide junto, en una sola petición al Worker, la clasificación y las eliminatorias de cada cuadro (6 páginas para la División de Honor). El Worker guarda cada página para todos los que miran: 5 min si trae alguna eliminatoria de hoy o de ayer, y 1 día si no. La web las vuelve a pedir cada 5 min el fin de semana en que se juega (hoy hay eliminatoria, o ayer y falta algún resultado) y una vez al día el resto de días. La categoría (qué grupos y play-off tiene) se mira una vez al día. Los partidos de una eliminatoria solo se piden al abrirla: cada 5 min si es de hoy o de ayer, una vez al día si aún queda lejos; una vez jugada, se guardan en el móvil y no se vuelven a pedir. Así, aunque la mire mucha gente, a badminton.es le llegan como mucho unas 70 peticiones por hora en jornada y, el resto de días, una por página y día. (El Worker guarda las páginas en memoria y Cloudflare la vacía de vez en cuando, así que alguna página se puede pedir algo más de una vez al día.)

Las pantallas

Tutorial (tour.js): la primera vez que se entra en la web, en la página principal (la de todos los torneos) o directamente en un torneo, sale encima un carrusel de 10 tarjetas, cada una con un esquema de la web hecho con HTML y CSS: qué es la web, de dónde salen los datos (de badminton.es: si la organización no los actualiza allí, aquí tampoco cambian, y la web no se hace responsable), «Soy yo», la tarjeta de Inicio que cambia de color cuando te toca, corazón y estrella, «Los míos» en Horario, cuadros, clubes y datos, la ficha de jugador, torneos y liga, y cómo tenerla como una app. Esta última explica cómo añadirla a la pantalla de inicio en Android (Chrome), iPhone con Safari e iPhone con Chrome, con el navegador del móvil elegido de entrada; en Chrome de Android, si el navegador lo permite, tiene además un botón «Instalar ahora». Si la web ya está abierta como app, esa tarjeta no sale. Se pasa deslizando, con «Siguiente» o con las flechas; «Saltar» o Escape lo cierran. Sale una sola vez por móvil: se apunta como visto en cuanto aparece. No sale en la liga ni en la pantalla del pabellón, y se puede volver a ver desde la página principal o desde Mi perfil. Para volver a enseñárselo a todos (por ejemplo, con una función nueva importante), se sube TOUR_V.

Inicio

Tres marcas de jugador, cada una con su color en los partidos (borde izquierdo de la tarjeta, el nombre resaltado, el cuadro y la fila de la clasificación):

Un jugador es seguido o tutelado, no las dos cosas: al marcar el corazón se quita la estrella y al revés. Si un partido tiene varios jugadores marcados, manda el más importante (tú, luego tutelado, luego seguido). “Finalizado” y los sets ganados van en gris para no confundirse con el verde de tus partidos.

Horario

Cuadros: agrupados por categoría y modalidad. Cada cuadro lleva el color de quien juega en él: verde si juegas tú, morado si juega un tutelado y naranja si juega un seguido (manda el más importante). Antes de que salgan los cuadros, igual con las pruebas de inscritos.

Clubes: tabla con jugadores, ganados–perdidos y medallas. La ficha de cada club tiene sus jugadores, sus próximos partidos y sus resultados.

Datos: cifras del torneo, líderes, récords, medallero por clubes, clubes por victorias y resumen por categoría, con filtro por categoría. En cada lista de líderes salen los 5 primeros; si has dicho «Soy yo» y no estás entre ellos, debajo sale tu puesto (de cuántos), y si estás, tu fila va marcada con «Tú».

Ficha de jugador: club, pruebas, medallas, números, botones a sus cuadros, todos sus partidos, y los botones Seguir, Soy yo, Compartir y Añadir al calendario.

Contra su próximo rival (en la ficha, y una línea en tu tarjeta de Inicio): el balance y los últimos partidos entre los dos, en cualquier torneo, sacados del historial «H2H» de badminton.es.

Añadir al calendario genera en el propio móvil un archivo .ics (js/ics.js) con los partidos que le quedan al jugador:

Mi perfil: tu nombre, tu licencia, el enlace a tu perfil de badminton.es, “No soy yo” y los torneos de esta web en los que juegas. Dentro de un torneo es la última pestaña; en la página principal y en la liga, que no tienen barra abajo, se abre con el botón redondo de arriba a la derecha (tus iniciales, o un muñeco si aún no has dicho quién eres), que lleva a ?perfil: el mismo perfil, sin las cosas de un torneo concreto. Si aún no has dicho quién eres, Mi perfil tiene un buscador con los inscritos de todos los torneos de la web (se piden la primera vez que se abre, y luego se guardan en el móvil como los de «Tus torneos»): sale cada nombre con en cuántos torneos aparece, y «Soy yo» guarda tu perfil a partir del más reciente.

Selector de torneos: sale al abrir la web sin ?t=. Primero los torneos en los que juegas (si tienes perfil), en un desplegable que sale plegado con el siguiente a la vista (el móvil recuerda si lo dejaste abierto), luego todos los torneos en un solo calendario por meses, cada mes en un desplegable. Arriba, un desplegable discreto «Torneos pasados» con los meses anteriores (el más reciente primero); después el mes en curso, el único abierto al entrar, que lleva arriba otro «Torneos pasados» con los ya terminados de ese mes; y luego los meses que vienen. Lo que abras o cierres no se recuerda entre visitas. Dentro de cada mes, por semanas de lunes a domingo con su número (la semana ISO, la que se usa en el calendario de bádminton) y sus fechas; la semana en curso lleva «Esta semana». Cada semana va entera al mes en que cae su sábado, para que un fin de semana entre dos meses no salga partido.

Reglas que calcula la web

Mi perfil: te reconoce en todos los torneos

  1. Al pulsar Soy yo en una ficha, la web pide al Worker los datos de ese jugador. El Worker entra en su ranking de badminton.es y saca su perfil y su número de licencia, que no cambian de un torneo a otro. El id de jugador sí cambia en cada torneo.
  2. Eso se guarda solo en el móvil.
  3. Al abrir otro torneo, la web busca entre los inscritos a alguien con tu nombre y comprueba que su ficha lleva a tu mismo perfil. Si coincide, te reconoce sin volver a pulsar nada. Mientras el torneo no haya terminado, lo vuelve a mirar como mucho cada 30 min, por si te inscribes después.
  4. Tu carrera: con el perfil, Mi perfil enseña el balance de toda tu carrera según badminton.es (partidos, ganados y perdidos, en total y este año, por modalidad), igual para cada tutelado. Se lee de la «Visión general» de tu perfil allí y se guarda en el móvil 1 hora.
  5. No soy yo lo borra todo. Si en algún torneo apareces con otro nombre, basta con pulsar Soy yo allí.

Qué se guarda en el móvil

Todo va en el localStorage del navegador, con el prefijo tb::

Además, sw.js (un service worker) guarda en la caché del navegador los archivos de la web (HTML, CSS, JS, fuentes e iconos), para que abra aunque no haya cobertura. Pide primero la versión nueva y, si la red no responde en 3 s, usa la guardada; así cada cambio publicado llega solo. Los datos de /api/ no pasan por él.

No hay cookies, cuentas ni base de datos de usuarios, y nada de esto sale del móvil. Para reconocerte solo se piden a badminton.es páginas públicas de jugadores.

Puesta en marcha (una sola vez)

  1. Token de Cloudflare. En el panel de Cloudflare ve a My Profile → API Tokens → Create Token y usa la plantilla Edit Cloudflare Workers. Copia el token.
  2. Id de la cuenta. En el panel, en Workers & Pages, el Account ID está en la columna de la derecha.
  3. Secretos en GitHub. En este repositorio ve a Settings → Secrets and variables → Actions → New repository secret y crea:
    • CLOUDFLARE_API_TOKEN con el token;
    • CLOUDFLARE_ACCOUNT_ID con el id.
  4. Publicar. Ve a Actions → Publicar en Cloudflare → Run workflow. La dirección de la web sale al final del registro de la Action, en el paso “Run cloudflare/wrangler-action”, y también en el panel de Cloudflare (Workers & Pages → torneo-bdmnt). Ahora es https://torneo-bdmnt.javiergar1315.workers.dev.

A partir de ahí, cada cambio que se suba a main se publica solo en uno o dos minutos. Esta web no funciona en GitHub Pages: Pages solo sirve archivos y no puede ejecutar el Worker, que es de donde salen los datos.

Avisos de «Solicita que lo añadamos»

El formulario para pedir un torneo (en Novedades y al final de la lista de torneos) lo recibe el Worker (src/solicitud.js) y avisa por Telegram y por correo. Necesita cuatro secretos en Cloudflare: en el panel, Workers & Pages → torneo-bdmnt → Settings → Variables and Secrets → Add, tipo Secret (se mantienen entre publicaciones y no van al repositorio):

Con uno de los dos canales basta; si no hay ninguno, quien envía el formulario ve que no se ha podido enviar. En local (dev/server.py) no se avisa a nadie: la solicitud sale en la consola.

Dentro de la web del club (badmintonburgos.es/torneos)

La web vive también en https://badmintonburgos.es/torneos. La web del club (repositorio badmintonburgos/badminton-burgos, en Vercel) pasa por detrás todo lo que llega a /torneos a este Worker. El visitante solo ve la dirección del club, y las visitas cuentan como visitas a su web.

badmintonburgos.es/torneos...  →  Vercel (web del club)  →  Worker (torneo-bdmnt...workers.dev)  →  badminton.es

En la web del club hay dos cambios:

Lo que hace esta web para vivir ahí:

Redirección desde workers.dev. En wrangler.toml, PUBLIC_URL = "https://badmintonburgos.es/torneos" y REDIRECT = "si": quien abra una página en la dirección de workers.dev pasa a la del club, con el mismo torneo, jugador o pantalla (?t=... y #...).

Qué cambia para quien ya la usa: lo guardado en el móvil (“Soy yo”, favoritos, tutelados) va ligado a la dirección, así que en la nueva hay que marcarlo otra vez. Y quien la tenga en la pantalla de inicio debe volver a añadirla desde la nueva dirección.

La barra de arriba del club. Lo que llega a través del club lleva además, arriba del todo, la barra de la web del club (logo, menú y Zona privada), para que /torneos se vea como una página más de su web. La pone el Worker (src/clubbar.js, estilos .cb en app.css) y es una copia de src/components/Navbar.jsx del repositorio del club: si cambian los enlaces del menú del club, hay que cambiarlos también en src/clubbar.js. En la dirección de workers.dev no sale. En el móvil, entre el logo y el menú lleva además un botón «Ir web Bádminton Burgos» (la mayoría no abre el menú); en el ordenador no, porque ya está el menú entero. Para verla en local: http://localhost:8787/?club.

Visitas. A lo que llega a través del club se le añaden también los scripts de Vercel Analytics y Speed Insights (/_vercel/insights/script.js y /_vercel/speed-insights/script.js, sin cookies), así que las visitas a /torneos y lo rápido que carga salen en los paneles de Analytics y Speed Insights de la web del club, en Vercel.

Protección del código

El repositorio es privado y todo el código es «todos los derechos reservados» (LICENSE y la sección «Propiedad intelectual» del aviso legal). Además:

Lo que no se puede evitar es que el navegador reciba el código para ejecutarlo. Quien insista puede guardarse la versión compactada, pero no el código original con sus nombres y comentarios.

Añadir torneos

En wrangler.toml, una línea por torneo con un nombre corto y su identificador:

TOURNAMENTS = """
ttr-astorga = 666EE6B4-96EA-41EE-9AD7-9822D1FFEFD2
sub15-leon  = 9A1C22F0-...
"""

Al subir el cambio a main, el torneo nuevo funciona en uno o dos minutos.

Las categorías de la liga van en LEAGUES, una por línea: nombre-corto = <id de la liga>/<número de la categoría>, con el id= y el event= del enlace de la categoría en badminton.es/sport/events.aspx?id=<id de la liga>.

Probar en local

pip install requests
python dev/server.py
# abre http://localhost:8787 (y http://localhost:8787/?t=<nombre-corto> para cada torneo)

dev/server.py hace lo mismo que el Worker, con una caché de 60 s en dev/cache/. Sin argumentos usa los torneos de wrangler.toml y los relee en cada petición, así que un torneo añadido funciona sin reiniciar. También se le pueden pasar otros: python dev/server.py prueba=<id>.

Para usar el Worker de verdad en local: npm install y npx wrangler dev.

Tests

npm install
npm test

Prueban parse.js contra páginas reales de badminton.es guardadas en test/fixtures/: un torneo, sus clubes y cuadros, un día con horario, un día ya jugado (con una retirada), un día en cola por pista, dos cuadros de eliminación y la «Visión general» de un perfil de jugador. Así, si badminton.es cambia su web o un cambio en parse.js rompe algo, se ve aquí y no en el pabellón.

Comprobación diaria contra badminton.es

Los tests usan páginas guardadas, así que no ven si badminton.es cambia su web mañana. Para eso, cada día a las 6:00 (hora UTC) la Action Comprobar badminton.es (.github/workflows/check.yml) ejecuta dev/check.js:

Si algo falla, la Action sale en rojo y GitHub manda un correo a quien modificó por última vez la programación del archivo. El resumen de qué ha fallado está en la página de la Action. Se puede lanzar a mano desde Actions → Comprobar badminton.es → Run workflow, o en local con npm run check (o node dev/check.js http://localhost:8787 contra dev/server.py).

GitHub desactiva las Actions programadas si el repositorio pasa 60 días sin cambios; avisa antes por correo, y se reactivan desde la pestaña Actions.

Archivos del repositorio

Archivo Para qué
public/index.html Esqueleto de la web: cabecera, contenido y pestañas
public/css/app.css Estilos; los colores son variables al principio, con modo oscuro
public/fonts/ Fuentes Inter y Outfit, servidas desde la propia web para no pasar la IP de los visitantes a Google
public/sw.js Guarda los archivos de la web en el móvil para abrir sin cobertura
public/manifest.webmanifest Nombre, icono y modo app al añadir la web a la pantalla de inicio; se abre en el torneo desde el que se añadió
public/legal.html Aviso legal, privacidad y cookies; enlazado desde el pie de todas las pantallas
public/js/parse.js Convierte el HTML de badminton.es en datos
public/js/data.js Carga, refresco, caché del móvil, modelo, “en pista”, horas estimadas y perfil
public/js/logic.js Clasificaciones, medallas y estadísticas
public/js/ics.js Archivo de calendario con los partidos de un jugador
public/js/app.js Pantallas, navegación, botones y refresco automático
public/js/tour.js El tutorial de la primera vez
public/js/liga.js La liga: carga, refresco y pantallas de grupos, play-off, eliminatorias y equipos
scripts/build.mjs Prepara en dist/ la web compactada que se publica
src/index.js El Worker: sirve la web y hace de intermediario con badminton.es
src/clubbar.js La barra de arriba de la web del club, que se añade cuando la web se abre en badmintonburgos.es/torneos
wrangler.toml Configuración del Worker, lista de torneos y categorías de la liga
.github/workflows/deploy.yml Pasa los tests y publica en Cloudflare con cada cambio en main
.github/workflows/check.yml Cada día comprueba que la web sigue sabiendo leer badminton.es
dev/check.js Esa comprobación: lee un torneo real a través de la web publicada
dev/server.py Servidor para probar en local
dev/fixtures.py Crea las páginas anonimizadas de los tests a partir de dev/cache/
test/ Tests de parse.js y del calendario (npm test), y las páginas de ejemplo
PRODUCT.md Para quién es la web y sus principios
DESIGN.md Sistema de diseño: colores, tipografías y componentes
package.json Para tener wrangler en local y pasar los tests (jsdom)

Límites y puntos débiles