Blog - Construyendo Hibrido: qué pasa cuando el modelo no puede nombrar un movimiento
Cómo Everseed construyó Hibrido, un entrenamiento híbrido diario publicado en tres idiomas, sacando el vocabulario de ejercicios del prompt y metiéndolo en un catálogo versionado del que el modelo solo puede elegir.
Producto y Ventures
Casi todos los productos de "la IA te escribe el entrenamiento" son un cuestionario, una suscripción y un plan generado para una sola persona. Hibrido lo construimos al revés: un entrenamiento al día, el mismo para todos, publicado cada mañana en español, inglés y portugués, gratis y sin cuenta.
Esa decisión hizo inevitable el problema interesante. Si todos reciben la misma sesión y nadie puede preguntar nada, la sesión tiene que estar bien a la primera, a las seis de la mañana, para un desconocido.
Esto es lo que hicimos al respecto.
"Casi siempre" es el estándar equivocado
Pon "nada de halterofilia, nada de gimnasia de alta dificultad" en un system prompt y un buen modelo lo cumplirá casi siempre. Casi siempre está bien cuando el fallo es una frase torpe. No está bien cuando el fallo es alguien intentando un arranque a solas en un gimnasio comercial porque una web se lo dijo.
Hay un segundo fallo, más aburrido, que solo aparece después de unas semanas. Un modelo que escribe sus propios nombres de movimientos los escribe un poco distinto cada noche, y distinto otra vez en cada idioma. "Empuje de trineo" se vuelve "empujón de trineo", sled push se vuelve sled drive, y los lectores de tres idiomas dejan de hacer poco a poco el mismo entrenamiento.
Los dos problemas tienen la misma forma. Se está confiando el vocabulario al modelo, y el vocabulario es justo la parte que tiene que ser estable.
La solución: un catálogo, no un prompt
Así que se lo quitamos.
Hibrido tiene un catálogo fijo de 59 movimientos, versionado en el repositorio y ya traducido a los tres idiomas. Cada movimiento lleva las variantes de equipo en las que existe. Al modelo se le entrega ese catálogo y compone la sesión eligiendo ids. Nunca escribe el nombre de un movimiento.
La salida se valida contra el catálogo antes de poder guardarse. Un id desconocido se rechaza. Un id conocido usado en una variante de equipo donde no existe se rechaza. Ante un rechazo, el generador reintenta con los errores de validación adjuntos, tres veces, y después falla la ejecución en lugar de guardar algo degradado.
La diferencia no es sutil. "Por favor evita X" es una petición. "X no está en el catálogo, y lo que está fuera del catálogo no parsea" es una restricción. Solo una de las dos sigue en pie la noche número cuatrocientos.
Esa es la lección general, y sirve fuera del fitness: lo que tu producto no se pueda permitir que sea inventado, sácalo del prompt y ponlo en datos de los que el modelo solo pueda elegir. El prompt es donde pones el criterio. El repositorio es donde pones los hechos.
Dos llamadas, para que la traducción no desvíe el entrenamiento
La generación ocurre en dos llamadas, y el orden importa.
La primera produce la estructura neutra al idioma: qué movimientos, cuántas repeticiones, qué cargas, qué dominios de tiempo y en qué orden. Sin prosa, sin indicaciones, sin idioma más allá de los ids.
La segunda escribe las indicaciones en los tres idiomas contra esa estructura ya fijada. Puede explicar cómo debería sentirse la sesión, dónde contenerse y dónde apretar. No puede cambiar ni un número, porque los números se decidieron antes de que se ejecutara.
La traducción suele ser el punto donde el contenido multilingüe diverge en silencio, porque cada idioma se genera como su propio artefacto y lo revisa quien casualmente lee ese idioma. Separar estructura y prosa elimina la posibilidad en vez de auditarla. Quien lee en portugués y quien lee en inglés están mirando la misma sesión por construcción.
Al modelo de imagen se le cuenta todavía menos
Hibrido renderiza sus propias imágenes: una pieza 16:9 para el sitio y una 4:5 para redes, las dos con la tipografía deliberadamente fuera para poder poner texto encima.
Al modelo de imagen nunca se le dice cómo se llama el entrenamiento. Recibe una descripción física del movimiento dominante del día, sacada de un archivo con una línea por cada id del catálogo. Un test rompe el build cuando un id del catálogo no tiene escena, así que los dos archivos no pueden separarse.
De ahí salen dos cosas. Las exclusiones valen para la foto igual que para la sesión, porque solo existe escena para un movimiento del catálogo. Y la prosa del entrenamiento nunca llega a un modelo que la pintaría encantado en la esquina de un encuadre hecho para llevar texto real encima.
Quién sale en esas fotos viene de una tabla de Postgres: atletas inventados, una fila cada uno, con una descripción física y un retrato de referencia renderizado a partir de ella. Los días rotan por el reparto, y cuando la persona tiene retrato se le envía al modelo de imagen junto al prompt, limitado solo a la identidad. Una semana de imágenes son reconociblemente las mismas caras y no siete desconocidos que encajaron con siete frases. Si la tabla está vacía o no responde, la generación cae a una lista interna de descripciones: sin rostro, pero las imágenes siguen saliendo.
Una etapa tiene permiso para fallar
La estructura y el texto se rinden antes que degradarse. La etapa de imagen hace lo contrario: si falla, el fallo se registra en su propia etapa y el entrenamiento se guarda igual.
Esa asimetría es deliberada, y conviene dejarla escrita en cualquier pipeline con varias llamadas a modelos. Pregúntale a cada etapa: ¿es peor una salida que falta que una equivocada? Para la sesión, una equivocada es muchísimo peor, así que falla en alto. Para la imagen, una que falta cuesta un fondo, así que falla en silencio y el producto sigue funcionando.
Publicar con dos días de antelación
El proceso nocturno corre a las 08:00 UTC y genera dos días por delante.
Los entrenamientos generados se guardan como publicados de inmediato, lo que suena temerario hasta que ves la segunda regla: una fecha solo se muestra cuando ya es ese día en algún punto de la tierra. Así hay una ventana de 48 horas en la que una sesión existe, se puede revisar en el panel y no la ve nadie. Una ejecución fallida, una caída del modelo o una sesión que simplemente se lee mal tienen dos días para ser cazadas y retiradas.
Qué fecha es la tuya es otra pregunta, y la home la responde desde la zona horaria del lector, resuelta en el servidor a partir de la petición. A quien lee en Vancouver no se le sirve la sesión de mañana en el desayuno, mientras que quien ya está en el día 9 puede abrir el 9 directamente.
El stack
Next.js 16 con React 19, renderizando en servidor cada página fechada. Supabase con Postgres para las sesiones, el reparto y la lista de correo, con la escritura cerrada al rol de servicio. Llamadas a modelos vía OpenRouter, validadas con Zod. Las migraciones son solo hacia adelante y se aplican automáticamente al hacer merge desde CI, con el mismo script que se ejecuta en local, y con las sentencias destructivas rechazadas salvo que el archivo lo permita explícitamente. Desplegado en Vercel, con la generación nocturna en Vercel Cron.
No hay rollback de migraciones, a propósito. Una migración hacia atrás ejecutándose sin supervisión es más peligrosa que el problema que resuelve; la recuperación es una nueva migración hacia adelante, o un restore.
Dónde está ahora
Deliberadamente temprano. El generador corre cada noche, el sitio publica en tres idiomas y el formulario guarda la dirección que le dejes. Todavía no se ha enviado nada: ni correo, ni confirmación, ni baja, porque nada de eso está construido. WhatsApp y las redes son fases posteriores.
Ese orden es todo el argumento. La calidad del entrenamiento es el producto; lo demás es distribución para algo que primero tiene que ser bueno.
Lee la sesión de hoy en hibrido.app, o mira el caso de éxito completo de Hibrido.
