diff --git a/app/src/app/api/admin/sync/route.ts b/app/src/app/api/admin/sync/route.ts index 8a1095a..0577d2d 100644 --- a/app/src/app/api/admin/sync/route.ts +++ b/app/src/app/api/admin/sync/route.ts @@ -1,4 +1,29 @@ import { NextResponse } from 'next/server' +import { prisma } from '@/lib/prisma' +import { calculatePoints } from '@/lib/scoring' + +interface FDTeam { + tla: string + name: string +} + +interface FDScore { + fullTime: { home: number | null; away: number | null } +} + +interface FDMatch { + id: number + status: 'TIMED' | 'SCHEDULED' | 'IN_PLAY' | 'PAUSED' | 'FINISHED' | 'POSTPONED' | 'SUSPENDED' | 'CANCELLED' + homeTeam: FDTeam + awayTeam: FDTeam + score: FDScore +} + +function mapStatus(s: FDMatch['status']): 'SCHEDULED' | 'LIVE' | 'FINISHED' { + if (s === 'FINISHED') return 'FINISHED' + if (s === 'IN_PLAY' || s === 'PAUSED') return 'LIVE' + return 'SCHEDULED' +} export async function POST(req: Request) { const { searchParams } = new URL(req.url) @@ -8,15 +33,80 @@ export async function POST(req: Request) { return NextResponse.json({ error: 'No autorizado' }, { status: 401 }) } - const url = process.env.RESULTS_API_URL - if (!url) { - return NextResponse.json({ error: 'RESULTS_API_URL no configurada' }, { status: 400 }) + const apiUrl = process.env.RESULTS_API_URL + const apiKey = process.env.RESULTS_API_KEY + if (!apiUrl || !apiKey) { + return NextResponse.json({ error: 'RESULTS_API_URL o RESULTS_API_KEY no configuradas' }, { status: 400 }) } - // TODO: implementar cuando se provea la URL del sitio externo - // const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.RESULTS_API_KEY}` } }) - // const data = await res.json() - // ... procesar y actualizar partidos + let fdMatches: FDMatch[] + try { + const res = await fetch(apiUrl, { + headers: { 'X-Auth-Token': apiKey }, + cache: 'no-store', + }) + if (!res.ok) { + return NextResponse.json({ error: `Error de API externa: ${res.status}` }, { status: 502 }) + } + const data = await res.json() + fdMatches = data.matches ?? [] + } catch { + return NextResponse.json({ error: 'No se pudo conectar con la API externa' }, { status: 502 }) + } - return NextResponse.json({ ok: true, message: 'Endpoint listo. Configurar RESULTS_API_URL para activar sync.' }) + // Precargar equipos una sola vez + const teams = await prisma.team.findMany() + const teamByShortName = Object.fromEntries(teams.map(t => [t.shortName, t])) + + let updated = 0 + let skipped = 0 + + for (const fdMatch of fdMatches) { + const newStatus = mapStatus(fdMatch.status) + if (newStatus === 'SCHEDULED') { skipped++; continue } + + const homeTla = fdMatch.homeTeam?.tla + const awayTla = fdMatch.awayTeam?.tla + if (!homeTla || !awayTla) { skipped++; continue } + + const homeTeam = teamByShortName[homeTla] + const awayTeam = teamByShortName[awayTla] + if (!homeTeam || !awayTeam) { skipped++; continue } + + const match = await prisma.match.findFirst({ + where: { homeTeamId: homeTeam.id, awayTeamId: awayTeam.id }, + }) + if (!match) { skipped++; continue } + + // Evitar re-procesar partidos ya finalizados + if (match.status === 'FINISHED' && newStatus === 'FINISHED') { skipped++; continue } + + const homeScore = fdMatch.score.fullTime.home + const awayScore = fdMatch.score.fullTime.away + + const updatedMatch = await prisma.match.update({ + where: { id: match.id }, + data: { + status: newStatus, + ...(homeScore !== null && { homeScore }), + ...(awayScore !== null && { awayScore }), + }, + }) + + if (newStatus === 'FINISHED' && homeScore !== null && awayScore !== null) { + const predictions = await prisma.prediction.findMany({ where: { matchId: match.id } }) + await Promise.all( + predictions.map(p => + prisma.prediction.update({ + where: { id: p.id }, + data: { points: calculatePoints(p.result as 'HOME' | 'DRAW' | 'AWAY', homeScore, awayScore) }, + }) + ) + ) + } + + updated++ + } + + return NextResponse.json({ ok: true, updated, skipped, total: fdMatches.length }) }