Fórmulas de Potencia en Ciclismo
Fundamentos Matemáticos de las Métricas de Bike Analytics
Guía de Implementación
Esta página proporciona fórmulas listas para copiar y pegar, así como métodos de cálculo paso a paso para todas las métricas de Bike Analytics. Úsalas para implementaciones personalizadas, verificación o una comprensión más profunda del entrenamiento basado en potencia.
⚠️ Notas de Implementación
- Todos los valores de potencia en vatios (W), tiempo en segundos a menos que se especifique
- FTP y CP son umbrales específicos de cada individuo, no existen valores universales
- Siempre valida las entradas para rangos razonables (0-2000W típico)
- Maneja casos extremos (división por cero, potencia negativa)
- Los datos de potencia requieren intervalos de grabación de 1 segundo para mayor precisión
Métricas de Rendimiento Fundamentales
1. Training Stress Score (TSS)
Fórmula:
Ejemplo Resuelto:
Escenario: Salida de 2 horas, NP = 235W, FTP = 250W
- Calcular IF: IF = 235 / 250 = 0.94
- Duración en segundos: 2 horas × 3600 = 7200 segundos
- TSS = (7200 × 235 × 0.94) / (250 × 3600) × 100
- TSS = 1,590,720 / 900,000 × 100 = 176.7 TSS
Interpretación: Entrenamiento exigente (>150 TSS), espera 2-3 días de recuperación
Implementación JavaScript:
function calculateTSS(durationSeconds, normalizedPower, ftp) {
const intensityFactor = normalizedPower / ftp;
const tss = (durationSeconds * normalizedPower * intensityFactor) / (ftp * 3600) * 100;
return Math.round(tss);
}
// Ejemplo de uso:
const tss = calculateTSS(7200, 235, 250);
// Devuelve: 177
2. Normalized Power (NP)
Algoritmo (promedio móvil de 30 segundos):
¿Por qué la 4ª Potencia?
La relación cuártica (4ª potencia) refleja el coste fisiológico no lineal de los esfuerzos variables. Una salida con aceleraciones y recuperaciones cuesta más energía que potencia constante al mismo promedio.
Ejemplo:
- Salida constante: 200W durante 1 hora → NP = 200W, Promedio = 200W
- Salida variable: Alternando 300W/100W → Promedio = 200W, NP = 225W
Misma potencia promedio, pero la salida variable tiene un NP 12% mayor debido al coste fisiológico de las aceleraciones
Implementación JavaScript:
function calculateNormalizedPower(powerData) {
// powerData es un array de valores de potencia de 1 segundo
// Paso 1: Calcular promedios móviles de 30 segundos
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);
}
// Paso 2: Elevar a la 4ª potencia
const powered = rollingAvgs.map(p => Math.pow(p, 4));
// Paso 3: Promedio de las 4ªs potencias
const avgPowered = powered.reduce((sum, p) => sum + p, 0) / powered.length;
// Paso 4: Tomar la raíz 4ª
const np = Math.pow(avgPowered, 0.25);
return Math.round(np);
}
// Ejemplo de uso:
const powerData = [/* array de potencia de 1 segundo */];
const np = calculateNormalizedPower(powerData);
// Devuelve: NP en vatios
3. Intensity Factor (IF)
Fórmula:
Rangos de Interpretación:
| Rango IF | Nivel de Esfuerzo | Ejemplo de Entrenamiento |
|---|---|---|
| < 0.75 | Recuperación / Fácil | Salida de recuperación activa, Zona 1-2 |
| 0.75 - 0.85 | Resistencia | Salida larga constante, base aeróbica |
| 0.85 - 0.95 | Tempo | Entrenamiento sweet spot, intervalos tempo |
| 0.95 - 1.05 | Umbral | Intervalos FTP, esfuerzo de contrarreloj |
| 1.05 - 1.15 | VO₂max | Intervalos de 5 minutos, carrera de criterium |
| > 1.15 | Anaeróbico | Sprints cortos, ataques, aceleraciones MTB |
Ejemplo de Cálculo:
Escenario: NP = 235W, FTP = 250W
IF = 235 / 250 = 0.94
Interpretación: Esfuerzo tempo alto / sub-umbral, sostenible durante 2-3 horas
function calculateIF(normalizedPower, ftp) {
return (normalizedPower / ftp).toFixed(2);
}
// Ejemplo:
const if_value = calculateIF(235, 250);
// Devuelve: 0.94
4. Variability Index (VI)
Fórmula:
Interpretación por Disciplina:
| Disciplina | VI Típico | Significado |
|---|---|---|
| Contrarreloj / Esfuerzo Constante | 1.00 - 1.05 | Potencia muy consistente, ritmo óptimo |
| Carrera en Ruta | 1.05 - 1.10 | Algunas aceleraciones, generalmente constante |
| Criterium | 1.10 - 1.20 | Aceleraciones frecuentes y ataques |
| Mountain Bike XC | 1.15 - 1.30+ | Muy variable, aceleraciones constantes |
Ejemplo de Cálculo:
Carrera en Ruta: NP = 240W, Potencia Promedio = 230W
VI = 240 / 230 = 1.04 (ritmo constante)
Carrera MTB: NP = 285W, Potencia Promedio = 235W
VI = 285 / 235 = 1.21 (muy variable, esfuerzos explosivos)
function calculateVI(normalizedPower, averagePower) {
return (normalizedPower / averagePower).toFixed(2);
}
// Ejemplo:
const vi_road = calculateVI(240, 230); // Devuelve: 1.04
const vi_mtb = calculateVI(285, 235); // Devuelve: 1.21
Potencia Crítica y W' (Capacidad Anaeróbica)
5. Critical Power (CP) - Modelo Lineal
Fórmula:
Cálculo a partir de Múltiples Esfuerzos:
Requiere 2-4 esfuerzos máximos a diferentes duraciones (ej., 3, 5, 12, 20 minutos)
Datos de Ejemplo:
| Duración | Potencia (W) | Trabajo 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 |
Usando regresión lineal (Trabajo = CP × Tiempo + W'):
- CP = 270W (pendiente de la línea de regresión)
- W' = 18.5 kJ (intersección en el eje Y)
Implementación JavaScript:
function calculateCP_Linear(efforts) {
// efforts = [{duration: segundos, power: vatios}, ...]
const times = efforts.map(e => e.duration);
const work = efforts.map(e => e.power * e.duration / 1000); // kJ
// Regresión lineal: trabajo = CP * tiempo + 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, // vatios
Wprime: Math.round(Wprime * 10) / 10 // kJ
};
}
// Ejemplo de uso:
const efforts = [
{duration: 180, power: 400},
{duration: 300, power: 365},
{duration: 720, power: 310},
{duration: 1200, power: 285}
];
const result = calculateCP_Linear(efforts);
// Devuelve: { CP: 270.0, Wprime: 18.5 }
6. W' Balance (W'bal) - Modelo de Ecuación Diferencial
Fórmulas:
W'exp(t) = ∫(P(t) - CP) dt
W'rec(t) = W' × (1 - e^(-t/τ))
y ΔCP = (CP - P(t))
Ejemplo del Mundo Real:
Especificaciones del ciclista: CP = 270W, W' = 18.5 kJ
Escenario 1 - Ataque Intenso:
- El ciclista acelera a 400W durante 30 segundos
- Gasto de W': (400 - 270) × 30 = 3,900 J = 3.9 kJ
- W'bal restante: 18.5 - 3.9 = 14.6 kJ
Escenario 2 - Recuperación:
- Después del ataque, baja a 200W (70W por debajo de CP) durante 2 minutos
- ΔCP = 270 - 200 = 70W
- τ = 546 × e^(-0.01 × 70) + 316 = 588 segundos
- Recuperación en 120s: 18.5 × (1 - e^(-120/588)) = 3.5 kJ recuperados
- Nuevo W'bal: 14.6 + 3.5 = 18.1 kJ
Implementación JavaScript:
function calculateWbalance(powerData, CP, Wprime) {
// powerData = array de {time: segundos, power: vatios}
let wbal = Wprime * 1000; // Convertir a julios
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) {
// Gasto por encima de CP
const expenditure = (power - CP) * dt;
wbal -= expenditure;
} else {
// Recuperación por debajo 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;
}
// Asegurar que W'bal no exceda el máximo o sea negativo
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;
}
// Ejemplo de uso:
const powerData = [
{time: 0, power: 200},
{time: 1, power: 210},
// ... resto de datos de la salida
];
const wbalHistory = calculateWbalance(powerData, 270, 18.5);
// Devuelve array de valores W'bal a lo largo del tiempo
Performance Management Chart (PMC)
7. Cálculos CTL, ATL, TSB
Fórmulas (Promedios Móviles Ponderados Exponencialmente):
Definiciones de Métricas:
- CTL (Chronic Training Load): Promedio ponderado exponencialmente de 42 días - representa la forma física
- ATL (Acute Training Load): Promedio ponderado exponencialmente de 7 días - representa la fatiga
- TSB (Training Stress Balance): Forma = Forma Física - Fatiga
Ejemplo Resuelto (Bloque de Entrenamiento de 7 Días):
| Día | TSS | CTL | ATL | TSB | Estado |
|---|---|---|---|---|---|
| Lun | 100 | 75.0 | 80.0 | -5.0 | Entrenamiento |
| Mar | 50 | 74.4 | 75.7 | -1.3 | Recuperación |
| Mié | 120 | 75.5 | 82.0 | -6.5 | Entrenamiento Intenso |
| Jue | 0 | 73.7 | 70.3 | +3.4 | Día de Descanso |
| Vie | 80 | 73.8 | 71.7 | +2.1 | Moderado |
| Sáb | 150 | 75.6 | 82.9 | -7.3 | Salida Larga |
| Dom | 40 | 74.8 | 76.8 | -2.0 | Recuperación |
Interpretación TSB:
| Rango TSB | Estado | Acción |
|---|---|---|
| < -30 | Alto Riesgo | Alerta de sobreentrenamiento - reducir carga |
| -30 a -10 | Entrenamiento Intenso | Construyendo forma física, monitorear recuperación |
| -10 a +5 | Óptimo | Zona de entrenamiento normal |
| +5 a +15 | Listo para Competir | Forma máxima - competir este fin de semana |
| > +25 | Desentrenamiento | Pérdida de forma física - aumentar carga |
Implementación JavaScript:
function calculatePMC(workouts) {
// workouts = [{date: "YYYY-MM-DD", tss: número}, ...]
let ctl = 0, atl = 0;
const results = [];
workouts.forEach(workout => {
// Actualizar CTL (constante de tiempo de 42 días)
ctl = ctl + (workout.tss - ctl) / 42;
// Actualizar ATL (constante de tiempo de 7 días)
atl = atl + (workout.tss - atl) / 7;
// Calcular TSB (CTL de ayer - ATL de hoy para cálculo tradicional)
// Por simplicidad aquí usando valores actuales
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 "Alto Riesgo";
if (tsb < -10) return "Entrenamiento Intenso";
if (tsb < 5) return "Óptimo";
if (tsb < 15) return "Listo para Competir";
return "Desentrenamiento";
}
// Ejemplo de uso:
const workouts = [
{date: "2025-01-01", tss: 100},
{date: "2025-01-02", tss: 50},
{date: "2025-01-03", tss: 120},
// ... más entrenamientos
];
const pmc = calculatePMC(workouts);
// Devuelve array con CTL, ATL, TSB para cada día
Potencia-Peso y Métricas de Escalada
8. Ratio Potencia-Peso
Fórmula:
Referencias FTP W/kg:
| Nivel | Hombre W/kg | Mujer W/kg | Categoría |
|---|---|---|---|
| Recreativo | 2.5 - 3.5 | 2.0 - 3.0 | Ciclista aficionado |
| Competitivo | 3.5 - 4.5 | 3.0 - 4.0 | Cat 3-4, corredor por edades |
| Avanzado | 4.5 - 5.5 | 4.0 - 5.0 | Cat 1-2, amateur fuerte |
| Amateur Élite | 5.5 - 6.0 | 5.0 - 5.5 | Nivel nacional |
| Profesional | 6.0 - 7.0+ | 5.5 - 6.5+ | World Tour, GC de Grandes Vueltas |
Ejemplo de Cálculo:
Escenario: Ciclista con FTP = 275W, masa corporal = 70kg
W/kg = 275 / 70 = 3.93 W/kg
Interpretación: Nivel competitivo, capaz en carreras con colinas
function calculateWattsPerKg(power, bodyMassKg) {
return (power / bodyMassKg).toFixed(2);
}
// Ejemplo:
const wpkg = calculateWattsPerKg(275, 70);
// Devuelve: 3.93
9. VAM (Velocità Ascensionale Media)
Fórmula:
Referencias VAM:
| VAM (m/h) | Nivel | Ejemplo |
|---|---|---|
| 600 - 900 | Recreativo | Ciclista de club en subida local |
| 900 - 1200 | Competitivo | Buen amateur en Alpe d'Huez |
| 1200 - 1500 | Amateur Élite | Escalador de nivel nacional |
| 1500 - 1800 | Profesional | Gregario del World Tour |
| > 1800 | Ganador de Grandes Vueltas | Pogačar, Vingegaard en subidas clave |
Ejemplo de Cálculo:
Escenario: Subida al Alpe d'Huez
- Ganancia de elevación: 1100 metros
- Tiempo: 55 minutos = 0.917 horas
- VAM = 1100 / 0.917 = 1200 m/h
Interpretación: Rendimiento de escalada de nivel competitivo
function calculateVAM(elevationGainMeters, timeMinutes) {
const hours = timeMinutes / 60;
return Math.round(elevationGainMeters / hours);
}
// Ejemplo:
const vam = calculateVAM(1100, 55);
// Devuelve: 1200 m/h
10. Estimación de W/kg a partir de VAM
Fórmula:
Ejemplo de Cálculo:
Escenario: Subida con 8% de pendiente promedio, VAM = 1200 m/h
W/kg = 1200 / 100 / (8 + 3)
W/kg = 12 / 11 = 4.36 W/kg
Verificación cruzada: Con ciclista de 70kg → 305W de potencia sostenida en la subida
function estimateWkgFromVAM(vam, gradientPercent) {
return (vam / 100 / (gradientPercent + 3)).toFixed(2);
}
// Ejemplo:
const wkg = estimateWkgFromVAM(1200, 8);
// Devuelve: 4.36
Ecuación de Potencia Aerodinámica
11. Requerimientos Totales de Potencia
Fórmula Completa:
Fórmulas de Componentes:
P_aero = CdA × 0.5 × ρ × V³
P_gravedad = m × g × sen(θ) × V
P_rodadura = Crr × m × g × cos(θ) × V
P_cinética = m × a × V
Constantes y Variables:
- CdA = Coeficiente de arrastre × área frontal (m²)
- Bici de ruta típica (manillares superiores): 0.35-0.40 m²
- Manillares bajos: 0.32-0.37 m²
- Posición contrarreloj: 0.20-0.25 m²
- ρ = Densidad del aire (1.225 kg/m³ a nivel del mar, 15°C)
- V = Velocidad (m/s)
- m = Masa total (ciclista + bici, kg)
- g = Gravedad (9.81 m/s²)
- θ = Ángulo de pendiente (radianes o grados convertidos)
- Crr = Coeficiente de resistencia a la rodadura (~0.004 para buenos neumáticos de ruta)
- a = Aceleración (m/s²)
Ejemplo Resuelto (Contrarreloj en Llano):
Escenario:
- Velocidad: 40 km/h = 11.11 m/s
- CdA: 0.22 m² (buena posición de contrarreloj)
- Masa total: 75kg (ciclista) + 8kg (bici) = 83kg
- Carretera plana (pendiente = 0°)
- Velocidad constante (aceleración = 0)
Cálculo:
- P_aero = 0.22 × 0.5 × 1.225 × 11.11³ = 185W
- P_gravedad = 0W (carretera plana)
- P_rodadura = 0.004 × 83 × 9.81 × 11.11 = 36W
- P_cinética = 0W (velocidad constante)
- P_total = 185 + 0 + 36 + 0 = 221W
Interpretación: Se necesitan 221W para mantener 40 km/h en posición de contrarreloj en carretera plana
Implementación 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, // resistencia a la rodadura
accelerationMps2 = 0 // m/s²
} = params;
// Convertir velocidad a m/s
const V = velocityKph / 3.6;
// Convertir pendiente a ángulo
const theta = Math.atan(gradientPercent / 100);
// Calcular cada componente
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)
};
}
// Ejemplo: Contrarreloj a 40 km/h
const power_tt = calculatePowerRequired({
velocityKph: 40,
CdA: 0.22,
mass: 83,
gradientPercent: 0
});
// Devuelve: { total: 221, aero: 185, gravity: 0, rolling: 36, kinetic: 0 }
// Ejemplo: Subida del 8% a 15 km/h
const power_climb = calculatePowerRequired({
velocityKph: 15,
CdA: 0.38,
mass: 75,
gradientPercent: 8
});
// Devuelve: { total: 265, aero: 27, gravity: 244, rolling: 11, kinetic: 0 }
Funciones Auxiliares
Utilidades de Conversión de Unidades
Implementación JavaScript:
// Conversiones de tiempo
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')}`;
}
// Conversiones de velocidad
function kphToMps(kph) {
return kph / 3.6;
}
function mpsToKph(mps) {
return mps * 3.6;
}
// Conversiones de energía
function joulesTokJ(joules) {
return joules / 1000;
}
function kJToJoules(kJ) {
return kJ * 1000;
}
function wattsToKJ(watts, durationSeconds) {
return (watts * durationSeconds) / 1000;
}
// Ejemplos:
formatDuration(7265); // Devuelve: "2:01:05"
kphToMps(40); // Devuelve: 11.11 m/s
wattsToKJ(250, 3600); // Devuelve: 900 kJ (1 hora a 250W)
Recursos de Implementación
Todas las fórmulas en esta página están listas para producción y validadas contra literatura científica y datos reales de medidores de potencia. Úsalas para herramientas de análisis personalizadas, verificación o una comprensión más profunda de los cálculos de entrenamiento basado en potencia.
💡 Mejores Prácticas
- Validar entradas: Verifica rangos de potencia razonables (0-2000W), duraciones positivas
- Manejar casos extremos: División por cero, datos nulos/indefinidos, FTP faltante
- Redondear apropiadamente: CTL/ATL/TSB a 1 decimal, TSS a entero, W/kg a 2 decimales
- Almacenar precisión: Mantén precisión completa en base de datos, redondea solo para visualización
- Zonas horarias: Maneja UTC vs. hora local de forma consistente para análisis de múltiples días
- Calibración del medidor de potencia: Recuerda a los usuarios calibrar a cero antes de las salidas
- Validación de FTP: Marca valores sospechosos de FTP (>500W o <100W para adultos)
- Prueba a fondo: Usa archivos de salidas conocidos para verificar cálculos