Ciclo de vida de los entrenamientos en HealthKit: estados de HKWorkoutSession y la superficie multiplataforma de iOS 26
HKWorkoutSession es la máquina de estados de entrenamientos de HealthKit. La sesión atraviesa seis estados (.notStarted, .prepared, .running, .paused, .stopped, .ended), expone eventos del ciclo de vida a través de HKWorkoutSessionDelegate y (desde iOS 26) se ejecuta en el iPhone además del Apple Watch1. Un HKLiveWorkoutBuilder emparejado con la sesión recopila muestras y eventos de forma incremental; iOS 26 trajo el mismo constructor API al iPhone. El artículo del clúster Contrato de ejecución de watchOS sostuvo que las apps de watchOS necesitan un tipo de sesión reconocido para seguir ejecutándose en segundo plano; HKWorkoutSession es uno de esos tipos de sesión, y los estados del ciclo de vida se mapean directamente al modelo de ejecución.
El artículo recorre el ciclo de vida del entrenamiento contrastándolo con la documentación de Apple. El enfoque es “qué permite cada estado y qué dispara cada transición”, porque las apps de entrenamiento que gestionan mal el ciclo de vida o pierden datos (porque salen del estado running demasiado pronto) o agotan la batería (porque nunca salen del estado running).
TL;DR
- Ciclo de vida de
HKWorkoutSession:notStarted→prepared→running→ (pausedopcional →running) →stopped→ended. Las transiciones se reportan a través deHKWorkoutSessionDelegate.workoutSession(_:didChangeTo:from:date:)2. HKLiveWorkoutBuilderes el acumulador de datos en vivo emparejado con la sesión de entrenamiento. Surgió en watchOS en 2018 y se lanzó en iOS 26+, iPadOS 26+ y Mac Catalyst 26+. Los entrenamientos en iPhone usan el mismo API deHKLiveWorkoutBuilderque el Apple Watch, con diferencias específicas de cada plataforma en el modelo de ejecución y la disponibilidad de sensores3.- El método
prepare()en la sesión calienta los sensores antes de questartActivity(_:)ejecute el entrenamiento real. La recomendación de Apple es una UI de cuenta regresiva de 3 segundos entreprepare()ystartActivity(_:)para darles tiempo a los sensores de frecuencia cardíaca y a los dispositivos Bluetooth externos a conectarse. - El estado
stoppedes transitorio: las apps pueden finalizar las métricas en este estado pero no pueden reanudar la sesión. Llamar aend()transiciona aended, que es terminal. - El artículo del clúster Contrato de ejecución de watchOS cubre cómo la sesión de entrenamiento mantiene la app de watchOS ejecutándose cuando el usuario baja la muñeca. La máquina de estados del ciclo de vida y el contrato de mantener viva la ejecución son las dos mitades de la misma superficie.
Los seis estados
HKWorkoutSessionState enumera el ciclo de vida2:
.notStarted. La sesión se ha creado pero no se ha preparado. Los sensores no están calentados; la app aún no se considera un host activo de entrenamiento. La transición a .prepared ocurre cuando la app llama a prepare().
.prepared. La sesión ha llamado a prepare(); los sensores están calentándose pero el entrenamiento aún no ha comenzado. Los monitores de frecuencia cardíaca se conectan, los sensores de movimiento se inicializan, el GPS obtiene una posición. El patrón de cara al usuario es una cuenta regresiva de 3 segundos (“Prepárate… 3, 2, 1, ¡YA!”); durante esa ventana, el sistema tiene tiempo para adquirir una señal limpia y así las primeras métricas en el estado running son precisas.
.running. El estado activo del entrenamiento. La app está recopilando métricas, mostrando datos en vivo y (en watchOS) manteniendo la pantalla encendida a través del contrato de ejecución de entrenamiento activo. La transición a .running ocurre mediante startActivity(_:).
.paused. Un estado pausado por el usuario. La app ya no recopila métricas activas (por ejemplo, distancia) pero la sesión se preserva; llamar a resume() regresa a .running. El ciclo de pausar/reanudar puede ocurrir cualquier número de veces dentro de una sola sesión.
.stopped. Un estado transitorio posterior al entrenamiento. La sesión ha terminado su fase activa pero no se ha finalizado; el constructor en vivo aún puede finalizar las métricas. Desde .stopped, llamar a end() transiciona a .ended. La app no puede reanudar desde .stopped.
.ended. El estado terminal. La sesión ha terminado; al constructor en vivo se le ha indicado que finalice; el entrenamiento se guarda en HealthKit (si la app llamó a finishWorkout(completion:) en el constructor). Una vez en .ended, la sesión ya no es manipulable.
El diagrama de estados tiene una trampa específica: no hay un camino de .stopped de regreso a .running. Un entrenamiento que el usuario quiere “des-terminar” requiere iniciar una nueva sesión, no reanudar la antigua.
Los métodos que impulsan las transiciones
HKWorkoutSession expone los siguientes métodos para las transiciones de estado1:
prepare(). Transiciona de.notStarteda.prepared. Calienta los sensores.startActivity(with: Date). Transiciona de.prepareda.running. El parámetroDatepermite que la app establezca la hora oficial de inicio (típicamente.now).pause(). Transiciona de.runninga.paused.resume(). Transiciona de.pausedde regreso a.running.stopActivity(with: Date). Transiciona de.running(o.paused) a.stopped. LaDatees la hora oficial de finalización.end(). Transiciona de.stoppeda.ended.
El patrón de prepare() a startActivity(_:) es la ventana de calentamiento. El patrón de stopActivity(_:) a end() es la ventana de limpieza: el constructor en vivo tiene la oportunidad de añadir muestras finales antes de que la sesión termine.
HKLiveWorkoutBuilder en watchOS y iPhone
HKLiveWorkoutBuilder es el acumulador de datos en vivo emparejado con la sesión3. El constructor llegó a watchOS en watchOS 5 y se extendió a iOS 26+, iPadOS 26+ y Mac Catalyst 26+. El ciclo de vida del constructor se empareja con el de la sesión:
let configuration = HKWorkoutConfiguration()
configuration.activityType = .running
configuration.locationType = .outdoor
let session = try HKWorkoutSession(healthStore: store, configuration: configuration)
let builder = session.associatedWorkoutBuilder()
builder.dataSource = HKLiveWorkoutDataSource(healthStore: store, workoutConfiguration: configuration)
session.delegate = self
builder.delegate = self
session.prepare()
// User taps Start after countdown
session.startActivity(with: Date())
try await builder.beginCollection(at: Date())
// During the workout, metrics flow into the builder via the data source.
// builder.collectedTypes contains the sample types being collected.
// builder.statistics(for:) returns running stats.
// User ends the workout
session.stopActivity(with: Date())
try await builder.endCollection(at: Date())
let workout = try await builder.finishWorkout()
session.end()
Tres piezas pegan el entrenamiento:
HKWorkoutConfigurationespecifica el tipo de actividad y la ubicación. El tipo de actividad determina la selección de métricas (un entrenamiento de carrera recopila ritmo, uno de ciclismo en interior no).HKLiveWorkoutDataSourcees el puente entre la configuración de sensores de la sesión y la acumulación de datos del constructor. La fuente de datos publica las muestras; el constructor las recibe y las almacena.HKLiveWorkoutBuildermantiene el estado del entrenamiento en curso y finaliza el objetoHKWorkoutguardado.
El patrón es incremental: las muestras fluyen continuamente durante el estado .running; el constructor las integra en estadísticas en ejecución; el finishWorkout() final escribe el entrenamiento completo en HealthKit.
iOS 26: entrenamientos en el iPhone
iOS 26 trajo HKWorkoutSession al iPhone con el mismo HKLiveWorkoutBuilder y la misma fuente de datos API que usa watchOS4. La construcción es la misma; las diferencias específicas de cada plataforma están en el modelo de ejecución, la disponibilidad de sensores y el manejo de la privacidad, no en la superficie del API.
Los casos de uso que habilita el API de entrenamientos de iOS 26: - Apps de entrenamiento de teléfono como acompañante (el iPhone retiene los datos del monitor de frecuencia cardíaca junto con la sesión del Watch). - Apps de fitness solo para iPhone para usuarios sin Apple Watch, donde el iPhone rastrea la sesión a través de sensores integrados y accesorios conectados. - Continuidad de sesión entre dispositivos: una sesión de Apple Watch que se entrega al iPhone (el usuario se quita el Watch pero quiere que el iPhone siga rastreando) o viceversa.
Las diferencias de plataforma que vale la pena nombrar: - Disponibilidad de sensores. El iPhone tiene acelerómetro y GPS pero no sensor de frecuencia cardíaca integrado. Las apps que necesitan frecuencia cardíaca en entrenamientos de iOS se emparejan con una banda Bluetooth de frecuencia cardíaca o leen desde un Apple Watch conectado a través de HealthKit. - Modelo de ejecución. La ejecución de entrenamiento activo del Apple Watch garantiza acceso continuo a los sensores cuando el usuario baja la muñeca. La ejecución del iPhone depende del ciclo de vida normal del sistema en primer plano/segundo plano más la recuperación de fallos a través del scene delegate (cubierto en la sesión 322 de WWDC 2025), que es una forma de garantía diferente. - Privacidad y comportamiento de la pantalla bloqueada. Los entrenamientos de iPhone que se ejecutan mientras el dispositivo está bloqueado requieren configuración explícita para seguir recopilando muestras, ya que la pantalla bloqueada es un límite de privacidad más fuerte que la muñeca bajada.
El protocolo del delegate
HKWorkoutSessionDelegate reporta las transiciones de estado y los errores5:
extension WorkoutCoordinator: HKWorkoutSessionDelegate {
func workoutSession(
_ workoutSession: HKWorkoutSession,
didChangeTo toState: HKWorkoutSessionState,
from fromState: HKWorkoutSessionState,
date: Date
) {
switch toState {
case .running:
// workout is active
case .paused:
// user paused
case .stopped:
// finalize metrics
case .ended:
// workout done; cleanup
default:
break
}
}
func workoutSession(
_ workoutSession: HKWorkoutSession,
didFailWithError error: Error
) {
// session failed (e.g., heart rate sensor disconnected unexpectedly)
}
}
El delegate es la única fuente de verdad para las transiciones de estado. Las apps que infieren el estado a partir de las llamadas a métodos (llamé a startActivity(), así que ahora estamos en running) se pierden los cambios de estado que aplica el sistema (auto-pausa cuando el usuario está estacionario, auto-finalización cuando el reloj se quita). El patrón guiado por el delegate es el correcto.
El contrato de ejecución
HKWorkoutSession es uno de los tipos de sesión que watchOS reconoce para mantener una app ejecutándose en segundo plano, junto con las sesiones de mindfulness, alarma y grabación de audio6. El contrato: mientras la sesión esté en estado .prepared, .running, .paused o .stopped, la app sigue ejecutándose; la pantalla se enciende cuando el usuario levanta la muñeca; los sensores transmiten datos a la app de forma continua.
El artículo del clúster Contrato de ejecución de watchOS cubre esto en detalle. El punto relevante para las apps de entrenamiento: la máquina de estados del ciclo de vida es lo que le dice a watchOS “mantén esta app ejecutándose”; la transición a .ended libera el contrato y permite que el sistema operativo suspenda la app.
Una implicación práctica: no termines una sesión de entrenamiento prematuramente. Si el usuario se aleja de su entrenamiento para una llamada telefónica y regresa, la sesión debería permanecer en .running (o pausarse mediante pause()), no terminarse. Terminar y reiniciar pierde los datos y la continuidad de la ejecución.
Fallos comunes
Tres patrones de los registros de fallos de apps de entrenamiento:
Saltarse prepare(). Las apps que llaman a startActivity(_:) sin antes llamar a prepare() producen entrenamientos en los que los primeros 5-10 segundos de datos de frecuencia cardíaca no son confiables (el sensor no estaba calentado) o faltan (la banda Bluetooth de frecuencia cardíaca aún no se había conectado). Solución: siempre llama a prepare(), muestra una breve UI de cuenta regresiva, luego startActivity(_:).
Llamar a end() directamente desde .running. Saltarse .stopped se salta la ventana de finalización de métricas. El constructor en vivo puede no haber procesado las muestras finales antes de que la sesión termine, lo que lleva a estadísticas de resumen faltantes. Solución: siempre llama primero a stopActivity(_:), espera el callback del delegate para confirmar .stopped, y luego llama a end().
Inferir el estado en lugar de usar el delegate. Las apps que rastrean estado local (isWorkoutActive: Bool) y nunca conectan el delegate se pierden las transiciones impulsadas por el sistema (auto-pausa, auto-finalización al quitarse el reloj, estados de error). Solución: siempre usa el delegate como la fuente de verdad.
Lo que este patrón significa para las apps de iOS 26+
Tres ideas para llevar.
-
Mapea el ciclo de vida al estado de la UI explícitamente. La UI de una app de entrenamiento tiene estados obvios: no iniciado, preparándose, activo, pausado, resumen, terminado. Mapea cada uno a un
HKWorkoutSessionState. No ejecutes la UI a partir de booleanos ad-hoc; vincúlala al estado reportado por la sesión a través del delegate. -
Usa
prepare()más una UI de cuenta regresiva para cualquier sesión que muestre métricas. El calentamiento de 3 segundos es la diferencia entre datos en los que el usuario confía y datos que el usuario descarta. El costo es un pequeño elemento de UI; la ganancia son métricas confiables. -
Las sesiones de entrenamiento del iPhone en iOS 26 necesitan código de constructor diferente. El API de la sesión es compartido; el lado del constructor es específico de cada plataforma. Las apps que comparten una ruta de código entre iOS y watchOS necesitan ramas explícitas
#if os(watchOS)o un wrapper que abstraiga la diferencia.
El clúster completo del Ecosistema Apple: App Intents tipados; servidores MCP; la pregunta del enrutamiento; Foundation Models; la distinción LLM entre ejecución y herramientas; tres superficies; el patrón de fuente única de verdad; Dos servidores MCP; hooks para el desarrollo de Apple; Live Activities; el runtime de watchOS; internos de SwiftUI; el modelo mental espacial de RealityKit; disciplina de esquemas en SwiftData; patrones de Liquid Glass; envío multiplataforma; la matriz de plataformas; framework Vision; Symbol Effects; inferencia con Core ML; Writing Tools API; Swift Testing; Privacy Manifest; Accesibilidad como plataforma; tipografía SF Pro; patrones espaciales de visionOS; framework Speech; migraciones en SwiftData; motor de foco de tvOS; internos de @Observable; protocolo Layout de SwiftUI; SF Symbols personalizados; HDR de AVFoundation; sobre lo que me niego a escribir. El hub está en la Serie del Ecosistema Apple. Para un contexto más amplio sobre iOS con agentes de IA, consulta la guía de desarrollo de agentes en iOS.
Preguntas frecuentes
¿El iPhone usa el mismo HKLiveWorkoutBuilder que el Apple Watch?
Sí, desde iOS 26. El mismo API de HKLiveWorkoutBuilder se distribuye en iOS 26+, iPadOS 26+, Mac Catalyst 26+ y watchOS 5+. Las diferencias de plataforma están en el modelo de ejecución y la disponibilidad de sensores, no en el API del constructor. Los entrenamientos del iPhone manejan la privacidad de pantalla bloqueada y la recuperación de fallos a través del scene delegate (según la sesión 322 de WWDC 2025), lo que difiere de la garantía de ejecución con muñeca bajada de watchOS, pero el API de acumulación de datos es el mismo.
¿Cuál es la duración máxima de un entrenamiento?
No hay un límite duro de duración. Los límites prácticos provienen de la batería (el Apple Watch dura entre 6 y 8 horas bajo entrenamiento continuo) y el almacenamiento (los entrenamientos con datos de alta frecuencia se acumulan rápidamente). Hoy se distribuyen apps de carreras de maratón (entrenamientos de más de 12 horas); el framework las soporta.
¿Cómo manejo la auto-pausa?
Establece HKWorkoutConfiguration.activityType en uno que admita auto-pausa (por ejemplo, .running). watchOS pausará y reanudará automáticamente según el movimiento del usuario. Las transiciones de estado fluyen a través del delegate; trátalas igual que las pausas iniciadas por el usuario.
¿Qué pasa si el usuario se quita el reloj a mitad del entrenamiento?
La sesión continúa en su estado actual (típicamente .running). El sistema watchOS eventualmente terminará la sesión si el reloj está fuera de la muñeca durante demasiado tiempo; el callback didFailWithError del delegate se dispara cuando esto sucede. Las apps con sesiones entre dispositivos (iOS 26+) pueden entregar al iPhone si el usuario tiene ambos dispositivos.
¿Debería guardar el entrenamiento en HealthKit?
Casi siempre sí. Llamar a builder.finishWorkout(completion:) escribe el entrenamiento completo en HealthKit, lo que significa que los datos aparecen en la app Actividad, en la lista de entrenamientos de la app Salud y en cualquier otra app que el usuario haya autorizado. Saltarse el guardado descarta los datos; el framework no proporciona ninguna ruta de recuperación.
¿Cómo se relaciona esto con otras adiciones recientes de Apple Health?
iOS 26 / watchOS 26 expandieron el API de entrenamientos de dos formas específicas: primero, llevando HKWorkoutSession al iPhone (cubierto arriba); segundo, ampliando la lista de tipos de actividad y la cobertura de auto-detección. El artículo del clúster Contrato de ejecución de watchOS cubre el lado de la ejecución; este artículo cubre el lado del ciclo de vida. Juntos describen la superficie completa para distribuir una app de entrenamiento.
Referencias
-
Documentación para desarrolladores de Apple:
HKWorkoutSession. La clase de sesión con transiciones de estado, configuración y el protocolo del delegate. ↩↩ -
Documentación para desarrolladores de Apple:
HKWorkoutSessionState. Los cinco casos de estado (.notStarted,.prepared,.running,.paused,.stopped,.ended) y su semántica. ↩↩ -
Documentación para desarrolladores de Apple:
HKLiveWorkoutBuilderyHKLiveWorkoutDataSource. El API del constructor en vivo (watchOS 5+, iOS 26+, iPadOS 26+, Mac Catalyst 26+) y su fuente de datos. ↩↩ -
Apple Developer: Track workouts with HealthKit on iOS and iPadOS (sesión 322 de WWDC 2025). La expansión en iOS 26 de
HKWorkoutSessional iPhone. ↩ -
Documentación para desarrolladores de Apple:
HKWorkoutSessionDelegate. El protocolo del delegate con callbacks de transición de estado y de error. ↩ -
Documentación para desarrolladores de Apple: Background Execution on watchOS. El contrato de ejecución de watchOS que describe qué tipos de sesión mantienen las apps ejecutándose cuando el usuario baja la muñeca. ↩