Assistent Strava/Garmin
Codi privat
Portada pública, deliberadament sense dades 
Tauler de càrrega i forma, amb la tendència de cada mètrica 
Rendiment: preparació, prediccions i millors marques 
Evolució de la càrrega aguda i crònica 
Calendari d'entrenaments: el que estava planificat i el que s'ha fet 
Operacions: cua, errors, registre i webhooks
Worker de Cloudflare que rep els webhooks de Strava, compara l'activitat principal del dia amb el pla d'entrenament i hi afegeix el temps i les dades de cursa. Després genera amb IA el títol i el resum que publica a Strava.
- Cues amb reintents i cua de missatges fallits, més cron horari i purga diària.
- Tokens de Strava xifrats amb AES-GCM abans de desar-los a D1.
- Tauler d'administració en React servit pel mateix Worker.
- Importació d'arxius FIT de Garmin i streams d'activitat desats a R2.
El problema
Strava titula cada entrenament «Cursa de la tarda» i la descripció es queda buida, així que l'historial d'un any no diu res de com va anar cap d'aquells dies. Les dades per explicar-ho sí que hi són: el pla d'entrenament, la meteorologia d'aquella hora i, si l'activitat era una cursa, el seu web oficial. El que faltava era alguna cosa que els ajuntés sola, cada dia, sense recordar-se'n.
El meu paper
Tot: el Worker, l'esquema de D1, les cues, el tauler d'administració en React i les eines locals que importen els fitxers de Garmin.
Decisions
El webhook encua i contesta; la feina va a part
Strava dona dos segons per respondre a un webhook, i el que cal fer —llegir l'activitat, creuar-la amb el pla, demanar el clima, generar el text i tornar-lo a publicar— no hi cap. El webhook deixa una feina en una cua de Cloudflare i respon; la cua la processa amb reintents i el que esgota els reintents cau en una cua morta en lloc de desaparèixer. Sense aquesta segona cua, una fallada de l'API de tercers s'empassa el dia sense deixar rastre.
Al model no li arriba on vius
El punt d'inici d'una tirada que surt de casa és el domicili, i aquesta coordenada anava sencera al prompt d'un tercer i es quedava desada en clar a la taula de curses. Ara s'arrodoneix a dos decimals, que són uns 1,1 km: suficient per distingir dues curses amb el mateix nom en ciutats diferents, que és l'única cosa per a la qual hi era, i insuficient per assenyalar un portal. Els tokens de Strava, a més, van xifrats amb AES-GCM a la base.
El model escriu la prosa; els números els posa el codi
El títol i l'entradeta els genera un model, però el bloc de mètriques és determinista i es calcula a part. Un model que redacta bé també arrodoneix ritmes, inventa un desnivell plausible i ningú ho nota fins que algú ho compara amb el rellotge. Separar-los deixa que cada part faci el que sap, i que un canvi de model no mogui ni una xifra.
Garmin s'importa des de fora, no des del Worker
Garmin Connect no té una API oberta com la de Strava: cal iniciar sessió, passar el segon factor i descodificar fitxers FIT. Això viu en eines locals que llegeixen i envien al Worker, en lloc de ficar credencials de Garmin i un descodificador binari dins d'alguna cosa que corre sempre i de cara a internet. La superfície exposada es queda en el que de debò ha d'estar exposat.
Resultat
- 197 commits entre abril i setembre de 2026, 387 fitxers de TypeScript i 36 migracions de D1: un servei que porta mesos publicant sol.
- 40 fitxers de proves amb gairebé 11 000 línies, corrent dins del mateix runtime de Workers i no en un Node simulat.
- Dues fonts de dades —Strava per webhook i Garmin per fitxer FIT— acaben al mateix historial, amb clima i cursa oficial quan n'hi ha.
Què canviaria
La coordenada del domicili va arribar a viatjar a un tercer abans que ningú es preguntés què s'estava enviant, i es va arreglar després, llegint el codi amb altres ulls. Aquesta pregunta —què surt d'aquí i on va— no es pot deixar per a una auditoria: va en el moment d'escriure la primera crida. I el projecte va créixer fins a les 62 000 línies per a una cosa que fa servir una persona; la meitat de les funcions del tauler les he obert dues vegades.