KITKIT 19 ago 2026Aug 19, 2026

Dashboard de salud: de tu reloj a tu propio panel Health dashboard: from your watch to your own panel

No compres una app de terceros para ver datos que ya son tuyos. Con un pipeline pequeño en Python y una página estática puedes tener tu propio tablero de sueño, recuperación y carga. Don't buy a third-party app to view data that's already yours. With a small Python pipeline and a static page you can have your own sleep, recovery and load board.

Grabación real del tablero funcionando. Las cifras son de muestra, inventadas a propósito; la herramienta y lo que hace no. Real screen recording of the dashboard running. The figures are sample data, invented on purpose; the tool and what it does are not.

Los promptsThe prompts

Archivos de texto. Ábrelos, cópialos y pégalos en el asistente que uses. Plain text files. Open them, copy and paste into whichever assistant you use.

Tu reloj ya mide sueño, frecuencia cardiaca, estrés y actividad. El problema es que esos datos viven en una app cerrada: te la muestran como ellos quieren, no como tú la necesitas. Esta receta te lleva de los archivos crudos a una página tuya que se actualiza sola.

Los ejemplos usan Garmin porque es el reloj que tengo a mano. El patrón funciona igual con Apple Watch, Fitbit, Whoop, Oura o cualquier marca que te deje exportar tus datos: solo cambia la primera etapa, la de extracción.

Lo que vas a montar

Un pipeline que:

  1. Lee tus datos (export del reloj o API oficial).
  2. Limpia y alinea las métricas por fecha.
  3. Calcula KPIs de 7 y 30 días y correlaciones básicas.
  4. Escribe un fitness.json que una página HTML lee y pinta.

El HTML es estático: anillos SVG, sparklines y unas pocas tarjetas. No hace falta base de datos ni framework.

Dos trampas que hacen que el dashboard mienta

El recorrido completo, sin cifras reales: el export crudo, la corrección de unidades (3600000 ms son 60 min, no 60 h), el archivo que llega tarde, el trabajo repartido entre agentes y el panel al final.

1. Las unidades cambian según de dónde saques el dato

El export GDPR de Garmin usa milisegundos, centímetros y kilojoules. La API de Garmin Connect usa segundos, metros y kilocalorías. Si lees de una fuente y divides por 60 creyendo que son segundos, una sesión de una hora se convierte en 60 horas. Si confundes ms con s, metes un error de 60×. Si confundes kJ con kcal, metes uno de ~4,2×.

La regla útil: antes de sumar nada, anota explícitamente la unidad de cada campo. duration puede venir en ms, s o min; distance en m, km o cm; energy en kJ o kcal. Convierte todo a segundos, metros y kcal en el momento de leerlo.

2. El export se parte en tandas, y leer solo la primera tira datos

El export GDPR divide las actividades en varios archivos (summarizedActivities_0_.json, _1001_.json, etc.). Leer solo el primero puede dejarte fuera casi un tercio de tu historial. El dashboard seguirá funcionando, pero tus métricas de 30 días y tus correlaciones estarán construidas sobre un agujero invisible.

Solución: usa un patrón glob (*summarizedActivities*.json) y extiende todas las listas.

Bonus: el triatlón se duplica si no lo merges bien

Algunos relojes registran un evento multi-deporte como una sola actividad “triatlón” y también como las tres disciplinas por separado. Si sumas minutos por fecha sin mirar deporte y duración, entrenas el doble de lo que crees. Haz el merge por (fecha, deporte, minutos); no por fecha sola.

Estructura del JSON

El HTML solo necesita esto:

{
  "salud": {
    "actualizado": "2026-08-19",
    "rango": "21 jul – 19 ago",
    "kpis": {
      "sleepHours": 7.2,
      "restHR": 58,
      "readiness": 74,
      "stress": 38
    },
    "ring": {
      "sleepScore": 82,
      "readiness": 74,
      "stress": 38,
      "fecha": "19 ago"
    },
    "serie30": {
      "fechas": ["21 jul", "22 jul", "..."],
      "sleepScore": [78, 81, "..."],
      "restHR": [59, 58, "..."],
      "stress": [42, 40, "..."],
      "readiness": [70, 73, "..."],
      "sleepHours": [6.9, 7.1, "..."]
    },
    "correlaciones": [
      {"k": "Sueño → Recuperación", "r": 0.84, "n": 30},
      {"k": "Sueño → Estrés", "r": -0.62, "n": 30},
      {"k": "Estrés → Recuperación", "r": -0.71, "n": 30}
    ],
    "insights": [
      {"tipo": "good", "titulo": "El sueño manda tu recuperación", "texto": "..."}
    ]
  }
}json

