29. Calcular y verificar trabajo, energía y potencia con JavaScript

Programar un modelo físico no consiste solo en traducir fórmulas. También exige declarar unidades, validar datos, tratar errores numéricos y comprobar que distintas leyes conduzcan al mismo balance.

29.1 Del modelo físico al programa

Un cálculo confiable comienza antes del código. Hay que definir sistema, hipótesis, variables de entrada, unidades y resultados esperados. Luego se implementan ecuaciones y verificaciones independientes.

ModeloSistema e hipótesis
DatosValores y unidades
CálculoFunciones físicas
ValidaciónDominio y finitud
VerificaciónBalances e invariantes

29.2 Usar unidades coherentes

Una estrategia simple consiste en convertir todas las entradas al Sistema Internacional antes de calcular: kg, m, s, N, J y W. La interfaz puede aceptar otras unidades, pero el núcleo trabaja con una convención única.

const convertir = {
  kilometrosHoraAMetrosSegundo: valor => valor / 3.6,
  kilojoulesAJoules: valor => valor * 1000,
  kilowattsAWatts: valor => valor * 1000,
  horasASegundos: valor => valor * 3600
};

console.log(convertir.kilometrosHoraAMetrosSegundo(72)); // 20

29.3 Funciones pequeñas y puras

Una responsabilidadCada función calcula una magnitud claramente nombrada.
Entradas explícitasNo depende de variables globales ocultas.
Salida reproducibleLos mismos datos producen el mismo resultado.

Separar física, interfaz y formato facilita las pruebas y evita que una modificación visual altere el cálculo.

29.4 Trabajo de una fuerza constante

function productoEscalar(a, b) {
  return a.x * b.x + a.y * b.y + (a.z ?? 0) * (b.z ?? 0);
}

function trabajoConstante(fuerza, desplazamiento) {
  return productoEscalar(fuerza, desplazamiento);
}

console.log(trabajoConstante(
  { x: 12, y: -3 },
  { x: 4, y: 2 }
)); // 42 J

Usar componentes evita calcular ángulos y conserva naturalmente el signo del producto escalar.

29.5 Energías cinética y potencial

function energiaCinetica(masa, rapidez) {
  if (masa <= 0 || rapidez < 0) throw new RangeError("Datos no físicos");
  return 0.5 * masa * rapidez ** 2;
}

function potencialGravitatorio(masa, altura, gravedad = 9.8, referencia = 0) {
  if (masa <= 0 || gravedad <= 0) throw new RangeError("Datos no físicos");
  return masa * gravedad * (altura - referencia);
}

console.log(energiaCinetica(2, 3) + potencialGravitatorio(2, 5));

29.6 Balance entre estados

Una función puede devolver no solo el resultado principal, sino también cantidades auditables:

function auditarBalance({ Ki, Ui, Kf, Uf, trabajoNoConservativo }) {
  const cambioMecanico = Kf + Uf - Ki - Ui;
  return {
    cambioMecanico,
    trabajoNoConservativo,
    residuo: cambioMecanico - trabajoNoConservativo
  };
}

console.log(auditarBalance({
  Ki: 10, Ui: 50, Kf: 35, Uf: 20, trabajoNoConservativo: -5
}));

Un residuo próximo a cero confirma ΔEm = Wnc.

29.7 Fuerzas variables e integración

Para W = ∫F(x)dx puede usarse la regla del punto medio:

function integrarPuntoMedio(funcion, inicio, final, pasos = 1000) {
  if (pasos < 1 || final <= inicio) throw new RangeError("Intervalo inválido");
  const dx = (final - inicio) / pasos;
  let suma = 0;
  for (let i = 0; i < pasos; i += 1) {
    suma += funcion(inicio + (i + 0.5) * dx) * dx;
  }
  return suma;
}

console.log(integrarPuntoMedio(x => 3 * x ** 2, 0, 2)); // ≈ 8 J

29.8 Potencia a partir de vectores o energía

P = F⃗ · v⃗   y   Pmedia = ΔE/Δt

Ambas rutas permiten una verificación cruzada cuando se dispone de fuerzas, velocidades y cambios energéticos. No son intercambiables sin comprobar que describen el mismo sistema e intervalo.

29.9 Integrar datos de potencia

Con muestras irregulares (tj, Pj), la regla trapezoidal calcula:

function energiaDesdePotencia(muestras) {
  let energia = 0;
  for (let i = 1; i < muestras.length; i += 1) {
    const anterior = muestras[i - 1];
    const actual = muestras[i];
    const dt = actual.t - anterior.t;
    if (dt <= 0) throw new RangeError("Los tiempos deben crecer");
    energia += 0.5 * (anterior.p + actual.p) * dt;
  }
  return energia;
}

console.log(energiaDesdePotencia([
  { t: 0, p: 100 }, { t: 2, p: 140 }, { t: 5, p: 80 }
])); // 570 J

