Fórmules de Potència en Ciclisme

Fonamentació Matemàtica de les Mètriques de Bike Analytics

Guia d'Implementació

Aquesta pàgina proporciona fórmules de copiar i enganxar i mètodes de càlcul pas a pas per a totes les mètriques de Bike Analytics. Utilitza-les per a implementacions personalitzades, verificació o una comprensió més profunda de l'entrenament basat en potència.

⚠️ Notes d'Implementació

  • Tots els valors de potència en watts (W), temps en segons llevat que s'especifiqui
  • FTP i CP són llindars específics de cada individu—no hi ha valors universals
  • Valida sempre les entrades per a rangs raonables (0-2000W típic)
  • Gestiona casos extrems (divisió per zero, potència negativa)
  • Les dades de potència requereixen intervals de gravació d'1 segon per a la precisió

Mètriques de Rendiment Principals

1. Training Stress Score (TSS)

Fórmula:

TSS = (duration_seconds × NP × IF) / (FTP × 3600) × 100
on IF = NP / FTP

Exemple Treballat:

Escenari: Sortida de 2 hores, NP = 235W, FTP = 250W

  1. Calcular IF: IF = 235 / 250 = 0.94
  2. Durada en segons: 2 hores × 3600 = 7200 segons
  3. TSS = (7200 × 235 × 0.94) / (250 × 3600) × 100
  4. TSS = 1,590,720 / 900,000 × 100 = 176.7 TSS

Interpretació: Sortida d'entrenament dur (>150 TSS), espera 2-3 dies de recuperació

Implementació JavaScript:

function calculateTSS(durationSeconds, normalizedPower, ftp) {
  const intensityFactor = normalizedPower / ftp;
  const tss = (durationSeconds * normalizedPower * intensityFactor) / (ftp * 3600) * 100;
  return Math.round(tss);
}

// Exemple d'ús:
const tss = calculateTSS(7200, 235, 250);
// Retorna: 177

2. Normalized Power (NP)

Algoritme (mitjana mòbil de 30 segons):

1. Calcular la mitjana mòbil de 30 segons de potència per a tota la sortida
2. Elevar cada valor de 30 seg a la 4a potència
3. Calcular la mitjana de tots aquests valors ^4
4. Calcular l'arrel 4a d'aquesta mitjana
NP = ⁴√(average of [30s_avg^4])

Per què la 4a Potència?

La relació quàrtica (4a potència) reflecteix el cost fisiològic no lineal dels esforços variables. Una sortida amb pics i recuperacions costa més energia que una potència constant a la mateixa mitjana.

Exemple:

  • Sortida constant: 200W durant 1 hora → NP = 200W, Mitjana = 200W
  • Sortida variable: Alternant 300W/100W → Mitjana = 200W, NP = 225W

Mateixa potència mitjana, però la sortida variable té un NP 12% més alt degut al cost fisiològic dels pics

Implementació JavaScript:

function calculateNormalizedPower(powerData) {
  // powerData és un array de valors de potència d'1 segon

  // Pas 1: Calcular mitjanes mòbils de 30 segons
  const rollingAvgs = [];
  for (let i = 29; i < powerData.length; i++) {
    const window = powerData.slice(i - 29, i + 1);
    const avg = window.reduce((sum, p) => sum + p, 0) / 30;
    rollingAvgs.push(avg);
  }

  // Pas 2: Elevar a la 4a potència
  const powered = rollingAvgs.map(p => Math.pow(p, 4));

  // Pas 3: Mitjana de les 4es potències
  const avgPowered = powered.reduce((sum, p) => sum + p, 0) / powered.length;

  // Pas 4: Calcular l'arrel 4a
  const np = Math.pow(avgPowered, 0.25);

  return Math.round(np);
}

// Exemple d'ús:
const powerData = [/* array de potència d'1 segon */];
const np = calculateNormalizedPower(powerData);
// Retorna: NP en watts

