Cuando los datos de referencia se vuelven logica de producto

Las ultimas 24 horas en Nima Project estuvieron muy concentradas en world-cup-porra, world-cup-api y la documentacion raiz:

  • 2026-06-11 17:02 +0200 - Reject favorite teams as surprise predictions
  • 2026-06-11 17:02 +0200 - Refresh competition settings during active pool sync
  • 2026-06-11 20:42 +0200 - Fix competition catalog kickoff import
  • 2026-06-11 20:42 +0200 - Fix World Cup kickoff lock time
  • 2026-06-11 20:54 +0200 - Hotfix tournament start lock
  • 2026-06-11 20:59 +0200 - Fix surprise team favorite filtering
  • 2026-06-12 06:51 +0200 - Fix World Cup match kickoff schedule
  • 2026-06-12 08:34 +0200 - Add FIFA startup sync loader
  • 2026-06-12 08:56 +0200 - Show loader while opening pools
  • 2026-06-12 09:11 +0200 - Document FIFA endpoint reference
  • 2026-06-12 09:14 +0200 - Document localized FIFA match centre URL
  • 2026-06-12 09:52 +0200 - Document FIFA match detail endpoints
  • 2026-06-12 09:56 +0200 - Sync FIFA match bonus details

La lectura rapida seria: se han retocado datos del Mundial y se ha mejorado la carga.

La lectura correcta es otra: en cuanto esos datos empiezan a bloquear votos, disparar sincronizaciones y recalcular bonus, dejan de ser contenido auxiliar y pasan a ser logica de producto.

El problema no era "mostrar mejor datos", era decidir comportamiento

La conversacion de chat que abrio este tramo fue muy concreta:

los equipos favoritos configurados por admin no debian poder elegirse como "equipo revelacion", y si un usuario tenia uno ya guardado habia que invalidarlo

Eso parece una regla pequena de formulario.

No lo era, porque tocaba tres capas a la vez:

Capa Decision Efecto real
Admin surpriseTeamFavorites.teams pasa a ser la lista de exclusion el panel no solo configura contenido; define restricciones
API si el usuario intenta guardar un favorito como revelacion, se limpia ese valor la persistencia deja de aceptar estados que ya no son validos
Frontend el selector debe ocultar esas opciones aunque lleguen con distintas formas de payload la UI no puede depender de un unico nombre de campo si el contrato ya tiene historia

La pista mas pequena del diff del frontend lo dice todo: team-lists.js tuvo que aceptar tambien source.teams.

Un solo campo nuevo parece irrelevante. No lo es cuando el sistema real mezcla:

  • payloads antiguos,
  • payloads normalizados,
  • valores leidos desde admin,
  • y una UI que necesita convertir todo eso en una lista de opciones valida.

El favorito excluido no era una excepcion; era una frontera de producto

El backend world-cup-api hizo el movimiento importante.

No se limito a "validar al guardar". Hizo algo mas fuerte: leer los favoritos persistidos de la edicion y, si el surpriseTeam entraba en esa lista, vaciarlo durante la normalizacion de la previa.

Eso cambia la naturaleza de la regla:

flowchart LR admin["Admin guarda favoritos"] --> edition["competition_editions.payload"] edition --> api["API normaliza la previa"] api --> ui["Frontend sincroniza estado"] ui --> vote["Usuario solo puede votar opciones validas"]

Ya no estamos hablando de un dropdown bonito.

Estamos hablando de una frontera de producto donde la verdad vive fuera del navegador y la app tiene que obedecerla aunque cambie a mitad de torneo.

El segundo aprendizaje fue mas duro: la fecha oficial tambien manda

La otra mitad del trabajo fue aun mas reveladora.

Hubo tres commits consecutivos alrededor del mismo dolor:

  • Fix competition catalog kickoff import
  • Fix World Cup kickoff lock time
  • Hotfix tournament start lock

Eso no es casualidad. Es el patron de un sistema que descubre que estaba tratando los horarios como metadato ligero, cuando en realidad eran politica de negocio.

Porque en una porra los horarios no solo sirven para pintar tarjetas:

  • cierran la creacion de partidas,
  • bloquean predicciones,
  • ordenan el calendario,
  • y determinan si una accion sigue siendo valida o ya llega tarde.

La conversacion posterior lo volvio todavia mas explicito: hubo que corregir los startsAt, localTime y timeZone de los 104 partidos del Mundial, contrastarlos con FIFA oficial y volver a desplegar.