La correlación de Pearson basta. Es solo stdlib: no necesitas instalar pandas ni numpy para esto.

Cómo automatizarlo

Una vez que el script genera fitness.json, la parte fácil es subirlo a tu servidor. Lo difícil es que el archivo actualizado no significa que el dato sea de hoy. Puede que el script corrió, pero la API del reloj devolvió ayer. Añade en el JSON un campo actualizado con la fecha real del último dato, no con la fecha del sistema.

Luego en el HTML compara esa fecha con hoy y muestra “Hace N días” cuando el dato se retrase.

En el teléfono, como una app — sin tienda

El panel es una página, y una página se puede instalar: un manifest, un service worker mínimo y dos iconos (prompt 4) y queda en la pantalla de inicio del teléfono, a pantalla completa, con el último dato visible aunque no haya señal. Sin tienda, sin cuenta, sin actualizaciones forzadas. El único requisito real: los service workers piden HTTPS o localhost.

El panel te pregunta cómo estuvo el día

El reloj mide el cuerpo; el día lo cuentas tú. Cuatro escalas de un toque (estrés, cansancio, ánimo, dolor) guardadas en tu navegador, cruzadas por fecha con lo que midió el reloj (prompt 5). Con dos semanas de diario, la línea “tu cansancio sube cuando duermes menos de 7 horas” sale de tus datos, no de un artículo. Y con eso el panel te dice qué toca mañana — descanso, suave, normal o fuerte — con la regla a la vista, y te sugiere dos o tres entrenamientos gratuitos filtrados por ese semáforo y tu nivel, de una lista que es un JSON tuyo y se edita como cualquier archivo. La sugerencia es orientación de entrenamiento, no consejo médico.

Tus reportes médicos, con tu IA — y con tu médico

El prompt 6 no construye nada: es el que le pegas a tu IA junto con un laboratorio para que te lo traduzca a lenguaje llano y te arme las preguntas de la consulta. El límite va escrito dentro del propio prompt: nada de diagnósticos, nada de tocar tratamientos, freno inmediato si hay señales de alarma o síntomas de urgencia, y el cierre es siempre el mismo — esto se revisa con tu médico. Y antes de pegar nada, la decisión honesta de siempre: un reporte médico pegado en un chat viaja a los servidores de ese proveedor; si no te cuadra, usa un modelo local o borra tu nombre y folios del texto — los valores se explican igual sin saber de quién son.

Qué puede salir mal

  • Token caducado. Si usas la API del fabricante, el token dura meses. Cuando caduca, la tarea automática falla en silencio. La señal es que actualizado se queda quieto.
  • Ventana corta. Si la máquina que corre el script se apaga varios días, un hueco de más de 45 días no se rellena solo si tu script solo pide los últimos 45 días. Parametriza la ventana.
  • El músculo no aparece. Si calculas el mapa muscular y lo añades al payload después de escribir el JSON, no llega al archivo. Calcula todo, monta el dict completo y escribe una sola vez.

Límite honesto: si tu reloj no tiene API pública y no te deja exportar, esta receta no sirve. Comprueba primero si tu marca permite descargar tus propios datos.

Your watch already measures sleep, heart rate, stress and activity. The problem is that data lives inside a closed app: they show it the way they want, not the way you need it. This recipe takes you from raw files to your own page that updates itself.

The examples use Garmin because that’s the watch I have. The pattern works the same with Apple Watch, Fitbit, Whoop, Oura or any brand that lets you export your data: only the first stage, extraction, changes.

What you will build

A pipeline that:

  1. Reads your data (watch export or official API).
  2. Cleans and aligns metrics by date.
  3. Calculates 7- and 30-day KPIs and basic correlations.
  4. Writes a fitness.json that an HTML page reads and renders.

The HTML is static: SVG rings, sparklines and a few cards. No database or framework needed.

Two traps that make the dashboard lie

The whole path, with no real figures: the raw export, the unit fix (3600000 ms is 60 min, not 60 h), the late file, the work split across agents, and the panel at the end.

1. Units change depending on the source

Garmin’s GDPR export uses milliseconds, centimeters and kilojoules. Garmin Connect’s API uses seconds, meters and kilocalories. If you read from one source and divide by 60 thinking they are seconds, a one-hour session becomes 60 hours. Confusing ms with s gives a 60× error. Confusing kJ with kcal gives about a 4.2× error.

