26. Representar cuerpos y fuerzas con objetos en JavaScript

Un simulador confiable comienza con datos claros: cada cuerpo debe conservar su identidad, parámetros y estado, mientras cada fuerza registra qué interacción representa.

26.1 Del modelo físico al modelo de datos

En papel describimos un cuerpo mediante símbolos como m, r y v. En un programa necesitamos decidir cómo guardar esos valores, qué nombres usar y qué condiciones deben cumplir.

Un objeto JavaScript permite agrupar datos que pertenecen a una misma entidad. La estructura elegida debe hacer visibles las decisiones físicas y dificultar estados imposibles.

Representar no es solo almacenar números: es codificar el significado y los límites del modelo.

26.2 Separar identidad, parámetros y estado

IdentidadNombre o identificador estable para distinguir el cuerpo.
ParámetrosMasa y otras propiedades que definen el modelo.
EstadoPosición y velocidad que pueden cambiar con el tiempo.
MetadatosEtiquetas útiles para depuración, visualización o selección.

La aceleración no suele guardarse como estado fundamental: puede calcularse a partir de las fuerzas del instante. Evitar datos derivados duplicados reduce inconsistencias.

26.3 Vectores como objetos

En dos dimensiones podemos representar un vector mediante propiedades x e y:

function vector(x = 0, y = 0) {
  if (!Number.isFinite(x) || !Number.isFinite(y)) {
    throw new TypeError('Las componentes deben ser números finitos');
  }
  return { x, y };
}

const posicion = vector(3, 1.5);
const velocidad = vector(-2, 4);
console.log({ posicion, velocidad });

Los nombres x e y no incluyen unidades. La documentación o el nombre del campo contenedor debe establecer que, por ejemplo, la posición está en metros y la velocidad en metros por segundo.

26.4 Estructura mínima de un cuerpo

Un cuerpo puntual móvil puede representarse así:

{ id, masaKg, posicionM: {x, y}, velocidadMps: {x, y}, movil }

Los sufijos de unidades hacen explícito qué espera cada campo. Una alternativa válida es declarar una convención global del motor, pero debe mantenerse sin excepciones.

Conviene que id sea único y estable. El nombre visible puede cambiar sin romper referencias internas.

26.5 Cuerpos móviles e inmóviles

Una pared o el suelo pueden modelarse como cuerpos que no responden dinámicamente a las fuerzas. No es necesario asignarles una masa numérica gigantesca.

Cuerpo móvil: masa finita y positiva.
Cuerpo inmóvil: movil = false y aceleración impuesta en cero.

Muchos motores almacenan la masa inversa:

masaInversa = movil ? 1/masaKg : 0

Esto evita dividir repetidamente y permite que una masa inversa cero represente un cuerpo que no acelera. No significa que su masa física sea literalmente infinita.

26.6 Una fábrica con valores iniciales seguros

Una función de creación centraliza valores predeterminados, copias y validaciones:

function crearCuerpo({
  id,
  masaKg,
  posicionM = { x: 0, y: 0 },
  velocidadMps = { x: 0, y: 0 },
  movil = true
}) {
  if (!id) throw new TypeError('El cuerpo necesita un id');
  if (movil && (!Number.isFinite(masaKg) || masaKg <= 0)) {
    throw new RangeError('La masa móvil debe ser finita y positiva');
  }

  return {
    id,
    masaKg: movil ? masaKg : null,
    masaInversa: movil ? 1 / masaKg : 0,
    posicionM: { ...posicionM },
    velocidadMps: { ...velocidadMps },
    movil
  };
}

console.log(crearCuerpo({ id: 'caja', masaKg: 5 }));

Copiar los vectores de entrada evita que otro código los modifique accidentalmente después de crear el cuerpo.

26.7 Representar una fuerza como dato

Una fuerza evaluada en un instante necesita al menos un receptor y un vector. Agregar el tipo y el agente mejora la trazabilidad:

