PTENES
MÓDULO 2.4

⌚ Wearable y programación

Conecta WHOOP de punta a punta — OAuth, callback, sync diario p/ la tabla vitals — con los dos gotchas de producción, y programa la sincronización y el check-in de la mañana.

7
Temas
~35
Minutos
Intermedio
Nivel
Práctico
Tipo
Tu progreso en este módulo 0% · 0 de 7
1

🔗 WHOOP OAuth: crear la app

Para que el coach lea tu WHOOP, primero creas un app de desarrollador en el portal de WHOOP. Esta app le da una identidad a HealthOS (client id + secret) y dice exactamente adónde debe llevarte WHOOP después de que autorices. Tres decisiones lo definen todo: el redirect URI, los alcances e o offline.

🟢 ¿Nuevo por aquí? Qué es OAuth

OAuth es la forma estándar de «dar permiso sin entregar la contraseña». En vez de escribir tu usuario y contraseña de WHOOP en HealthOS, haces clic en Autorizar en el sitio de WHOOP y esta devuelve un token — una clave temporal que solo sirve para leer lo que autorizaste. El redirect URI es la dirección a la que WHOOP te devuelve con este token.

En el portal para desarrolladores, tres pasos:

1

Crear la app

En el portal para desarrolladores de WHOOP, crea una app. Recibes un WHOOP_CLIENT_ID y un WHOOP_CLIENT_SECRET — van a ~/.env.

2

Registrar el redirect URI

Pega exactamente https://<seu-host>/whoop/callback. Debe coincidir carácter por carácter con lo que usa tu callback; si no, WHOOP rechazará la autorización.

3

Habilitar los permisos

Marca read:recovery read:sleep read:cycles offline. Los tres read: liberan los datos; el offline libera el refresh token (sync sin que estés presente).

📋 Pega en el panel · objetivo: los dos campos exactos de la app WHOOP

Redirect URI:  https://<seu-host>/whoop/callback
Scopes:        read:recovery read:sleep read:cycles offline

Cómo verificar: el panel guarda sin quejarse y el offline aparece marcado. Sin el offline tendrías que volver a autorizar todos los días; la sincronización automática no se ejecutaría.

🔗 App WHOOPid + alcances ↩️ /whoop/callbacktú autorizas 1× 🔑 refresh tokenguarda en ~/.env 🔄 whoop-sync.pycron diario 🗄️ vitalsrecovery, hrv, rhr, sueño en cada ejecución, la sincronización guarda un nuevo refresh token sobre el anterior (rotación)

📊 Cómo leer: de izquierda a derecha está el recorrido del permiso hasta el dato: la app autoriza en el callback, el callback guarda el token, la sincronización usa el token y escribe en vitals. La línea de vuelta discontinua es la rotación del token (gotcha 1 del tema 4): en cada ejecución, el token se cambia por el siguiente.

Conceptos clave

App WHOOP

Le da id + secret a HealthOS.

Redirect URI

.../whoop/callback, exacto.

Alcances

recovery + sleep + cycles.

offline

Libera el refresh token.

2

✅ Autorización de una sola vez

Autorizas a WHOOP una sola vez. Con esta autorización, tu callback recibe el código, lo intercambia por tokens y guarda el WHOOP_REFRESH_TOKEN en el ~/.env. A partir de ahí, la sincronización diaria funciona sola con ese refresh token: no necesitas volver a abrir el navegador.

1

Haces clic en «Autorizar»

Abre la pantalla de consentimiento de WHOOP con los permisos que marcaste. Aceptas.

2

WHOOP redirige

Te lleva a https://<seu-host>/whoop/callback con un código de un solo uso.

3

El callback guarda el token

El callback intercambia el código por tokens y escribe el WHOOP_REFRESH_TOKEN en el ~/.env. Listo: el one-time terminó.

▶ Ejecútalo tú mismo · objetivo: comprobar que se guardó el refresh token

grep WHOOP_REFRESH_TOKEN ~/.env

Cómo verificar: aparece una línea WHOOP_REFRESH_TOKEN=... con un valor largo (no vacío). Si viene vacío, la autorización no se completó: vuelve a hacer el paso de "Autorizar".

💾 Lo que quedó en el ~/.env

Después del one-time, tu ~/.env tiene los tres: WHOOP_CLIENT_ID, WHOOP_CLIENT_SECRET (de la app, en el tema 1) y ahora el WHOOP_REFRESH_TOKEN (guardado por el callback, rotado en cada sincronización). Este archivo está en tu directorio personal y nunca va a git.

Conceptos clave

One-time

Autorízalo una sola vez.

Callback

Cambia código por tokens.

Refresh token

Guardado en ~/.env.

Sin navegador

Después, el sync se encarga solo.

3

🔄 whoop-sync.py