La leccion aqui no es "faltaban horas correctas".

La leccion es esta:

Important

Cuando una fecha decide si un usuario aun puede actuar, esa fecha ya no es contenido. Es logica.

El loader no era cosmético; era una admision de dependencia real

Los commits de la manana del 12 de junio parecen de UX:

  • Add FIFA startup sync loader
  • Show loader while opening pools

Pero su sentido real es otro.

Si al arrancar la app ahora hay que:

  1. validar acceso,
  2. sincronizar con FIFA desde el Worker,
  3. actualizar resultados oficiales via admin API,
  4. y solo despues abrir la porra,

entonces mostrar un loader ya no es maquillaje. Es honestidad del sistema.

Antes la app podia fingir que el estado local bastaba para pintar algo rapido.

Ahora reconoce que hay una dependencia fuerte de runtime: sin datos oficiales suficientemente frescos, la interfaz todavia no sabe en que estado real esta el torneo.

Ese cambio importa porque hace visible una verdad arquitectonica:

  • el frontend ya no arranca solo con assets compilados,
  • el Worker ya no es solo un proxy,
  • y FIFA deja de ser "fuente para mirar" para convertirse en "fuente que condiciona el arranque".

El ultimo paso fue inevitable: si entra el marcador, tambien entran los bonus

El cierre del tramo fue casi automatico:

  • primero se documento la referencia de endpoints FIFA,
  • luego la URL localizada del match centre,
  • despues los endpoints de detalle,
  • y finalmente llego Sync FIFA match bonus details.

Eso tampoco fue un capricho documental.

La secuencia muestra una disciplina correcta:

Paso Lo que se hizo Por que importaba
1 fijar la referencia de endpoints evitar integraciones "por memoria"
2 identificar la ruta localizada correcta distinguir calendario publico de detalle real
3 documentar endpoints de detalle dejar trazabilidad para la siguiente iteracion
4 consumir live/football/{IdMatch} completar scorer y redCard con datos oficiales

La conversacion final fue especialmente precisa:

  • el sync solo asigna scorer si hay un goleador unico,
  • si hay empate guarda scorerTie: true y no reparte bonus injustos,
  • redCard se deriva de Bookings[].Card === 2,
  • y si un partido ya estaba finished con marcador correcto pero sin detalle, se republica para completar esos campos.

Es dificil pedir una prueba mejor de que los datos oficiales ya forman parte de la mecanica del producto.

No adornan la porra.

La puntuan.

La idea central: referencia y runtime ya no se pueden separar

Estas 24 horas dejaron una transicion muy clara:

Antes Ahora
favoritos del Mundial como configuracion de admin favoritos del Mundial como regla que invalida votos
horario del partido como dato de catalogo horario del partido como cierre operativo y de puntuacion
sync con FIFA como mejora deseable sync con FIFA como parte del arranque
detalle del partido como informacion extra detalle del partido como fuente de bonus y correccion

Esa transicion suele pasar desapercibida en proyectos pequenos porque se sigue llamando "datos" a todo.

Pero no todo dato tiene el mismo peso.

En cuanto un dato:

  • habilita o bloquea acciones,
  • limpia estados ya guardados,
  • decide si la pantalla puede abrirse,
  • o recalcula puntos,

ya no es solo referencia. Es comportamiento.

Lo que me llevo de este tramo

  • Una lista de favoritos administrable puede convertirse en una regla dura de validacion.
  • Los horarios oficiales no solo pintan el calendario; tambien gobiernan cierres, locks y orden del sistema.
  • Un loader bien puesto puede ser una declaracion arquitectonica: "todavia no somos coherentes hasta sincronizar".
  • Documentar endpoints antes de estirar la integracion reduce la improvisacion cuando los datos empiezan a tocar puntuacion real.
  • En una app de torneo, marcador, goleador y tarjeta roja no son analitica: son motor de producto.

Estas no fueron 24 horas de features aisladas.

Fueron 24 horas en las que world-cup-porra dejo mas claro que una porra no vive solo de formularios y pantallas. Vive de la calidad de las verdades externas que decide obedecer.

Y cuando esas verdades bloquean, corrigen y puntuan, ya no estamos integrando contenido. Estamos programando reglas de producto con datos oficiales.


Parte de mis notas de producto y plataforma. Sigue el blog o escribeme.