{ tipo, agenteId, receptorId, vectorN: {x, y} }

Ejemplo conceptual:

{ tipo: 'peso', agenteId: 'tierra', receptorId: 'caja', vectorN: { x: 0, y: -49.05 } }

El vector debe describir la fuerza sobre el receptor. Esta convención evita ambigüedades al registrar pares de interacción.

26.8 Fuerza instantánea y regla de fuerza

Conviene distinguir dos conceptos:

Regla
Función que recibe estado y tiempo.
→
Evaluación
Calcula el vector actual.
→
Fuerza
Dato instantáneo en newtons.

Una regla de peso uniforme puede producir un nuevo objeto fuerza en cada evaluación. La regla permanece configurada; el vector resultante puede cambiar si cambian sus parámetros o el estado.

No deben serializarse funciones como si fueran datos JSON. Es mejor guardar el nombre del tipo y sus parámetros, y reconstruir la regla al cargar.

26.9 Unidades visibles en la estructura

JavaScript no distingue metros de segundos. La seguridad depende de convenciones consistentes:

CampoSignificadoUnidad
masaKgMasa inercialkg
posicionMPosición cartesianam
velocidadMpsVelocidad cartesianam/s
vectorNVector fuerzaN
tiempoSTiempo del modelos

También debe definirse cómo se convierten las coordenadas físicas a píxeles. Guardar la posición física directamente en píxeles mezcla el modelo con la vista.

26.10 Invariantes y validación

Una invariante es una condición que todo objeto válido debe conservar. Validarla al crear o actualizar datos hace que los errores aparezcan cerca de su origen.

MasaFinita y positiva para un cuerpo móvil.
ComponentesNúmeros finitos, nunca NaN ni infinito.
Identidadid no vacío y único dentro del mundo.
CoherenciamasaInversa = 1/masaKg para cuerpos móviles.

También pueden validarse límites propios de cada modelo: coeficientes no negativos, radios positivos y referencias a cuerpos existentes.

26.11 Referencias compartidas y aliasing

Los objetos JavaScript se asignan por referencia. Si dos cuerpos reciben el mismo objeto posición, modificar uno también modificará el otro:

cuerpoA.posicionM === cuerpoB.posicionM // no debería ser true

Este problema se denomina aliasing. Para evitarlo, cada cuerpo debe poseer sus propios vectores mutables o usar estructuras inmutables.

La copia superficial { ...vector } alcanza para un vector plano con números. Para estructuras anidadas hace falta copiar cada nivel relevante.

26.12 Estado mutable e instantáneas

Durante una simulación puede ser eficiente actualizar el estado actual. Aun así, para comparar instantes, depurar o interpolar conviene crear una instantánea independiente:

{ posicionM: { ...cuerpo.posicionM }, velocidadMps: { ...cuerpo.velocidadMps } }

Guardar una referencia al mismo vector no conserva el pasado: cuando el estado actual cambie, el supuesto historial también cambiará.

Debe quedar claro qué funciones mutan objetos y cuáles devuelven copias. Mezclar ambos estilos sin una convención genera errores difíciles de rastrear.

26.13 Serializar y reconstruir

JSON.stringify puede guardar números, cadenas, booleanos, arreglos y objetos simples, pero no conserva funciones, undefined ni prototipos personalizados.

Una escena serializable puede almacenar:

{ cuerpos: [...], reglas: [{ tipo: 'gravedadUniforme', g: { x: 0, y: -9.81 } }] }

Al cargarla, un registro de fábricas transforma cada descripción de regla en la función correspondiente. Después de analizar JSON externo deben repetirse todas las validaciones; que el texto sea JSON válido no garantiza que represente un mundo físico válido.

26.14 Actividad interactiva: inspeccionar un cuerpo

Modificá los datos del cuerpo. El laboratorio genera una representación serializable y una fuerza peso instantánea separada.

