Skip to content

Sesiones de Entrenamiento — Ciclo de Vida Offline-First

Filosofía: el teléfono es la fuente de verdad

Una sesión de entrenamiento vive en el teléfono. Se crea, se edita y se finaliza en la base de datos local de la app, sin requests al servidor: el usuario puede entrenar en el sótano de un gimnasio sin señal, durante semanas si hace falta.

El servidor recibe la sesión — y todo lo demás que el usuario posee: rutinas, carpetas, ejercicios custom — a través del mecanismo de Sync, cuando hay conexión. El sync es bidireccional: la app empuja sus cambios y trae los del servidor (incluido el catálogo de ejercicios, que solo viaja en esa dirección).

Nota histórica. El diseño anterior mantenía la sesión activa en el servidor mediante "draft sync" — snapshots periódicos durante el entrenamiento, con endpoints propios de start/draft/finish. Ese modelo se retiró al adoptar offline-first (agosto 2026): exigía conectividad al menos al empezar y al terminar, y la recuperación ante crash dependía del último snapshot que hubiera alcanzado a subir. Hoy la recuperación es local — el estado nunca salió del teléfono — y la conectividad no es requisito de ningún paso del entrenamiento.

El ciclo de vida

[start local] → [entrenar: sets, ejercicios, supersets] → [finish local]
                                                              |
                                    (cuando hay red) → sync push → servidor
  1. Start. El usuario abre un entrenamiento libre, o inicia desde una rutina — la app precarga los ejercicios y sets objetivo de la plantilla. Solo puede haber una sesión activa a la vez.
  2. Entrenar. Sets, ejercicios, supersets, notas, RPE — todo estado local. Cerrar la app, quedarse sin batería o crashear no pierde nada: el estado está en la base local, no en memoria.
  3. Finish. El usuario cierra la sesión y asigna opcionalmente el esfuerzo percibido. La app muestra la pantalla de celebración con el resumen (duración, volumen, sets por tipo, músculos trabajados) calculado localmente.
  4. Sync. En cuanto hay red, la sesión viaja al servidor con el resto de cambios pendientes.

Qué hace el servidor cuando recibe una sesión

  • Persistirla como historial. El servidor es la copia durable y el punto de restauración: reinstalar la app o estrenar teléfono recupera todo el historial con una sincronización completa.
  • Derivar los Récords Personales. Al recibir una sesión finalizada, el servidor compara sus mejores marcas contra el histórico del usuario e inserta los récords nuevos. La derivación es idempotente — reprocesar la misma sesión no duplica récords — y la sesión es siempre la fuente de verdad: los récords pueden reconstruirse desde ella.
  • Servir las vistas derivadas. La analítica de ejercicio (charts de progresión), el historial set-por-set y el resumen que muestra una publicación con entrenamiento compartido se calculan del lado del servidor sobre las sesiones sincronizadas.

Decisiones de dominio

  • La unidad de cada set es del set. El usuario mezcla equipo en kg y en lb en el mismo gimnasio; cada set guarda lo que se escribió (valor + unidad) y el sistema deriva el valor canónico en kg para comparar. Registrar en libras no puede cambiar la detección de un récord.
  • El nombre del ejercicio no se congela; el código sí. El historial muestra siempre el nombre de catálogo vigente — un rename del equipo de contenido corrige todo el historial al instante. El ancla histórica estable es el exercise_code, snapshot tomado al loggear. Un ejercicio archivado sigue visible en el historial, marcado como tal.
  • Los sets de calentamiento no compiten. Se excluyen de récords y volúmenes: son preparación, no marca.
  • Editar el pasado es posible y los récords lo siguen a medias. Borrar un set elimina el récord que ese set produjo (la marca "no ocurrió"), pero un récord anterior no resucita automáticamente. Re-detección retroactiva queda fuera del MVP.

Lo que no existe (y por qué)

  • No hay endpoints de sesión activa en el servidor. La sesión activa es un concepto del teléfono. El servidor solo conoce sesiones que ya viajaron por sync.
  • No hay merge de conflictos entre dispositivos. El modelo es un dispositivo activo por cuenta; ante escrituras cruzadas gana el último en escribir. Arbitraje fino de conflictos es complejidad que el caso de uso real no pide.
  • No hay programas (mesociclos con progresión). Las carpetas de rutinas son la base organizativa sobre la que se construirán; hoy el criterio de qué rutina toca hoy es del usuario.

El contrato técnico del sync — tablas, cursores, paginación, cuotas — vive en docs/OFFLINE_FIRST.md del repo del servidor; los shapes de la API, en su referencia OpenAPI.