The useful rule: before summing anything, explicitly note each field’s unit. duration may arrive in ms, s or min; distance in m, km or cm; energy in kJ or kcal. Convert everything to seconds, meters and kcal at read time.

2. The export is split into batches, and reading only the first one drops data

The GDPR export splits activities across several files (summarizedActivities_0_.json, _1001_.json, etc.). Reading only the first can leave out almost a third of your history. The dashboard will still work, but your 30-day metrics and correlations will be built on an invisible hole.

Solution: use a glob pattern (*summarizedActivities*.json) and extend all lists.

Bonus: triathlons duplicate if you don’t merge correctly

Some watches record a multi-sport event as a single “triathlon” activity and also as the three separate disciplines. If you sum minutes by date without looking at sport and duration, you train twice as much as you think. Merge by (date, sport, minutes); not by date alone.

JSON structure

The HTML only needs this:

{
  "salud": {
    "actualizado": "2026-08-19",
    "rango": "21 Jul – 19 Aug",
    "kpis": {
      "sleepHours": 7.2,
      "restHR": 58,
      "readiness": 74,
      "stress": 38
    },
    "ring": {
      "sleepScore": 82,
      "readiness": 74,
      "stress": 38,
      "fecha": "19 Aug"
    },
    "serie30": {
      "fechas": ["21 Jul", "22 Jul", "..."],
      "sleepScore": [78, 81, "..."],
      "restHR": [59, 58, "..."],
      "stress": [42, 40, "..."],
      "readiness": [70, 73, "..."],
      "sleepHours": [6.9, 7.1, "..."]
    },
    "correlaciones": [
      {"k": "Sleep → Recovery", "r": 0.84, "n": 30},
      {"k": "Sleep → Stress", "r": -0.62, "n": 30},
      {"k": "Stress → Recovery", "r": -0.71, "n": 30}
    ],
    "insights": [
      {"tipo": "good", "titulo": "Sleep drives your recovery", "texto": "..."}
    ]
  }
}json

Pearson correlation is enough. It’s stdlib only: no need to install pandas or numpy for this.

How to automate it

Once the script generates fitness.json, the easy part is uploading it to your server. The hard part is that an updated file does not mean the data is from today. The script may have run, but the watch API returned yesterday. Add an actualizado field to the JSON with the real date of the latest data, not the system date.

Then in the HTML compare that date with today and show “N days ago” when the data lags.

On your phone, like an app — no store

The dashboard is just a web page, and a web page can be installed: add a manifest, a minimal service worker and two icons (prompt 4), and it sits on your phone’s home screen, full screen, with the latest data visible even offline. No store, no account, no forced updates. The only real requirement: service workers demand HTTPS or localhost.

The dashboard asks how your day went

The watch measures your body; the day is yours to tell. Four one-tap scales (stress, fatigue, mood, soreness) stored in your browser, matched by date with what the watch recorded (prompt 5). After two weeks of daily logging, the line “your fatigue rises when you sleep under 7 hours” comes straight from your own data, not a generic article. With that, the panel suggests what tomorrow calls for — rest, easy, normal or hard — with the rule in plain sight, and recommends two or three free workouts filtered by that status and your level, pulled from an editable JSON file of your own. It is training guidance, not medical advice.

Your medical reports, with your AI — and with your doctor

Prompt 6 builds no code: it is what you paste into your AI alongside a lab result so it translates the terms into plain language and drafts questions for your appointment. The boundaries are written into the prompt itself: no diagnoses, no touching treatments, an immediate stop if there are red flags or emergency symptoms, and it always closes the same way — this must be reviewed with your doctor. And before pasting anything, the same honest choice as always: a medical report pasted into a chat travels to that provider’s servers. If that does not sit right with you, use a local model or strip your name, date of birth and accession numbers from the text — the values read just as well without knowing whose they are.

What can go wrong

  • Expired token. If you use the manufacturer’s API, the token lasts months. When it expires, the automated task fails silently. The signal is that actualizado stops moving.
  • Short window. If the machine running the script shuts down for several days, a gap longer than 45 days won’t refill itself if your script only requests the last 45 days. Parameterize the window.
  • Muscle map missing. If you calculate the muscle map and add it to the payload after writing the JSON, it never reaches the file. Calculate everything, build the complete dict and write once.

Honest limit: if your watch has no public API and won’t let you export, this recipe won’t help. Check first whether your brand lets you download your own data.