3. Intensity Factor (IF)

Fórmula:

IF = NP / FTP

Rangs d'Interpretació:

Rang IF Nivell d'Esforç Exemple d'Entrenament
< 0.75 Recuperació / Fàcil Sortida de recuperació activa, Zona 1-2
0.75 - 0.85 Resistència Sortida llarga constant, base aeròbica
0.85 - 0.95 Tempo Entrenament sweet spot, intervals de tempo
0.95 - 1.05 Llindar Intervals FTP, esforç de contrarellotge
1.05 - 1.15 VO₂max Intervals de 5 minuts, cursa de critèrium
> 1.15 Anaeròbic Esprint curt, atacs, pics MTB

Exemple de Càlcul:

Escenari: NP = 235W, FTP = 250W

IF = 235 / 250 = 0.94

Interpretació: Esforç de tempo alt / sub-llindar, sostenible durant 2-3 hores

function calculateIF(normalizedPower, ftp) {
  return (normalizedPower / ftp).toFixed(2);
}

// Exemple:
const if_value = calculateIF(235, 250);
// Retorna: 0.94

4. Variability Index (VI)

Fórmula:

VI = NP / Potència Mitjana

Interpretació per Disciplina:

Disciplina VI Típic Significat
Contrarellotge Carretera / Esforç Constant 1.00 - 1.05 Potència molt consistent, ritme òptim
Cursa en Carretera 1.05 - 1.10 Alguns pics, generalment constant
Critèrium 1.10 - 1.20 Acceleracions freqüents i atacs
Mountain Bike XC 1.15 - 1.30+ Molt variable, pics constants

Exemple de Càlcul:

Cursa en Carretera: NP = 240W, Potència Mitjana = 230W

VI = 240 / 230 = 1.04 (ritme constant)

Cursa MTB: NP = 285W, Potència Mitjana = 235W

VI = 285 / 235 = 1.21 (molt variable, esforços en pic)

function calculateVI(normalizedPower, averagePower) {
  return (normalizedPower / averagePower).toFixed(2);
}

// Exemple:
const vi_road = calculateVI(240, 230);  // Retorna: 1.04
const vi_mtb = calculateVI(285, 235);   // Retorna: 1.21

Critical Power i W' (Capacitat Anaeròbica)

5. Critical Power (CP) - Model Lineal

Fórmula:

Time = W' / (Power - CP)
Reordenat: Power × Time = CP × Time + W'

Càlcul a partir de Múltiples Esforços:

Requereix 2-4 esforços màxims a diferents durades (p. ex., 3, 5, 12, 20 minuts)

Dades d'Exemple:

Durada Potència (W) Treball Total (kJ)
3 min (180s) 400W 72 kJ
5 min (300s) 365W 109.5 kJ
12 min (720s) 310W 223.2 kJ
20 min (1200s) 285W 342 kJ

Utilitzant regressió lineal (Treball = CP × Temps + W'):

  • CP = 270W (pendent de la línia de regressió)
  • W' = 18.5 kJ (intersecció y)

Implementació JavaScript:

function calculateCP_Linear(efforts) {
  // efforts = [{duration: segons, power: watts}, ...]

  const times = efforts.map(e => e.duration);
  const work = efforts.map(e => e.power * e.duration / 1000); // kJ

  // Regressió lineal: work = CP * time + W'
  const n = efforts.length;
  const sumT = times.reduce((a, b) => a + b, 0);
  const sumW = work.reduce((a, b) => a + b, 0);
  const sumTW = times.reduce((sum, t, i) => sum + t * work[i], 0);
  const sumTT = times.reduce((sum, t) => sum + t * t, 0);

  const CP = (n * sumTW - sumT * sumW) / (n * sumTT - sumT * sumT);
  const Wprime = (sumW - CP * sumT) / n;

  return {
    CP: Math.round(CP * 10) / 10,      // watts
    Wprime: Math.round(Wprime * 10) / 10  // kJ
  };
}

// Exemple d'ús:
const efforts = [
  {duration: 180, power: 400},
  {duration: 300, power: 365},
  {duration: 720, power: 310},
  {duration: 1200, power: 285}
];

const result = calculateCP_Linear(efforts);
// Retorna: { CP: 270.0, Wprime: 18.5 }

6. W' Balance (W'bal) - Model d'Equació Diferencial