Inspector del modelo de datos

Las coordenadas físicas permanecen en metros; el canvas realiza su propia conversión a píxeles.

Objeto cuerpo

Fuerza evaluada

26.15 Datos derivados y fuente única de verdad

Si un valor puede calcularse de manera confiable a partir de otros, almacenarlo por duplicado crea dos posibles versiones de la verdad.

DatoDecisión recomendada
Aceleración actualCalcularla desde fuerza resultante y masa.
RapidezCalcularla con Math.hypot(v.x, v.y).
Masa inversaPuede almacenarse por rendimiento, pero debe actualizarse junto con la masa.
Posición en píxelesCalcularla en la capa de visualización desde la posición física.
Fuerza pesoEvaluarla a partir de masa y campo actual.

Cuando se decide almacenar un dato derivado por rendimiento, la actualización debe centralizarse para conservar la invariante.

26.16 Pruebas del modelo

Las fábricas y validadores pueden probarse sin ejecutar una simulación completa:

  • Crear un cuerpo móvil válido y comprobar sus valores predeterminados.
  • Rechazar masa cero, negativa, infinita o NaN.
  • Comprobar que los vectores de entrada fueron copiados.
  • Crear un cuerpo inmóvil y verificar masaInversa = 0.
  • Serializar, reconstruir y comparar todos los datos físicos.
  • Confirmar que cada fuerza señala un receptor existente.

Probar estas piezas pequeñas reduce la cantidad de causas posibles cuando más adelante falle el acumulador o el integrador.

26.17 Errores frecuentes y ejercicios

Errores frecuentes

  • Mezclar metros y píxeles en el mismo campo.
  • Usar nombres genéricos como valor o vector sin contexto ni unidad.
  • Permitir masas móviles nulas o negativas.
  • Compartir accidentalmente el mismo objeto posición entre cuerpos.
  • Guardar aceleración y fuerza resultante sin mantenerlas sincronizadas.
  • Serializar funciones y esperar que JSON pueda reconstruirlas.

Ejercicios propuestos

  1. Diseñá el objeto de un cuerpo llamado pelota, de 2 kg, en (3; 4) m y con velocidad (−1; 5) m/s.
  2. Calculá y agregá su masa inversa.
  3. Representá el peso terrestre sobre la pelota indicando tipo, agente, receptor y vector.
  4. Explicá por qué { masaKg: 0, movil: true } debe rechazarse.
  5. Escribí una instantánea independiente de posición y velocidad para la pelota.
  6. Indicá qué parte de una regla de fuerza debe guardarse en JSON y cuál debe reconstruirse.
Ver soluciones
  1. { id: 'pelota', masaKg: 2, posicionM: {x: 3, y: 4}, velocidadMps: {x: -1, y: 5}, movil: true }.
  2. masaInversa = 1/2 = 0,5 kg−1.
  3. { tipo: 'peso', agenteId: 'tierra', receptorId: 'pelota', vectorN: {x: 0, y: -19.62} }.
  4. La segunda ley requeriría dividir por cero y el estado no representa un cuerpo móvil clásico válido.
  5. { posicionM: { ...pelota.posicionM }, velocidadMps: { ...pelota.velocidadMps } }.
  6. Se guardan el tipo y los parámetros serializables; la función que evalúa la regla se reconstruye desde un registro del programa.

26.18 Ideas para recordar

  • La estructura de datos debe reflejar el significado físico del modelo.
  • Identidad, parámetros, estado y fuerzas cumplen funciones diferentes.
  • Los nombres y convenciones deben hacer visibles las unidades.
  • Las fábricas centralizan valores iniciales, copias y validaciones.
  • Los objetos anidados requieren cuidado con referencias compartidas.
  • JSON guarda datos; las reglas de comportamiento deben reconstruirse.

En el próximo tema construiremos un acumulador de fuerzas y calcularemos la aceleración resultante de cada cuerpo.