29.10 Validar entradas

JavaScript permite valores como NaN e Infinity. Una validación reutilizable debe comprobar tipo, finitud y dominio físico:

function exigirNumeroFinito(nombre, valor) {
  if (typeof valor !== "number" || !Number.isFinite(valor)) {
    throw new TypeError(`${nombre} debe ser un número finito`);
  }
  return valor;
}

function exigirPositivo(nombre, valor) {
  exigirNumeroFinito(nombre, valor);
  if (valor <= 0) throw new RangeError(`${nombre} debe ser positivo`);
  return valor;
}

const masa = exigirPositivo("masa", 2.5);
const gravedad = exigirPositivo("gravedad", 9.8);
const altura = exigirNumeroFinito("altura", -3.5);

console.log("Masa validada:", masa, "kg");
console.log("Gravedad validada:", gravedad, "m/s²");
console.log("Altura validada:", altura, "m");
console.log("Todos los datos son números finitos y válidos.");

29.11 Errores de punto flotante

Muchos decimales no se representan exactamente en binario. Por eso no conviene comprobar resultados físicos calculados con igualdad estricta:

function casiIguales(a, b, toleranciaRelativa = 1e-9, toleranciaAbsoluta = 1e-12) {
  const diferencia = Math.abs(a - b);
  const escala = Math.max(Math.abs(a), Math.abs(b));
  return diferencia <= Math.max(toleranciaAbsoluta, toleranciaRelativa * escala);
}

console.log(casiIguales(0.1 + 0.2, 0.3)); // true

29.12 Invariantes físicos como pruebas

K ≥ 0Una energía cinética negativa indica estado inaccesible o error.
ΔK = WnetoCompara una suma de trabajos con estados cinéticos.
ΔEm = WncAudita potenciales y trabajos no conservativos.

Las verificaciones deben ejecutarse con datos normales, límites, ceros y casos deliberadamente inválidos.

29.13 Pruebas automatizadas mínimas

function comprobar(condicion, mensaje) {
  if (!condicion) throw new Error(`Prueba fallida: ${mensaje}`);
}

comprobar(energiaCinetica(2, 3) === 9, "energía cinética");
comprobar(trabajoConstante({ x: 5, y: 0 }, { x: 2, y: 0 }) === 10,
  "trabajo paralelo");
comprobar(casiIguales(integrarPuntoMedio(x => 2 * x, 0, 3), 9, 1e-6),
  "integración lineal");
console.log("Pruebas superadas");

29.14 Datos estructurados y trazabilidad

Guardar entradas, hipótesis y resultados juntos facilita reproducir un cálculo:

const simulacion = {
  unidades: "SI",
  modelo: { gravedadUniforme: true, rozamientoConstante: true },
  parametros: { masa: 2, gravedad: 9.8 },
  estadoInicial: { x: 0, y: 2, rapidez: 3 },
  tramos: [
    { dx: 4, dy: 1, fx: 10, fy: 0, rozamiento: 2, dt: 2 }
  ]
};

console.log(JSON.stringify(simulacion, null, 2));

29.15 Separar cálculo y presentación

El núcleo debería devolver números sin redondear. La interfaz decide cuántos decimales mostrar mediante Intl.NumberFormat. Redondear en cada operación acumula error y dificulta auditar el resultado.

Calcular con precisión disponible; redondear solo al presentar o al comparar con la incertidumbre física.

29.16 Fallos que deben hacerse visibles

Un programa no debería convertir silenciosamente una raíz negativa en cero ni reemplazar un dato ausente por un valor arbitrario. Es preferible devolver un estado explícito:

  • { ok: false, error: "estado_inaccesible" }
  • { ok: false, error: "duracion_no_positiva" }
  • { ok: false, error: "unidad_desconocida" }

Los mensajes deben explicar qué dato corregir y conservar los valores que originaron el problema.

29.17 Actividad interactiva: auditar una trayectoria

Cada tramo define desplazamiento, fuerza aplicada, rozamiento medio y duración. El programa actualiza el estado y verifica ΔK = Wneto y ΔEm = Wnc.

Analizador energético por tramos

Unidades SI; gravedad uniforme y fuerza de rozamiento opuesta al recorrido.

TramoΔx (m)Δy (m)Fx (N)Fy (N)Rozamiento (N)Δt (s)
1
2
3
v₀ = 3 m/s v₁ = 4,6 m/s v₂ = 7,84 m/s v₃ = 8,88 m/s Trayectoria acumulada x–y Trayectoria por tres tramos y rapidez calculada en cada estado.
La geometría se escala para mostrar todos los estados; no representa el tiempo.
i123 Em máx. = 81,14 J Rojo: Pnc media Energía mecánica por estado y potencia no conservativa media por tramo.
Las variaciones de Em deben coincidir con los trabajos no conservativos.
Trabajo aplicado52 J
Trabajo de rozamiento−21,3 J
Rapidez final8,88 m/s
ΔE mecánica30,7 J
Residuo del balance0 J
Verificación superada: ΔK = Wneto y ΔEm = Wnc dentro de la tolerancia numérica.

