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- 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.
- 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.
- 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.
- 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.