O agent/scripts/whoop-sync.py es el corazón de la conexión. Cada vez que se ejecuta, obtiene la recuperación y el sueño de WHOOP y guarda cuatro campos en la tabla vitals: recovery_pct, hrv_ms, resting_hr, sleep_hours. Es determinista (mismo día, mismo resultado) y idempotente (volver a ejecutarlo no crea una línea duplicada: actualiza la de ese día).

🟢 ¿Nuevo por aquí?

  • Determinista — no hay IA ni aleatoriedad de por medio: con los mismos datos del día en WHOOP, la salida siempre es la misma. Puedes confiar en ella y volver a ejecutarla.
  • Idempotente — ejecutarlo 1× o 5× deja la base de datos en el mismo estado: una fila por día en vitals, no cinco. Por eso es seguro programar repeticiones.

🗄️ Los 4 campos que caen en vitals

recovery_pct

% de recuperación de la noche.

hrv_ms

Variabilidad cardíaca (ms).

resting_hr

Frecuencia en reposo.

sleep_hours

Horas de sueño de la noche.

▶ Ejecútalo tú mismo · objetivo: sincronizar ahora y ver los 4 campos en vitals

python3 agent/scripts/whoop-sync.py
python3 agent/scripts/db.py select vitals

Cómo verificar: la línea de hoy en vitals trae recovery_pct, hrv_ms, resting_hr e sleep_hours completados. Vuelve a ejecutar los dos comandos: sigue una fila para el día (idempotente), no dos.

Conceptos clave

Extrae recovery + sueño

La noche que WHOOP puntuó.

Guarda en vitals

4 campos por día.

Determinista

El mismo día, el mismo resultado.

Idempotente

Volver a ejecutar no duplica.

4

🛡️ Los 2 gotchas de producción

Dos detalles hacen fallar la sincronización si no los resuelves, y ambos solo aparecen en producción, después de que la primera sync ya haya funcionado. El whoop-sync.py ya se encarga de ambos; entiende por qué para no tropezar al reescribirlo.

✗ Si lo ignoras

  • ✗(a) Reutilizar el mismo refresh token → en la 2.ª ejecución, WHOOP responde invalid_grant y la sincronización falla.
  • ✗(b) Usar el user-agent predeterminado de Python → Cloudflare de WHOOP te bloquea con error 1010.

✓ Qué hace la sincronización

  • ✓(a) En cada run, guarda el nuevo refresh token que WHOOP devuelve sobre el anterior (rotación).
  • ✓(b) Envía un user-agent de browser en las solicitudes, así que Cloudflare las deja pasar.

⚠️ Cómo aparece cada fallo

  • •Gotcha (a) — rotación: el refresh token es de uso único. Cuando lo usas, WHOOP devuelve uno nuevo e invalida el anterior. Si no guardas el nuevo, la siguiente ejecución usa un token ya consumido → invalid_grant. Verifica ejecutando el sync dos veces: la 2.ª aún debería tener éxito.
  • •Gotcha (b) — user-agent: Cloudflare, delante de la API, bloquea a los clientes «robóticos». El user-agent predeterminado del requests/urllib entrega el juego → 1010. Enviar un header de browser lo resuelve.

Conceptos clave

Rotación

Guardar el token nuevo en cada ejecución.

invalid_grant

Síntoma de token no rotado.

User-agent

Simular un navegador supera Cloudflare.

Error 1010

Cloudflare bloqueó al cliente.

5

⏰ Programar la sincronización

WHOOP no puntúa tu noche a una hora fija: a veces a las 7h, a veces más tarde. Por eso la sincronización se ejecuta tres veces por la mañana (7h, 10h, 13h): alguna de estas ejecuciones detecta la recuperación en cuanto esté lista. Como la sincronización es idempotente, repetirla es gratis y seguro.

⏰ cron scheduler 🔄 sync · 07:00whoop-sync.py 🔄 sync · 10:00whoop-sync.py 🔄 sync · 13:00whoop-sync.py 🌅 /checkin · 07:00turno del agente 🗄️ vitals 🤖 revisión en Telegramlee la recuperación

📊 Cómo leer: a la izquierda, el cron activa cuatro cosas por la mañana: tres sincronizaciones (azul: 7h/10h/13h) que escriben en vitals, e o /checkin (ámbar, tema 6) que lee la recuperación y envía el análisis a Telegram. Las tres horas garantizan detectar la recovery en cuanto WHOOP le asigne una puntuación.

▶ Ejecútalo tú mismo · objetivo: programar la sincronización 3× por la mañana (Linux/cron)

# abra o crontab:  crontab -e
# sync às 7h, 10h e 13h (idempotente, repetir é seguro)
0 7,10,13 * * * cd ~/<sua-pasta>/agent && /usr/bin/python3 scripts/whoop-sync.py >> ~/whoop-sync.log 2>&1