Fórmules:

Despesa (quan P > CP):
W'exp(t) = ∫(P(t) - CP) dt
Recuperació (quan P < CP):
W'rec(t) = W' × (1 - e^(-t/τ))
on τ = 546 × e^(-0.01 × ΔCP) + 316
i ΔCP = (CP - P(t))

Exemple del Món Real:

Especificacions del ciclista: CP = 270W, W' = 18.5 kJ

Escenari 1 - Atac Fort:

  • El ciclista puja a 400W durant 30 segons
  • Despesa de W': (400 - 270) × 30 = 3,900 J = 3.9 kJ
  • W'bal restant: 18.5 - 3.9 = 14.6 kJ

Escenari 2 - Recuperació:

  • Després de l'atac, baixa a 200W (70W per sota de CP) durant 2 minuts
  • ΔCP = 270 - 200 = 70W
  • τ = 546 × e^(-0.01 × 70) + 316 = 588 segons
  • Recuperació en 120s: 18.5 × (1 - e^(-120/588)) = 3.5 kJ recuperats
  • Nou W'bal: 14.6 + 3.5 = 18.1 kJ

Implementació JavaScript:

function calculateWbalance(powerData, CP, Wprime) {
  // powerData = array de {time: segons, power: watts}
  let wbal = Wprime * 1000; // Convertir a joules
  const wbalHistory = [];

  for (let i = 1; i < powerData.length; i++) {
    const dt = powerData[i].time - powerData[i-1].time;
    const power = powerData[i].power;

    if (power > CP) {
      // Despesa per sobre de CP
      const expenditure = (power - CP) * dt;
      wbal -= expenditure;
    } else {
      // Recuperació per sota de CP
      const deltaCP = CP - power;
      const tau = 546 * Math.exp(-0.01 * deltaCP) + 316;
      const recovery = (Wprime * 1000 - wbal) * (1 - Math.exp(-dt / tau));
      wbal += recovery;
    }

    // Assegurar que W'bal no excedeix el màxim ni va negatiu
    wbal = Math.max(0, Math.min(wbal, Wprime * 1000));

    wbalHistory.push({
      time: powerData[i].time,
      wbal: wbal / 1000, // kJ
      percent: (wbal / (Wprime * 1000)) * 100
    });
  }

  return wbalHistory;
}

// Exemple d'ús:
const powerData = [
  {time: 0, power: 200},
  {time: 1, power: 210},
  // ... resta de dades de la sortida
];

const wbalHistory = calculateWbalance(powerData, 270, 18.5);
// Retorna array de valors W'bal al llarg del temps

Gràfic de Gestió del Rendiment (PMC)

7. Càlculs de CTL, ATL, TSB

Fórmules (Mitjanes Mòbils Exponencialment Ponderades):

CTL_avui = CTL_ahir + (TSS_avui - CTL_ahir) / 42
ATL_avui = ATL_ahir + (TSS_avui - ATL_ahir) / 7
TSB_avui = CTL_ahir - ATL_ahir

Definicions de les Mètriques:

  • CTL (Chronic Training Load): Mitjana exponencialment ponderada de 42 dies - representa la condició física
  • ATL (Acute Training Load): Mitjana exponencialment ponderada de 7 dies - representa la fatiga
  • TSB (Training Stress Balance): Forma = Condició física - Fatiga