29.18 Procesar una trayectoria por tramos

function procesarTramo(estado, tramo, { masa, gravedad = 9.8 }) {
  const distancia = Math.hypot(tramo.dx, tramo.dy);
  const trabajoAplicado = tramo.fx * tramo.dx + tramo.fy * tramo.dy;
  const trabajoRozamiento = -tramo.rozamiento * distancia;
  const trabajoGravedad = -masa * gravedad * tramo.dy;
  const cineticaFinal = estado.cinetica + trabajoAplicado
    + trabajoRozamiento + trabajoGravedad;

  if (cineticaFinal < -1e-10) {
    return { ok: false, error: "se_detiene_en_el_tramo" };
  }

  return {
    ok: true,
    cinetica: Math.max(0, cineticaFinal),
    altura: estado.altura + tramo.dy,
    rapidez: Math.sqrt(2 * Math.max(0, cineticaFinal) / masa),
    trabajoNoConservativo: trabajoAplicado + trabajoRozamiento
  };
}

const estadoInicial = { cinetica: 25, altura: 4 };
const parametros = { masa: 2, gravedad: 9.8 };

const tramoAccesible = {
  dx: 3, dy: -1,
  fx: 5, fy: 0,
  rozamiento: 2
};

const tramoImposible = {
  dx: 2, dy: 5,
  fx: 0, fy: 0,
  rozamiento: 4
};

console.log("Tramo accesible:",
  procesarTramo(estadoInicial, tramoAccesible, parametros));
console.log("Tramo imposible:",
  procesarTramo(estadoInicial, tramoImposible, parametros));

29.19 Estrategia de depuración

  1. Probar cada función con un caso calculable a mano.
  2. Registrar contribuciones antes de sumarlas.
  3. Comprobar unidades y signos en cada frontera.
  4. Comparar rutas independientes: trabajos contra cambios de estado.
  5. Reducir el problema a un solo tramo cuando falle el balance.
  6. Conservar el caso que reveló el error como prueba de regresión.

29.20 Comprobaciones y errores frecuentes

UnidadesNormalizar antes del núcleo numérico.
FinitudRechazar NaN e Infinity.
DominioMasa, duración y gravedad deben ser positivas.
ToleranciaComparar flotantes con criterio absoluto y relativo.
BalanceCalcular y mostrar el residuo.
RedondeoAplicarlo solo en la presentación.
  • Mezclar km/h con m/s o kJ con J.
  • Usar parseInt y perder la parte decimal.
  • Ocultar una raíz negativa mediante Math.abs.
  • Comparar resultados calculados con ===.
  • Actualizar el DOM dentro de cada operación del modelo.

29.21 Ejercicios propuestos

  1. Escribir una función que calcule Ue = ½kx² y valide k ≥ 0.
  2. Implementar potenciaMedia(energia, duracion) rechazando duración no positiva.
  3. Integrar F(x) = 4x³ entre 0 y 2 y comparar con el resultado exacto de 16 J.
  4. Crear una prueba que compruebe conservación mecánica en una caída desde 10 m.
  5. Modificar procesarTramo para devolver el residuo ΔK − Wneto.
  6. Con muestras E = [0, 4, 16, 36] J en t = [0, 1, 2, 3] s, estimar potencias medias por intervalo.
Ver respuestas orientativas
  1. if (k < 0) throw new RangeError(...); return 0.5 * k * x ** 2;
  2. if (duracion <= 0) throw new RangeError(...); return energia / duracion;
  3. La integral es x⁴ evaluada entre 0 y 2: 16 J. La aproximación debe converger hacia ese valor.
  4. Comparar mgh con ½mv² usando v = √(2gh) y una tolerancia relativa.
  5. Guardar trabajoNeto y devolver residuo: cineticaFinal - estado.cinetica - trabajoNeto.
  6. ΔE/Δt: 4 W, 12 W y 20 W.

29.22 Ideas para recordar

  • El programa comienza con un modelo físico y una convención de unidades.
  • Funciones puras y pequeñas son más fáciles de probar.
  • Los cálculos deben devolver contribuciones auditables, no solo un total.
  • Las integrales pueden aproximarse con sumas controlando paso y error.
  • Los flotantes se comparan con tolerancias.
  • Balances e invariantes físicos funcionan como pruebas automatizadas.
  • Los estados imposibles deben informarse, no ocultarse.

En el próximo tema reuniremos todo el curso en un analizador energético de un recorrido con gravedad y rozamiento.