macOS: en lugar del cron, usa agent/setup/whoop-sync.plist.example (launchd). Cómo verificar: mañana por la mañana la tabla vitals gana la línea del día y ~/whoop-sync.log muestra ejecuciones sin errores (sin invalid_grant, sin 1010).

Conceptos clave

7 / 10 / 13

Obtén la recovery cuando esté disponible.

crontab (Linux)

Una línea programa los 3 horarios.

launchd (macOS)

.plist.example listo en el repo.

Repetir es gratis

La idempotencia protege.

6

🌅 Programar el check-in de la mañana

El sync solo llena la tabla; quien te entrega la revisión es el check-in de la mañana. A diferencia del sync (que es solo un script que lee la API), el check-in activa un turno del agente — una llamada al LLM que lee la recuperación de la noche y escribe la revisión en Telegram. Por eso se programa por separado: envías el prompt /checkin cada mañana mediante tu scheduler.

💡 Sincronización ≠ registro

O sincronización es mecánico y barato: lee la WHOOP y guarda 4 números. El check-in es un turno del agente (cuesta una llamada al LLM): lee estos números + el resto de tu snapshot y conversación contigo. Programa el sync para que se ejecute antes del check-in, para que la revisión ya encuentre la recuperación del día lista.

▶ Ejecútalo tú mismo · objetivo: activar el /checkin cada mañana (un turno del agente)

# crontab -e — dispara /checkin às 7h05 (logo após o 1º sync)
5 7 * * * cd ~/<sua-pasta>/agent && /usr/bin/python3 <seu-disparador> "/checkin" >> ~/checkin.log 2>&1

Atajo: las plataformas de agentes con scheduler integrado (p. ej., ClaudeClaw) activan el /checkin directamente, sin cron. Cómo verificar: a las 7 h recibes en Telegram la revisión de la mañana abriendo con la recuperación de la noche y vinculándola con las decisiones de ayer.

🔁 El orden de la mañana

1) 07:00 el sync escribe la recovery en vitals. 2) 07:05 o /checkin activa el agente, que lee esa recuperación y te envía la revisión. Las sincronizaciones de las 10h y 13h son la red de seguridad por si WHOOP puntúa la noche más tarde.

Conceptos clave

/checkin

El prompt de la revisión de la mañana.

Turno del agente

Una llamada a un LLM, no un script.

Sincronización previa

Recovery en la mesa cuando se activa.

Scheduler

Cron o el de tu agente.

7

🔁 Otros wearables

WHOOP es solo el ejemplo resuelto. El mismo patrón — OAuth una vez + sync diario que escribe en vitals — sirve para Oura, Garmin, Fitbit o incluso para un registro manual. Al coach y al dashboard no les importa ni saben de dónde viene el número: trabajan con lo que llegue a la tabla vitals.

✓ Reutiliza el patrón

  • ✓Oura / Garmin / Fitbit — cambiar la app OAuth y el mapeo JSON→vitals; el resto queda igual.
  • ✓Registro manual — un mensaje en Telegram ("dormí 7 h, buena recuperación") ya se convierte en una fila en vitals.
  • ✓O /checkin, el snapshot y el dashboard siguen idénticos.

✗ Lo que no cambia

  • ✗No reescribas el coach según la fuente — lee vitals, no la API del wearable.
  • ✗No te saltes los 2 gotchas: cualquiera OAuth tiene rotación de token y protección antibot al frente.
  • ✗No necesitas WHOOP para empezar — el curso funciona con registro manual.

✅ Autoevaluación (opcional): por qué el whoop-sync.py ¿necesitaba enviar un user-agent de browser?

Conceptos clave

Patrón reutilizable

OAuth + sync diario.

vitals es el contrato

El coach lee la tabla, no la API.

El manual sirve

El mensaje se convierte en una fila.

Gotchas universales

Rotación + antibot en cada fuente.

📋 Resumen del módulo

✓
App + redirect + escopos — crea la app, registra .../whoop/callback exacto y marca read:recovery read:sleep read:cycles offline.
✓
Autoriza una vez — el callback guarda el WHOOP_REFRESH_TOKEN en el ~/.env.
✓
whoop-sync.py → vitals — determinista e idempotente, guarda recovery_pct, hrv_ms, resting_hr, sleep_hours.
✓
Los 2 gotchas — rota el refresh token (si no, invalid_grant) y envía un user-agent de navegador (si no, Cloudflare 1010).
✓
Programa sync + check-in — sync a las 7/10/13 y /checkin de la mañana; el patrón sirve para cualquier wearable.

Próximo módulo:

Ruta 3 — Cómo usar: la rutina diaria, comida y entrenamiento por foto, memoria/patrones y el dashboard. El paso a paso terminó; ahora toca vivir el coach en el día a día.