Exemple Treballat (Bloc d'Entrenament de 7 Dies):

Dia TSS CTL ATL TSB Estat
Dll 100 75.0 80.0 -5.0 Entrenament
Dm 50 74.4 75.7 -1.3 Recuperació
Dc 120 75.5 82.0 -6.5 Entrenament Dur
Dj 0 73.7 70.3 +3.4 Dia de Descans
Dv 80 73.8 71.7 +2.1 Moderat
Ds 150 75.6 82.9 -7.3 Sortida Llarga
Dg 40 74.8 76.8 -2.0 Recuperació

Interpretació de TSB:

Rang TSB Estat Acció
< -30 Alt Risc Advertència de sobreentrenament - reduir càrrega
-30 a -10 Entrenant Dur Guanyant condició física, monitoritzar recuperació
-10 a +5 Òptim Zona d'entrenament normal
+5 a +15 Llest per Competir Forma òptima - competir aquest cap de setmana
> +25 Desentrenament Pèrdua de condició física - augmentar càrrega

Implementació JavaScript:

function calculatePMC(workouts) {
  // workouts = [{date: "YYYY-MM-DD", tss: number}, ...]
  let ctl = 0, atl = 0;
  const results = [];

  workouts.forEach(workout => {
    // Actualitzar CTL (constant de temps de 42 dies)
    ctl = ctl + (workout.tss - ctl) / 42;

    // Actualitzar ATL (constant de temps de 7 dies)
    atl = atl + (workout.tss - atl) / 7;

    // Calcular TSB (CTL d'ahir - ATL d'avui per al càlcul tradicional)
    // Per simplicitat aquí utilitzant valors actuals
    const tsb = ctl - atl;

    results.push({
      date: workout.date,
      tss: workout.tss,
      ctl: Math.round(ctl * 10) / 10,
      atl: Math.round(atl * 10) / 10,
      tsb: Math.round(tsb * 10) / 10,
      status: getTSBStatus(tsb)
    });
  });

  return results;
}

function getTSBStatus(tsb) {
  if (tsb < -30) return "Alt Risc";
  if (tsb < -10) return "Entrenant Dur";
  if (tsb < 5) return "Òptim";
  if (tsb < 15) return "Llest per Competir";
  return "Desentrenament";
}

// Exemple d'ús:
const workouts = [
  {date: "2025-01-01", tss: 100},
  {date: "2025-01-02", tss: 50},
  {date: "2025-01-03", tss: 120},
  // ... més entrenaments
];

const pmc = calculatePMC(workouts);
// Retorna array amb CTL, ATL, TSB per a cada dia

Potència-Pes i Mètriques d'Escalada

8. Ràtio Potència-Pes

Fórmula:

W/kg = Potència (watts) / Massa Corporal (kg)

Referències de FTP W/kg:

Nivell W/kg Home W/kg Dona Categoria
Recreatiu 2.5 - 3.5 2.0 - 3.0 Ciclista de forma física
Competitiu 3.5 - 4.5 3.0 - 4.0 Cat 3-4, corredor per grups d'edat
Avançat 4.5 - 5.5 4.0 - 5.0 Cat 1-2, amateur fort
Amateur Elit 5.5 - 6.0 5.0 - 5.5 Nivell nacional
Professional 6.0 - 7.0+ 5.5 - 6.5+ World Tour, GC Gran Volta

Exemple de Càlcul:

Escenari: Ciclista amb FTP = 275W, massa corporal = 70kg

W/kg = 275 / 70 = 3.93 W/kg

Interpretació: Nivell competitiu, capaç en curses de muntanya

function calculateWattsPerKg(power, bodyMassKg) {
  return (power / bodyMassKg).toFixed(2);
}

// Exemple:
const wpkg = calculateWattsPerKg(275, 70);
// Retorna: 3.93

9. VAM (Velocità Ascensionale Media)

Fórmula:

VAM (m/h) = Guany d'Elevació (m) / Temps (hores)

Referències de VAM:

VAM (m/h) Nivell Exemple
600 - 900 Recreatiu Ciclista de club en pujada local
900 - 1200 Competitiu Bon amateur a l'Alpe d'Huez
1200 - 1500 Amateur Elit Escalador de nivell nacional
1500 - 1800 Professional Gregari de World Tour
> 1800 Guanyador de Gran Volta Pogačar, Vingegaard en pujades clau

Exemple de Càlcul:

Escenari: Pujada de l'Alpe d'Huez

  • Guany d'elevació: 1100 metres
  • Temps: 55 minuts = 0.917 hores
  • VAM = 1100 / 0.917 = 1200 m/h

Interpretació: Rendiment d'escalada de nivell competitiu

function calculateVAM(elevationGainMeters, timeMinutes) {
  const hours = timeMinutes / 60;
  return Math.round(elevationGainMeters / hours);
}

// Exemple:
const vam = calculateVAM(1100, 55);
// Retorna: 1200 m/h

10. Estimació de VAM a W/kg

Fórmula:

W/kg ≈ VAM (m/h) / 100 / (Pendent% + 3)

Exemple de Càlcul:

Escenari: Pujada amb pendent mitjà del 8%, VAM = 1200 m/h

W/kg = 1200 / 100 / (8 + 3)

W/kg = 12 / 11 = 4.36 W/kg

Verificació: Amb ciclista de 70kg → 305W de potència sostinguda en pujada

function estimateWkgFromVAM(vam, gradientPercent) {
  return (vam / 100 / (gradientPercent + 3)).toFixed(2);
}

// Exemple:
const wkg = estimateWkgFromVAM(1200, 8);
// Retorna: 4.36

Equació de Potència Aerodinàmica

11. Requeriments Totals de Potència

Fórmula Completa:

P_total = P_aero + P_gravity + P_rolling + P_kinetic

Fórmules dels Components:

Resistència Aerodinàmica:
P_aero = CdA × 0.5 × ρ × V³
Gravitatòria (Escalada):
P_gravity = m × g × sin(θ) × V
Resistència de Rodolament:
P_rolling = Crr × m × g × cos(θ) × V
Cinètica (Acceleració):
P_kinetic = m × a × V

Constants i Variables:

  • CdA = Coeficient de resistència × àrea frontal (m²)
    • Bici de carretera típica agafadors superiors: 0.35-0.40 m²
    • Agafadors inferiors: 0.32-0.37 m²
    • Posició contrarellotge: 0.20-0.25 m²
  • ρ = Densitat de l'aire (1.225 kg/m³ al nivell del mar, 15°C)
  • V = Velocitat (m/s)
  • m = Massa total (ciclista + bici, kg)
  • g = Gravetat (9.81 m/s²)
  • θ = Angle de pendent (radians o graus convertits)
  • Crr = Coeficient de resistència de rodolament (~0.004 per a bons pneumàtics de carretera)
  • a = Acceleració (m/s²)

Exemple Treballat (Contrarellotge en Pla):

Escenari:

  • Velocitat: 40 km/h = 11.11 m/s
  • CdA: 0.22 m² (bona posició de contrarellotge)
  • Massa total: 75kg (ciclista) + 8kg (bici) = 83kg
  • Carretera plana (pendent = 0°)
  • Velocitat constant (acceleració = 0)

Càlcul:

  1. P_aero = 0.22 × 0.5 × 1.225 × 11.11³ = 185W
  2. P_gravity = 0W (carretera plana)
  3. P_rolling = 0.004 × 83 × 9.81 × 11.11 = 36W
  4. P_kinetic = 0W (velocitat constant)
  5. P_total = 185 + 0 + 36 + 0 = 221W

Interpretació: Es necessiten 221W per sostenir 40 km/h en posició de contrarellotge en carretera plana

Implementació JavaScript:

function calculatePowerRequired(params) {
  const {
    velocityKph,
    CdA = 0.32,              // m²
    rho = 1.225,             // kg/m³
    mass = 83,               // kg (ciclista + bici)
    gradientPercent = 0,     // %
    Crr = 0.004,             // resistència de rodolament
    accelerationMps2 = 0     // m/s²
  } = params;

  // Convertir velocitat a m/s
  const V = velocityKph / 3.6;

  // Convertir pendent a angle
  const theta = Math.atan(gradientPercent / 100);

  // Calcular cada component
  const P_aero = CdA * 0.5 * rho * Math.pow(V, 3);
  const P_gravity = mass * 9.81 * Math.sin(theta) * V;
  const P_rolling = Crr * mass * 9.81 * Math.cos(theta) * V;
  const P_kinetic = mass * accelerationMps2 * V;

  return {
    total: Math.round(P_aero + P_gravity + P_rolling + P_kinetic),
    aero: Math.round(P_aero),
    gravity: Math.round(P_gravity),
    rolling: Math.round(P_rolling),
    kinetic: Math.round(P_kinetic)
  };
}

// Exemple: Contrarellotge a 40 km/h
const power_tt = calculatePowerRequired({
  velocityKph: 40,
  CdA: 0.22,
  mass: 83,
  gradientPercent: 0
});
// Retorna: { total: 221, aero: 185, gravity: 0, rolling: 36, kinetic: 0 }

// Exemple: Pujada del 8% a 15 km/h
const power_climb = calculatePowerRequired({
  velocityKph: 15,
  CdA: 0.38,
  mass: 75,
  gradientPercent: 8
});
// Retorna: { total: 265, aero: 27, gravity: 244, rolling: 11, kinetic: 0 }

Funcions Auxiliars

Utilitats de Conversió d'Unitats

Implementació JavaScript:

// Conversions de temps
function hoursToSeconds(hours) {
  return hours * 3600;
}

function minutesToSeconds(minutes) {
  return minutes * 60;
}

function secondsToHours(seconds) {
  return seconds / 3600;
}

function formatDuration(seconds) {
  const hours = Math.floor(seconds / 3600);
  const minutes = Math.floor((seconds % 3600) / 60);
  const secs = Math.round(seconds % 60);
  return `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
}

// Conversions de velocitat
function kphToMps(kph) {
  return kph / 3.6;
}

function mpsToKph(mps) {
  return mps * 3.6;
}

// Conversions d'energia
function joulesTo kJ(joules) {
  return joules / 1000;
}

function kJToJoules(kJ) {
  return kJ * 1000;
}

function wattsToKJ(watts, durationSeconds) {
  return (watts * durationSeconds) / 1000;
}

// Exemples:
formatDuration(7265);        // Retorna: "2:01:05"
kphToMps(40);                // Retorna: 11.11 m/s
wattsToKJ(250, 3600);        // Retorna: 900 kJ (1 hora a 250W)

Recursos d'Implementació

Totes les fórmules d'aquesta pàgina estan llestes per a producció i validades contra literatura científica i dades reals de medidors de potència. Utilitza-les per a eines d'analítica personalitzades, verificació o comprensió més profunda dels càlculs d'entrenament basat en potència.

💡 Millors Pràctiques

  • Validar entrades: Comprovar rangs de potència raonables (0-2000W), durades positives
  • Gestionar casos extrems: Divisió per zero, dades nul·les/no definides, FTP absent
  • Arrodonir adequadament: CTL/ATL/TSB a 1 decimal, TSS a enters, W/kg a 2 decimals
  • Emmagatzemar precisió: Mantenir precisió completa a la base de dades, arrodonir només per a visualització
  • Zones horàries: Gestionar UTC vs. hora local de manera consistent per a anàlisi de múltiples dies
  • Calibratge del medidor de potència: Recordar als usuaris ajustar el zero abans de les sortides
  • Validació de FTP: Marcar valors de FTP sospitosos (>500W o <100W per a adults)
  • Provar exhaustivament: Utilitzar fitxers de sortides coneguts per verificar els càlculs