MathBattle empezó con una premisa simple: hacer que los niños practiquen matemáticas compitiendo entre ellos en tiempo real. Sin lecciones, sin ejercicios repetitivos. Solo duelos rápidos, puntos y cartas coleccionables.
La premisa era simple. La implementación, no tanto.
El problema de estado distribuido en juegos por turnos rápidos
Un duelo de MathBattle funciona así: dos jugadores ven el mismo problema simultáneamente, el que responde primero y correcto gana el punto. Parece trivial. No lo es.
El primer diseño usaba un modelo cliente-autoritativo: cada cliente registraba su tiempo de respuesta y enviaba {answer, timestamp} al servidor, que decidía el ganador basándose en los timestamps. En pruebas internas funcionaba bien. En producción, con latencias variables, rompía completamente la percepción de justicia.
Un jugador con 20 ms de latencia siempre tenía ventaja sobre uno con 150 ms, aunque respondieran a la vez. Los usuarios lo notaban y lo reportaban como trampa.
Servidor autoritativo + compensación de latencia
Migramos a un modelo completamente servidor-autoritativo con compensación de latencia en cliente. El flujo real:
- El servidor envía el problema con un
serverTimestamp. El cliente registra ellocalTimestampde recepción. - El cliente calcula su estimación del desfase:
delta = localTimestamp - serverTimestamp. - Al responder, el cliente envía
{answer, localAnswerTime}. El servidor recalcula el tiempo efectivo como:effectiveTime = localAnswerTime - delta. - El servidor compara los
effectiveTimede ambos jugadores y declara ganador.
La compensación no es perfecta — los relojes del cliente no son fiables — pero reduciría la ventaja percibida a rangos imperceptibles. También añadimos un margen de empate: si dos respuestas llegan a menos de 80 ms de distancia (efectivo), ambos puntúan.
El resultado: la percepción de fairness en los primeros 1000 usuarios fue claramente mejor. Los reportes de “trampa” cayeron a cero.
El diseño de los bots: el problema de la intención
El mayor reto inesperado fue el sistema de bots. El requisito inicial parecía claro: bots de dificultad fácil, media y difícil que simulen un oponente cuando no hay jugadores disponibles.
Pero cuando lo pusimos en producción, los usuarios — niños de 7 a 12 años — detectaban perfectamente cuándo jugaban contra un bot. No por el comportamiento matemático, sino por lo que no hacían: los bots contestaban siempre en tiempos muy consistentes, sin vaivenes, sin errores humanos, sin el pequeño patrón de frustración que tiene un niño cuando falla tres veces seguidas.
Rediseñamos los bots con tres componentes:
Tiempo de respuesta con ruido gaussiano: en vez de fixedDelay ± pequeña_variación, usamos una distribución que modela comportamiento humano real: respuestas ocasionalmente rápidas, pausas largas esporádicas, aceleración cuando van ganando.
Errores intencionados: los bots de nivel medio y bajo tienen probabilidad de error calibrada — pero no aleatoriamente. Fallan más en categorías específicas (fracciones) y menos en otras (sumas simples), igual que un niño con perfil cognitivo real.
Estado emocional simulado: si el bot pierde 3 seguidas, su tiempo de respuesta aumenta ~15% durante las siguientes 2 preguntas. Si gana, acelera ligeramente. Es una capa de comportamiento que no altera el balance del juego pero hace la experiencia mucho más creíble.
El resultado: cuando preguntamos a usuarios si sabían cuándo jugaban contra un bot vs un humano, el porcentaje de acierto cayó al 58% — prácticamente el azar.
Sistema de cartas coleccionables: el verdadero motor de retención
Las mecánicas de combate fueron relativamente directas de implementar. El sistema de cartas fue donde de verdad tuvimos que pensar en product design.
El sistema tenía que cumplir tres objetivos simultáneos:
- Recompensa variable: las cartas actúan como los loot boxes que hacen los juegos adictivos — pero sin monetización oculta.
- Rareza creíble: el jugador debe percibir que conseguir una carta rara es un logro, no una casualidad injusta.
- Coleccionabilidad sin overwhelm: limitarse a ~60 cartas únicas para que completar la colección sea un objetivo alcanzable para un niño.
Para la distribución de rareza diseñamos un sistema de “pity mechanic” (mecánica de consolación): si llevas 15 batallas sin carta rara, la probabilidad de obtenerla se incrementa linealmente hasta la batalla 25, donde está garantizada. Esto elimina el escenario de 40 batallas sin nada, que genera frustración y abandono.
El impacto en retención al día 7 fue significativo — sin entrar en métricas específicas, fue el cambio con mayor impacto individual de todo el desarrollo.
Infraestructura: Node.js + Socket.io en un servidor stateful
Para el tiempo real elegimos Socket.io sobre WebSockets puros por el fallback automático y el sistema de rooms integrado. Cada duelo activo es una room. El servidor mantiene el estado de la partida en memoria durante la duración del duelo (~3 min máximo).
El mayor riesgo con servidores stateful es la pérdida de estado si el proceso muere. Lo resolvimos con dos medidas:
- Heartbeat cada 2s: si el servidor no recibe heartbeat de un jugador, el duelo se pausa y da 10s para reconectar antes de declarar abandono.
- Snapshot de estado a Redis cada 30s: si el proceso muere y el jugador reconecta, el servidor restaura el estado del último snapshot.
En práctica, la tasa de duelos perdidos por fallo de servidor en los primeros 3 meses fue del 0.04%.
Lo que cambia cuando el usuario real es un niño de 8 años
Construir para niños cambia completamente cómo piensas en gestión de errores, mensajes de estado y latencia tolerable. Un adulto entiende “conexión perdida, reconectando…”. Un niño entiende que el juego se rompió y es frustrante.
Tuvimos que rediseñar todos los estados de error para que fuesen visuales, amigables y, sobre todo, sin culpa al usuario. Un disconnect se convierte en “¡Oops! Tu aventura se pausó un momento…” con un spinner animado. Pequeño detalle que marcaba una diferencia enorme en la percepción del producto.
Esa perspectiva — que el sistema técnico y la experiencia emocional del usuario son inseparables — es algo que no aprendes en ningún tutorial. Solo construyendo productos reales y midiendo lo que ocurre.