23. Representación y conversión de unidades con JavaScript

Un conversor confiable no opera con números aislados: conserva magnitud y unidad, valida compatibilidad y hace explícita cada transformación.

23.1 El problema de los números sin unidad

El valor 25 podría representar metros, segundos, grados Celsius o cualquier otra cantidad. Si un programa guarda solamente el número, quien lo recibe debe adivinar su significado.

Una representación mínima reúne valor, unidad y magnitud. Esta información permite validar operaciones antes de producir resultados físicamente absurdos.

23.2 Un primer modelo de cantidad

const longitud = {
  value: 2.5,
  unit: "m",
  quantity: "length"
};
console.log("Medición:", longitud.value, longitud.unit);
console.log("Magnitud:", longitud.quantity);

Los nombres internos pueden mantenerse en inglés o español, pero deben ser consistentes. El símbolo mostrado al usuario no siempre es la mejor clave: puede contener caracteres Unicode o ser ambiguo.

23.3 Separar datos, reglas y presentación

  • Datos: el valor y la unidad elegida.
  • Reglas: factores, desplazamientos y compatibilidad.
  • Presentación: idioma, símbolo, redondeo y formato.

Separar estas responsabilidades permite probar la lógica sin depender del HTML y traducir la interfaz sin modificar las conversiones.

23.4 Un registro declarativo de unidades

const units = {
  m:  { quantity: "length", symbol: "m",  scale: 1 },
  cm: { quantity: "length", symbol: "cm", scale: 0.01 },
  ft: { quantity: "length", symbol: "ft", scale: 0.3048 },
  s:  { quantity: "time",   symbol: "s",  scale: 1 }
};
console.log("Unidades registradas:", Object.keys(units).join(", "));
console.log("1 m =", 1 / units.cm.scale, "cm");

scale convierte hacia la unidad base elegida para cada magnitud. Agregar una unidad se vuelve una operación de datos y no exige copiar la función de conversión.

23.5 La conversión en tres pasos

  1. Validar origen y destino
  2. Convertir a unidad base
  3. Convertir desde la base
valor destino = valor origen × escala origen / escala destino

Este patrón evita mantener una tabla para cada par posible. Con diez unidades harían falta hasta noventa conversiones dirigidas; con una referencia interna basta definir diez escalas.

23.6 Validar compatibilidad dimensional

const units = {
  m: { quantity: "length", scale: 1 },
  cm: { quantity: "length", scale: 0.01 },
  s: { quantity: "time", scale: 1 }
};

function convert(value, fromKey, toKey) {
  const from = units[fromKey];
  const to = units[toKey];

  if (!from || !to) throw new Error("Unidad desconocida");
  if (from.quantity !== to.quantity) {
    throw new Error("Magnitudes incompatibles");
  }

  return value * from.scale / to.scale;
}
console.log("2,5 m =", convert(2.5, "m", "cm"), "cm");

La función rechaza metros a segundos antes de calcular. Un resultado numérico finito no demuestra por sí solo que la operación tenga sentido físico.

23.7 Escalas afines: temperatura

Una escala con origen desplazado necesita dos funciones, no solo un factor:

const celsius = {
  quantity: "temperature",
  symbol: "°C",
  toBase: value => value + 273.15,
  fromBase: kelvin => kelvin - 273.15
};
const temperaturaK = celsius.toBase(20);
console.log("20 °C =", temperaturaK, "K");
console.log(temperaturaK, "K =", celsius.fromBase(temperaturaK), "°C");

Fahrenheit combina escala y desplazamiento. Además, los intervalos térmicos deben pertenecer a otro tipo para no sumar 273,15 a una diferencia.

23.8 Unificar conversiones lineales y afines

function linearUnit(quantity, symbol, scale) {
  return {
    quantity,
    symbol,
    toBase: value => value * scale,
    fromBase: value => value / scale
  };
}
const centimetre = linearUnit("length", "cm", 0.01);
console.log("250 cm =", centimetre.toBase(250), "m");

Al dar a todas las unidades la misma interfaz, la función general solo llama from.toBase() y to.fromBase(). No necesita conocer cada fórmula particular.

23.9 Inmutabilidad y resultados nuevos

Una conversión no debería cambiar silenciosamente el objeto recibido. Es preferible devolver una cantidad nueva:

