Cuando empezamos a construir NimblyMath, el objetivo era claro pero el problema técnico, no tanto: crear una app de entrenamiento cognitivo que se adapte al usuario en tiempo real, sin configuración previa y sin tests de nivel. Que simplemente sepa dónde estás.
Ese “simplemente sepa” fue el origen de unas cuantas semanas difíciles.
El problema con los sistemas de dificultad estática
La mayoría de apps de aprendizaje categorizan a los usuarios en niveles fijos — fácil, medio, difícil — y avanzan cuando se alcanza un porcentaje de acierto. Es predecible, fácil de implementar y… completamente inadecuado para entrenar velocidad cognitiva.
El problema central: la velocidad de respuesta y la precisión son dos dimensiones distintas que evolucionan a velocidades distintas para cada persona y para cada categoría de ejercicio. Alguien puede ser rápido con aritmética y lento con memoria operativa. Un sistema de niveles estático no captura eso.
Necesitábamos un modelo que representase el estado cognitivo de cada usuario en seis dimensiones independientes, y que actualizara ese modelo con cada respuesta.
Teoría de Respuesta al Ítem como base
El primer diseño que descartamos fue el más obvio: un sistema de puntuación acumulativa con umbrales de subida. Demasiado rígido y no refleja el componente temporal.
Optamos por un enfoque inspirado en la Teoría de Respuesta al Ítem (IRT), adaptado para incluir el parámetro de tiempo. La idea básica: cada ejercicio tiene una dificultad estimada, y cada respuesta del usuario actualiza un parámetro de habilidad latente θ (theta) — separado por categoría.
El modelo básico que implementamos es:
P(correcto | θ, b) = 1 / (1 + e^(-(θ - b)))
Donde θ es la habilidad estimada del usuario y b es la dificultad del ítem. La novedad: añadimos una penalización logarítmica sobre el tiempo de respuesta que reduce la probabilidad efectiva cuando el usuario tarda demasiado, aunque acierte.
Esto nos permitió separar dos señales que antes se mezclaban: sé hacerlo, pero lento frente a sé hacerlo y soy rápido. Para entrenar velocidad cognitiva, esa diferencia importa.
El bucle de calibración en tiempo real
El procesamiento ocurre de la siguiente forma en cada sesión de 10 minutos:
- Pre-sesión: Se recupera el perfil θ del usuario (vector de 6 dimensiones). Se seleccionan los primeros 3 ejercicios de dificultad media para establecer referencia de la sesión actual.
- Durante la sesión: Cada respuesta actualiza el estimado θ usando una actualización bayesiana simplificada (MAP). El siguiente ejercicio se selecciona del pool que maximiza la información de Fischer para el θ actual — básicamente, el ejercicio que más nos dice sobre dónde está el usuario.
- Post-sesión: Se persiste el nuevo θ y se actualiza el historial de velocidad (percentil interno, comparativa semana anterior).
La zona de flujo óptima que buscamos es un margen de ±0.3 σ alrededor del θ actual. Ejercicios en ese rango producen una tasa de acierto de entre 60-75%, lo que, según la literatura de aprendizaje adaptativo, es el punto de mayor retención e implicación.
El error que más tiempo nos costó
Durante las primeras semanas de beta, observamos un patrón extraño: algunos usuarios avanzaban muy rápido al principio y luego el motor se “atascaba” en una dificultad sin subir.
El problema era una asimetría en la actualización. El modelo actualizaba θ más agresivamente al alza (aciertos rápidos) que a la baja (errores o respuestas lentas), lo que llevaba a sobrestimar habilidades iniciales. Cuando llegaban ejercicios auténticamente difíciles, el usuario fallaba y el modelo intentaba bajar θ, pero la inercia acumulada lo impedía.
La solución fue añadir un factor de confianza temporal: cuantas menos respuestas recientes tiene el modelo para una categoría, más conservador es al actualizar θ. Al inicio de sesión, el prior tiene más peso. Tras 15 respuestas, el prior cede completamente a los datos de la sesión.
Pequeño ajuste, pero cambió comportamiento completamente.
Infraestructura y latencia
El motor vive en un microservicio Python (FastAPI) independiente del resto de la aplicación. Razón principal: aislamiento del ciclo de actualización del modelo y posibilidad de escalar independientemente del frontend.
Cada llamada al motor incluye el historial comprimido de la sesión actual + el perfil persistido. El cálculo del siguiente ejercicio tarda en media 8 ms — suficientemente rápido para que el usuario no perciba ninguna pausa entre problemas.
El perfil θ se persiste en Redis con un TTL corto (sesión activa) y se sincroniza a Postgres al final de cada sesión. Eso nos da la combinación de velocidad en sesión y durabilidad entre sesiones.
Lo que aprendemos de esto como equipo
Construir NimblyMath nos enseñó algo que no hubiéramos aprendido haciendo consultoría: la diferencia entre un sistema que funciona y un sistema que se siente bien.
El motor podía ser técnicamente correcto y aun así hacer que el producto se sintiese frustrante. Hubo que iterar con usuarios reales, medir percepción vs. métricas objetivas, y ajustar parámetros que ningún paper te dice cómo calibrar para un producto de consumo.
Eso — esa sensibilidad hacia la experiencia real del usuario mientras mantienes rigor técnico — es exactamente lo que intentamos traer cuando trabajamos con los productos de nuestros clientes.