32.1 Del esquema físico a los datos
Antes de programar un oscilador debemos decidir qué sistema representamos, dónde está el origen y cuáles son sus parámetros. Un objeto no corrige un modelo físico ambiguo: sólo almacena nuestras decisiones.
Para un sistema masa-resorte horizontal mediremos x desde el equilibrio, tomaremos positivo hacia la derecha y usaremos unidades del Sistema Internacional.
32.2 Parámetros y estado no son lo mismo
Los parámetros describen el sistema durante una ejecución:
El estado contiene las variables necesarias para continuar su evolución:
Separarlos evita modificar accidentalmente la masa cuando sólo queríamos actualizar la posición.
32.3 Nombres que incluyan la unidad
JavaScript almacena números, no magnitudes físicas. El valor 2 no indica por sí mismo si representa kilogramos, segundos o centímetros.
const parametros = {
masaKg: 1.2,
constanteNpm: 30,
amortiguamientoNsPm: 1.5
};
const estado = {
tiempoS: 0,
posicionM: 0.08,
velocidadMps: -0.2
};
console.log({ parametros, estado });Los sufijos hacen visible el contrato de unidad en cada lectura y revisión de código.
32.4 Una estructura anidada
Podemos reunir información relacionada sin mezclar responsabilidades:
const oscilador = {
tipo: "masa-resorte",
convencion: {
origen: "equilibrio",
ejePositivo: "derecha",
unidades: "SI"
},
parametros: {
masaKg: 1.2,
constanteNpm: 30,
amortiguamientoNsPm: 1.5
},
estado: {
tiempoS: 0,
posicionM: 0.08,
velocidadMps: -0.2
}
};
console.log(oscilador.parametros.masaKg, oscilador.estado.posicionM);La profundidad debe aportar claridad. Crear muchas capas con un solo valor cada una vuelve incómodo el acceso sin mejorar el modelo.
32.5 Establecer invariantes
El modelo utilizado en este curso requiere:
k > 0
b ≥ 0
t, x y v finitos
Una invariante es una condición que debe mantenerse válida. Conviene verificarla al crear el objeto, cerca del lugar donde ingresan los datos.
32.6 Una función constructora validada
function crearOscilador({ masaKg, constanteNpm, amortiguamientoNsPm = 0,
posicionInicialM = 0, velocidadInicialMps = 0 }) {
const valores = [masaKg, constanteNpm, amortiguamientoNsPm,
posicionInicialM, velocidadInicialMps];
if (!valores.every(Number.isFinite)) {
throw new TypeError("Todas las magnitudes deben ser números finitos");
}
if (masaKg <= 0 || constanteNpm <= 0 || amortiguamientoNsPm < 0) {
throw new RangeError("Se requiere m > 0, k > 0 y b >= 0");
}
return {
parametros: Object.freeze({ masaKg, constanteNpm, amortiguamientoNsPm }),
estado: { tiempoS: 0, posicionM: posicionInicialM, velocidadMps: velocidadInicialMps }
};
}
console.log(crearOscilador({ masaKg: 1.2, constanteNpm: 30,
amortiguamientoNsPm: 1.5, posicionInicialM: 0.08 }));32.7 Por qué comprobar números finitos
Comparaciones como masaKg <= 0 no rechazan NaN, porque toda comparación con NaN es falsa. Tampoco impiden Infinity.
Number.isFinite(valor) comprueba que el dato sea un número real representable y finito, sin convertir cadenas automáticamente.
32.8 Parámetros inmutables
Object.freeze impide reemplazar directamente las propiedades del objeto de parámetros. Esto ayuda a tratar m, k y b como configuración durante una simulación.
El congelamiento es superficial: si hubiera objetos anidados, también deberían protegerse. Además, inmutabilidad no significa que nunca podamos crear otra configuración; significa que el cambio será explícito mediante un nuevo objeto.
32.9 El estado sí evoluciona
Posición, velocidad y tiempo cambian paso a paso. Podemos mutar un objeto de estado controladamente o producir uno nuevo:
Producir estados nuevos facilita guardar un historial. Mutar un único objeto reduce asignaciones. La decisión es de diseño, pero debe ser coherente y estar documentada.
32.10 Copias y referencias
La asignación de objetos copia una referencia:
const copia = estado; no crea un estado independiente.Para una estructura plana podemos usar:
const copia = { ...estado };Si guardamos muchas veces la misma referencia en un arreglo, todo el historial terminará mostrando el último valor.
32.11 Propiedades derivadas
Algunas cantidades se calculan desde los parámetros:
bc = 2√(mk)
ζ = b/bc
Si su cálculo es barato, suele ser más seguro obtenerlas cuando se necesitan que almacenarlas junto a m, k y b. Así no pueden quedar desactualizadas.
32.12 Funciones puras para calcular
Una función pura devuelve siempre el mismo resultado para los mismos argumentos y no modifica objetos externos:
function propiedadesDerivadas({ masaKg, constanteNpm, amortiguamientoNsPm }) {
const omega0RadS = Math.sqrt(constanteNpm / masaKg);
const bCriticoNsPm = 2 * Math.sqrt(masaKg * constanteNpm);
const zeta = amortiguamientoNsPm / bCriticoNsPm;
const regimen = Math.abs(zeta - 1) < 1e-10
? "crítico" : zeta < 1 ? "débil" : "sobreamortiguado";
return { omega0RadS, bCriticoNsPm, zeta, regimen };
}
function evaluarEstado(parametros, estado) {
const fuerzaElasticaN = -parametros.constanteNpm * estado.posicionM;
const fuerzaViscosaN = -parametros.amortiguamientoNsPm * estado.velocidadMps;
const energiaCineticaJ = 0.5 * parametros.masaKg * estado.velocidadMps ** 2;
const energiaPotencialJ = 0.5 * parametros.constanteNpm * estado.posicionM ** 2;
return { fuerzaElasticaN, fuerzaViscosaN,
fuerzaResultanteN: fuerzaElasticaN + fuerzaViscosaN,
energiaMecanicaJ: energiaCineticaJ + energiaPotencialJ };
}
const p = { masaKg: 1.2, constanteNpm: 30, amortiguamientoNsPm: 1.5 };
const e = { tiempoS: 0, posicionM: 0.08, velocidadMps: -0.2 };
console.log(propiedadesDerivadas(p), evaluarEstado(p, e));32.13 Funciones externas o métodos
También podríamos escribir oscilador.energia() y oscilador.fuerza(). Los métodos resultan cómodos cuando la operación pertenece claramente al objeto.
Las funciones externas facilitan probar cada cálculo con objetos simples y separar datos de comportamiento. No existe una única respuesta correcta; para este curso elegiremos objetos de datos y funciones físicas independientes.
32.14 Representar una fuerza externa
Una fuerza dependiente del tiempo puede almacenarse como función:
fuerzaExternaN: tiempoS => fuerza0N * Math.cos(omegaRadS * tiempoS)Las funciones no se conservan al convertir un objeto a JSON. Si necesitamos guardar la configuración, podemos serializar sus parámetros:
{ tipo: "sinusoidal", amplitudN: 2, omegaRadS: 3, faseRad: 0 }Luego una función de fábrica transforma esa descripción en comportamiento ejecutable.
32.15 Un contrato con JSDoc
JSDoc documenta la forma de un objeto y permite que muchos editores detecten propiedades mal escritas:
/**
* @typedef {Object} ParametrosOscilador
* @property {number} masaKg
* @property {number} constanteNpm
* @property {number} amortiguamientoNsPm
*/
/**
* @param {ParametrosOscilador} parametros
* @returns {number}
*/
function frecuenciaNatural(parametros) {
return Math.sqrt(parametros.constanteNpm / parametros.masaKg);
}
console.log(frecuenciaNatural({
masaKg: 0.5, constanteNpm: 32, amortiguamientoNsPm: 0.8
}));JSDoc ayuda durante el desarrollo, pero no reemplaza la validación en tiempo de ejecución cuando los datos provienen de formularios o archivos.
32.16 Conversiones en los límites
Una interfaz puede aceptar centímetros o gramos, pero el núcleo físico debería trabajar en una unidad coherente. Convertimos al ingresar:
masaKg = masaG / 1000
Y convertimos nuevamente sólo para presentar el resultado. Mezclar unidades dentro del objeto principal multiplica las posibilidades de error.
32.17 Comparaciones con tolerancia
Un valor calculado de ζ puede quedar en 0.9999999999999999 aun cuando conceptualmente sea 1. Para clasificar el régimen usamos una tolerancia relativa o absoluta apropiada.
La tolerancia pertenece a la política numérica del programa. No debe ocultarse como un número mágico repetido en muchas funciones.
32.18 Serializar con JSON
JSON.stringify convierte datos compatibles en texto y JSON.parse reconstruye una estructura de datos:
datos = JSON.parse(texto)
JSON no preserva funciones, Infinity, NaN, prototipos ni referencias compartidas. Los datos recuperados deben validarse igual que una entrada nueva.
32.19 Versionar datos guardados
Si un formato se guarda en archivos o almacenamiento local, conviene agregar una versión:
{ version: 1, tipo: "masa-resorte", parametros: {...}, estado: {...} }Cuando la estructura cambie, una función de migración podrá reconocer versiones antiguas en vez de interpretar silenciosamente campos incorrectos.
32.20 Separar modelo, vista y control
- Modelo: objetos físicos y funciones de cálculo.
- Vista: Canvas, textos, tablas y controles HTML.
- Control: lectura de entradas y coordinación de actualizaciones.
El modelo no debería buscar elementos del documento. Así puede ejecutarse en pruebas automáticas, en otro entorno gráfico o sin interfaz.
32.21 Inspector interactivo del objeto
Modificá parámetros y estado. El inspector reconstruye un objeto válido y calcula sus propiedades sin realizar todavía una integración temporal.
Representación serializable
El JSON contiene datos y unidades explícitas; las propiedades derivadas se calculan aparte.
32.22 Pruebas útiles para el modelo
- Rechazar masa cero, rigidez negativa y valores no finitos.
- Comprobar que la frecuencia natural tenga el valor esperado.
- Verificar que invertir x invierta la fuerza elástica.
- Verificar que invertir v invierta la fuerza viscosa.
- Confirmar que la energía nunca sea negativa.
- Asegurar que una función pura no modifique sus argumentos.
- Serializar, recuperar, validar y comparar los campos esenciales.
32.23 Errores frecuentes y ejercicio
- Usar nombres como
movalueen toda la aplicación: ocultan significado y unidad. - Guardar ω₀ y no actualizarla al cambiar k: los datos quedan inconsistentes.
- Guardar referencias en el historial: todas terminan apuntando al mismo estado mutable.
- Confiar sólo en controles HTML: los datos también pueden llegar desde código o archivos.
- Convertir cadenas implícitamente: puede aceptar entradas vacías o ambiguas.
- Mezclar dibujo y física: dificulta probar y reutilizar el modelo.
Creá un objeto para m = 0,5 kg, k = 32 N/m, b = 0,8 N·s/m, x = −0,06 m y v = 0,4 m/s. Calculá fuerza resultante, aceleración, energía y ζ.
Ver solución y explicación
Fviscosa = −0,8·0,4 = −0,32 N
Fresultante = +1,60 N
a = 1,60/0,5 = +3,20 m/s²
ζ = 0,8/[2√(0,5·32)] = 0,10
const oscilador = {
parametros: Object.freeze({
masaKg: 0.5, constanteNpm: 32, amortiguamientoNsPm: 0.8
}),
estado: { tiempoS: 0, posicionM: -0.06, velocidadMps: 0.4 }
};
const { masaKg: m, constanteNpm: k, amortiguamientoNsPm: b } = oscilador.parametros;
const { posicionM: x, velocidadMps: v } = oscilador.estado;
const fuerzaN = -k * x - b * v;
const aceleracionMps2 = fuerzaN / m;
const energiaJ = 0.5 * m * v ** 2 + 0.5 * k * x ** 2;
const zeta = b / (2 * Math.sqrt(m * k));
console.log({ fuerzaN, aceleracionMps2, energiaJ, zeta });32.24 Ideas para recordar
- Los parámetros definen el sistema; el estado describe su situación instantánea.
- Los nombres con unidades convierten supuestos implícitos en contratos visibles.
- Los datos externos deben validarse con dominio físico y finitud numérica.
- Las propiedades derivadas no deberían duplicarse si pueden calcularse con seguridad.
- Las funciones puras facilitan pruebas, reutilización y diagnóstico.
- Una copia de referencia no es una copia del estado.
- JSON conserva datos, pero no funciones ni invariantes.
- Separar modelo, vista y control prepara una simulación mantenible.