const scales = { m: 1, cm: 0.01 };
const convert = (value, sourceKey, targetKey) =>
  value * scales[sourceKey] / scales[targetKey];

function convertQuantity(input, targetKey) {
  const converted = convert(input.value, input.unit, targetKey);
  return Object.freeze({ ...input, value: converted, unit: targetKey });
}
const input = { value: 1.25, unit: "m", quantity: "length" };
const output = convertQuantity(input, "cm");
console.log("Original:", input.value, input.unit);
console.log("Conversión:", output.value, output.unit);

La inmutabilidad facilita rastrear valores, comparar resultados y evitar efectos secundarios inesperados.

23.10 Analizar entradas del usuario

La interfaz puede aceptar coma o punto decimal, pero la lógica debe transformar la entrada de manera controlada y comprobar que sea finita:

function parseSpanishNumber(text) {
  const normalized = text.trim().replace(",", ".");
  const value = Number(normalized);
  if (!Number.isFinite(value)) throw new Error("Número no válido");
  return value;
}
console.log("Valor interpretado:", parseSpanishNumber("12,75"));

Este ejemplo sencillo no admite separadores de miles. En una aplicación internacional se necesita una política explícita para cada formato.

23.11 Símbolos, claves y alias

La clave interna puede ser degC, mientras que el símbolo visible es °C. Los alias permiten reconocer entradas como meter, metre o metro sin duplicar la definición física.

Los alias se normalizan hacia una clave canónica antes de convertir. Nunca deben hacer que dos unidades incompatibles compartan una misma clave.

23.12 Dimensiones como vectores

Para operaciones más generales puede representarse una dimensión mediante exponentes de magnitudes base. Una velocidad tiene L¹T⁻¹ y una fuerza, M¹L¹T⁻².

const dimensions = {
  metre:   { L: 1, M: 0, T: 0 },
  second:  { L: 0, M: 0, T: 1 },
  newton:  { L: 1, M: 1, T: -2 }
};
console.log("Dimensión del newton:", dimensions.newton);
console.log("Dimensión del metro:", dimensions.metre);

Multiplicar cantidades suma exponentes; dividirlos los resta. Así puede comprobarse que kg·m/s² es compatible con N.

23.13 Prefijos del SI

En lugar de registrar manualmente cada combinación, una función puede construir unidades prefijadas:

const prefixes = { k: 1e3, c: 1e-2, m: 1e-3, µ: 1e-6 };

function linearUnit(quantity, symbol, scale) {
  return { quantity, symbol, scale };
}

function prefixed(baseUnit, prefix) {
  return linearUnit(
    baseUnit.quantity,
    prefix + baseUnit.symbol,
    prefixes[prefix] * baseUnit.scale
  );
}
const metre = linearUnit("length", "m", 1);
const kilometre = prefixed(metre, "k");
console.log("Unidad generada:", kilometre.symbol);
console.log("Escala hacia metros:", kilometre.scale);

Debe evitarse interpretar kg como prefijo aplicado de forma ingenua a g cuando la unidad base SI de masa ya es el kilogramo.

23.14 Precisión de punto flotante

JavaScript utiliza números binarios de doble precisión. Algunas fracciones decimales no se representan exactamente: 0.1 + 0.2 no produce internamente exactamente 0.3.

No se debe redondear después de cada operación. Para comparar resultados se utiliza una tolerancia apropiada; para dinero u otros dominios pueden emplearse enteros escalados o bibliotecas decimales.

23.15 Formatear sin destruir el dato

const formatter = new Intl.NumberFormat("es-AR", {
  maximumSignificantDigits: 8,
  useGrouping: false
});

const result = { value: 1234.56789, unit: "m" };
const visible = formatter.format(result.value);
console.log("Valor almacenado:", result.value);
console.log("Valor presentado:", visible, result.unit);

El valor numérico completo continúa disponible para nuevos cálculos. El redondeo de presentación se aplica únicamente al texto visible.

23.16 Laboratorio de conversión programada

El laboratorio utiliza un registro de objetos con toBase y fromBase. Al cambiar la magnitud, reconstruye las unidades compatibles y genera un ejemplo de llamada.

Inspector del registro de unidades

Observá el recorrido del valor y el código equivalente.

Valor en unidad base
Validación dimensionalCompatible

