Quiniela de predicciones para el Mundial 2026 (USA · Canadá · México). 48 equipos, 12 grupos, 104 partidos.
Hecho por Julio Servan.
- Next.js 16 (App Router, Turbopack)
- React 19
- TypeScript
- Tailwind CSS v4
- Instrument Serif + Geist (Google Fonts)
- 🏠 Home con countdown al partido inaugural y ticker de equipos
- 📅 Calendario de partidos con vista de Lista y Calendario mensual
- 🏆 Página de los 12 grupos con tabla de posiciones
- 🥊 Bracket de eliminatoria (32 partidos a un encuentro)
- 🎯 Formulario de predicciones con guardado local
- 🏅 Ranking de usuarios
- 🌙 Dark mode con tipografía display italic
npm install
npm run devPor defecto arranca en http://localhost:3000. Para usar otro puerto:
PORT=3100 npm run devLos resultados, la clasificación y el cuadro de eliminatoria se actualizan solos desde API-Football (API-SPORTS). La web nunca llama a la API externa: un poller sincroniza los datos a Supabase y la web lee de ahí.
API-Football ──> /api/sync (cron, server) ──> Supabase ──> web (lee snapshot)
│ ├─ mundial_results (marcador final → quiniela + tabla)
│ ├─ mundial_fixtures (snapshot en vivo + equipos de cada llave)
└─ cuida la cuota ─────────┴─ mundial_meta (liga/temporada, cuota diaria, last_sync)
- Capa de proveedor (
lib/providers/): toda la app habla con la interfazResultsProvideren nuestros tipos. Para cambiar de proveedor, edita un solo archivo (lib/providers/index.ts). - Liga/temporada no se hardcodean: se resuelven vía
/leaguesy se cachean. - Cuota (plan Free ~100 req/día): solo se llama a la API en ventanas de partido activo; se lleva la cuenta diaria y, si se agota, la web sirve el último dato con un aviso "datos quizá con retraso".
- Clasificación con desempates FIFA completos (puntos → DG → GF →
enfrentamiento directo → fair play → sorteo) y ranking de los 8 mejores
terceros (
lib/fifa.ts). - Cuadro (
lib/bracket.ts): los ganadores avanzan solos R32 → R16 → … → Final; las llaves sin definir muestran "Por definir".
-
Consigue una key de API-Football: regístrate en dashboard.api-football.com, copia tu API Key (plan Free disponible).
-
Variables de entorno (copia
.env.example→.env.local). Las de servidor nunca se exponen al navegador:Variable Uso NEXT_PUBLIC_SUPABASE_URL/NEXT_PUBLIC_SUPABASE_ANON_KEYCliente Supabase (navegador) SUPABASE_SERVICE_ROLE_KEYEl poller escribe resultados (solo servidor) APIFOOTBALL_KEYCabecera x-apisports-key(solo servidor)CRON_SECRETProtege /api/syncAPIFOOTBALL_DAILY_CAP(opc.)Tope diario de peticiones (def. 95) -
Base de datos: ejecuta en el SQL Editor de Supabase, en orden:
supabase/schema.sql,supabase/winner-picks.sql,supabase/api-football.sql,supabase/match-detail.sql,supabase/knockout-scoring.sql(puntuación enriquecida de eliminatorias: ganador 1 pt + resultado exacto 3 pts),supabase/predictions-backup.sql(copia de seguridad diaria de pronósticos) ysupabase/penalties.sql(tanda de penales en el snapshot: sin ella el bracket no puede resolver una eliminatoria que acabe en empate). -
Backfill inicial (siembra los partidos para que la web no esté vacía):
npm run backfill
-
Cron:
vercel.jsontrae un cron diario (0 12 * * *) que llama a/api/sync?mode=auto(Vercel envíaAuthorization: Bearer $CRON_SECRET). Es compatible con el plan Hobby, pero solo refresca una vez al día.Para actualización casi en vivo (recomendado), añade un cron externo gratis (p. ej. cron-job.org) cada 1-2 min apuntando a:
https://TU-DOMINIO/api/sync?mode=auto&secret=TU_CRON_SECRETModos:
auto(completa periódica + en vivo durante partidos) ·live(solo marcadores) ·full(recarga todo).
Nota: el plan Hobby de Vercel limita los crons a una vez al día. Para usar el cron de minutos directamente en Vercel necesitas el plan Pro (entonces cambia el
scheduledevercel.jsona* * * * *).
- Panel admin en
/admin/sync: estado del robot (verde/ámbar/rojo), última sincronización, cuota usada (la nuestra y la que reporta el proveedor), liga detectada, equipos sin reconocer y últimos errores. - Endpoint
/api/health: el mismo estado en JSON, para vigilar o alertar. - Reintentos automáticos: el adaptador reintenta ante errores temporales (red / 5xx / 429) con backoff antes de rendirse.
- Forzar acciones:
/api/sync?mode=full&secret=…recarga todo; añade&refresh=1para re-detectar la liga. - Backup de pronósticos: se guarda una instantánea diaria automática en
mundial_pred_backups(la dispara/api/syncuna vez al día). Forzar copia:/api/backup?secret=…. Restaurar un día: leer sudata(JSON) y re-insertar enmundial_predictions.
worldcup/
├── app/ # Rutas Next.js (App Router)
│ ├── matches/ # Calendario (vista lista + calendario)
│ ├── groups/ # 12 grupos
│ ├── eliminatoria/ # Bracket eliminatoria
│ ├── predictions/ # Formulario de quiniela
│ ├── leaderboard/ # Ranking
│ └── login/ # Stub auth (Supabase próximamente)
├── components/ # Componentes reutilizables
│ ├── Navbar.tsx
│ ├── Footer.tsx
│ ├── Countdown.tsx
│ ├── MatchCard.tsx
│ ├── MatchesView.tsx # Toggle lista/calendario
│ ├── MatchesList.tsx
│ ├── MatchesCalendar.tsx
│ ├── KnockoutBracket.tsx
│ ├── PredictionForm.tsx
│ └── TeamMarquee.tsx
├── lib/
│ ├── data/ # Mock data (teams, venues, matches)
│ └── utils/ # Helpers (format, calendar)
└── types/ # TypeScript types
- Integrar Supabase para auth + persistencia
- Sistema de puntuación real (con resultados oficiales)
- Resultados, clasificación y cuadro automáticos (API-Football)
- Grupos privados para competir con amigos
- Predicciones de eliminatoria
- Deploy a Vercel
Uso personal. Datos del torneo basados en información pública.