23.17 Manejo de errores

Los errores deben ser específicos y comprensibles: entrada vacía, unidad desconocida, magnitudes incompatibles, división por cero o temperatura inferior al cero absoluto.

const input = { value: 2.5, unit: "m" };
const target = "cm";
const convertQuantity = (quantity, destination) => ({ value: quantity.value * 100, unit: destination });
const showResult = result => console.log("Resultado:", result.value, result.unit);
const showMessage = message => console.log("Mensaje:", message);

try {
  const result = convertQuantity(input, target);
  showResult(result);
} catch (error) {
  showMessage(error.message);
}

En una interfaz, el error se muestra cerca del control afectado y no se reemplaza silenciosamente por cero.

23.18 Pruebas automatizadas

Un conversor se presta a pruebas pequeñas y deterministas:

const convert = (value, source, destination) => {
  const length = { m: 1, cm: 0.01, ft: 0.3048 };
  if (source === "degF" && destination === "degC") return (value - 32) * 5 / 9;
  return value * length[source] / length[destination];
};

const assert = (condition, message) => {
  if (!condition) throw new Error(message);
};
assert(convert(1, "m", "cm") === 100, "Falló m → cm");
assert(Math.abs(convert(32, "degF", "degC")) < 1e-12, "Falló °F → °C");
assert(convert(1, "ft", "m") === 0.3048, "Falló ft → m");
console.log("Pruebas superadas: 3 de 3");

También deben probarse conversiones inversas, cero, valores negativos permitidos, números muy grandes, entradas no válidas y rechazo de dimensiones incompatibles.

23.19 Propiedades útiles para probar

  • Identidad: convertir una unidad hacia sí misma conserva el valor.
  • Ida y vuelta: convertir A → B → A recupera el valor dentro de una tolerancia.
  • Composición: A → B → C coincide con A → C.
  • Compatibilidad: nunca se convierten magnitudes diferentes.

Estas propiedades pueden generar muchos casos automáticamente y detectar errores que no aparecen en ejemplos manuales.

23.20 Incertidumbre junto con el valor

Una cantidad medida puede incorporar incertidumbre estándar. Para conversiones lineales, valor e incertidumbre se escalan por el valor absoluto del mismo factor. En escalas afines, el desplazamiento no afecta la incertidumbre.

const measured = {
  value: 2.50,
  uncertainty: 0.01,
  unit: "m",
  quantity: "length"
};
const measuredCm = { ...measured, value: measured.value * 100,
  uncertainty: measured.uncertainty * 100, unit: "cm" };
console.log("Original:", measured.value, "±", measured.uncertainty, measured.unit);
console.log("Convertida:", measuredCm.value, "±", measuredCm.uncertainty, measuredCm.unit);

El programa debe indicar qué tipo de incertidumbre almacena y evitar mezclar incertidumbre estándar con expandida.

23.21 Errores frecuentes

  • Guardar números sin unidad ni tipo de magnitud.
  • Crear una función distinta para cada par de unidades.
  • Usar un único factor para temperaturas absolutas.
  • Comparar resultados de punto flotante siempre con ===.
  • Mezclar lógica de conversión con texto localizado.
  • Redondear el valor interno para que coincida con la pantalla.
  • Aceptar unidades desconocidas o incompatibles sin informar el error.

23.22 Ejercicios propuestos

  1. Agregá millas y pulgadas al registro de longitud.
  2. Implementá Fahrenheit con toBase y fromBase.
  3. Creá una función que convierta también la incertidumbre de una cantidad lineal.
  4. Escribí pruebas de identidad e ida y vuelta para todas las unidades.
  5. Representá las dimensiones de energía, presión y potencia como vectores.
  6. Modificá el analizador para aceptar un alias sin duplicar la unidad.

23.23 Ideas para recordar

  • Una cantidad necesita valor, unidad y magnitud.
  • Un registro declarativo concentra las reglas y facilita agregar unidades.
  • La conversión pasa por una referencia interna después de validar compatibilidad.
  • Temperaturas, intervalos y unidades lineales deben modelarse correctamente.
  • El dato interno conserva precisión; el formato pertenece a la presentación.
  • Las pruebas verifican ejemplos, errores y propiedades generales.

En el próximo tema integraremos estos conceptos en un conversor y validador completo de mediciones.