33.1 De los datos a los cálculos
En el tema anterior construimos representaciones para fluidos y partículas. Ahora agregaremos funciones físicas sin mezclar cálculo, interfaz y almacenamiento.
Una función bien diseñada declara qué recibe, qué devuelve, qué unidades utiliza y bajo qué hipótesis es válida. Esa claridad permite reutilizarla en una calculadora, una prueba o una simulación.
33.2 Funciones puras
Una función pura depende solo de sus argumentos y no modifica estado externo. Para los mismos datos produce el mismo resultado.
function presionHidrostatica({ densidad, profundidad, g = 9.81 }) {
return densidad * g * profundidad;
}
console.log(presionHidrostatica({ densidad: 1000, profundidad: 5 }));Esta función puede probarse sin HTML, canvas ni variables globales.
33.3 Contratos y unidades
Adoptaremos entradas y salidas en SI. El contrato de presión anterior es:
Los nombres completos reducen ambigüedad. Una función pública debe documentar si una presión es absoluta o manométrica y si un caudal es volumétrico o másico.
33.4 Validación reutilizable
function exigirPositivo(nombre, valor, permiteCero = false) {
const valido = Number.isFinite(valor) && (permiteCero ? valor >= 0 : valor > 0);
if (!valido) throw new RangeError(`${nombre} debe ser ${permiteCero ? "no negativo" : "positivo"}`);
return valor;
}
console.log(exigirPositivo("densidad", 1000));
console.log(exigirPositivo("profundidad", 0, true));Fallar temprano señala el origen del problema en vez de propagar NaN por cálculos posteriores.
33.5 Presión manométrica y absoluta
function presionesEnProfundidad({ densidad, profundidad, presionSuperficie, g = 9.81 }) {
if (densidad <= 0 || profundidad < 0 || presionSuperficie < 0) {
throw new RangeError("Datos físicos inválidos");
}
const manometrica = densidad * g * profundidad;
return {
manometrica,
absoluta: presionSuperficie + manometrica
};
}
const p = presionesEnProfundidad({ densidad: 1000, profundidad: 5, presionSuperficie: 101325 });
console.log(`Manométrica: ${(p.manometrica / 1000).toFixed(2)} kPa`);
console.log(`Absoluta: ${(p.absoluta / 1000).toFixed(2)} kPa`);33.6 Fuerza sobre una superficie horizontal
Si toda el área A está a la misma profundidad y la presión es uniforme:
Para calcular la fuerza neta sobre una tapa con atmósfera del otro lado se usa presión manométrica. Si el otro lado es vacío u otra presión, debe incluirse esa condición explícitamente.
33.7 Superficies planas inclinadas o verticales
En un líquido de densidad constante, la fuerza resultante sobre una superficie plana es:
hc es la profundidad del centroide. El centro de presión no coincide generalmente con el centroide porque la presión aumenta con profundidad. Calcular solo F no determina su punto de aplicación.
33.8 Empuje
function empuje({ densidadFluido, volumenDesplazado, g = 9.81 }) {
if (densidadFluido <= 0 || volumenDesplazado < 0) throw new RangeError("Datos inválidos");
return densidadFluido * g * volumenDesplazado;
}
console.log(`Empuje: ${empuje({ densidadFluido: 1000, volumenDesplazado: 0.01 }).toFixed(2)} N`);La entrada es volumen desplazado, que no siempre coincide con el volumen total del cuerpo.
33.9 Peso aparente y fuerza neta
Para un cuerpo totalmente sumergido:
Un resultado positivo con eje vertical hacia arriba indica tendencia ascendente. Definir el signo evita devolver únicamente un texto ambiguo.
33.10 Fracción sumergida en flotación
Para un cuerpo homogéneo que flota en equilibrio:
Si el cociente supera 1, no existe equilibrio parcialmente sumergido: el cuerpo se hunde salvo que intervenga otra fuerza.
33.11 Función completa de flotación
function analizarCuerpo({ masa, volumen, densidadFluido, g = 9.81 }) {
if (masa <= 0 || volumen <= 0 || densidadFluido <= 0) throw new RangeError("Datos inválidos");
const densidadCuerpo = masa / volumen;
const peso = masa * g;
const empujeTotal = densidadFluido * g * volumen;
return {
densidadCuerpo,
peso,
empujeTotal,
fuerzaNetaSumergido: empujeTotal - peso,
fraccionSumergidaEquilibrio: Math.min(densidadCuerpo / densidadFluido, 1),
flota: densidadCuerpo < densidadFluido
};
}
console.log(analizarCuerpo({ masa: 8, volumen: 0.01, densidadFluido: 1000 }));33.12 Caudal volumétrico
Para velocidad media normal a una sección:
La función debe recibir velocidad media, no una velocidad puntual arbitraria. En una sección circular A = πD²/4.
33.13 Continuidad entre secciones
function continuidadCircular({ diametro1, diametro2, velocidad1 }) {
if (diametro1 <= 0 || diametro2 <= 0 || velocidad1 < 0) throw new RangeError("Datos inválidos");
const area1 = Math.PI * diametro1 ** 2 / 4;
const area2 = Math.PI * diametro2 ** 2 / 4;
const caudal = area1 * velocidad1;
return { area1, area2, caudal, velocidad2: caudal / area2 };
}
const r = continuidadCircular({ diametro1: 0.08, diametro2: 0.04, velocidad1: 2 });
console.log(`Q = ${(r.caudal * 1000).toFixed(3)} L/s`);
console.log(`v2 = ${r.velocidad2.toFixed(2)} m/s`);33.14 Caudal másico
Para densidad uniforme sobre la sección:
En flujo compresible estacionario se conserva ṁ, no necesariamente Q. Una función general de continuidad debe permitir densidades diferentes en entrada y salida.
33.15 Ramificaciones
En régimen estacionario:
Para líquido incompresible de igual densidad, se simplifica a suma de caudales volumétricos. Es útil representar cada conexión con un signo: positivo entrando al volumen de control y negativo saliendo.
33.16 Balance genérico entrada–salida
function balance({ entradas, salidas }) {
const suma = valores => valores.reduce((total, valor) => total + valor, 0);
return {
entradaTotal: suma(entradas),
salidaTotal: suma(salidas),
acumulacion: suma(entradas) - suma(salidas)
};
}
const masa = balance({ entradas: [5, 2], salidas: [4.5, 1] });
console.log(`Acumulación: ${masa.acumulacion.toFixed(2)} kg/s`);La misma estructura sirve para masa, energía o cantidad de una especie si las unidades de todos los términos son compatibles.
33.17 Nivel de un tanque
En un tanque de sección horizontal constante A y líquido incompresible:
Si ambos caudales son constantes, dh/dt es constante y el tiempo para pasar de h0 a hobjetivo se obtiene directamente. Si Qout depende de h, aparece una ecuación diferencial.
33.18 Balance estacionario y transitorio
Estacionario significa acumulación cero, no ausencia de flujo:
El código debe distinguir una tasa de cambio de un cambio finito. dhdt y deltaH no representan lo mismo.
33.19 Paso de Euler para un tanque
function avanzarTanque({ nivel, area, caudalEntrada, caudalSalida, dt }) {
if (area <= 0 || dt <= 0) throw new RangeError("Área y dt deben ser positivos");
const dhdt = (caudalEntrada - caudalSalida) / area;
return Math.max(0, nivel + dhdt * dt);
}
let nivel = 1;
for (let paso = 0; paso < 10; paso++) {
nivel = avanzarTanque({ nivel, area: 2, caudalEntrada: 0.012, caudalSalida: 0.005, dt: 1 });
}
console.log(`Nivel después de 10 s: ${nivel.toFixed(3)} m`);Limitar a cero evita nivel negativo, pero una simulación rigurosa también debe ajustar el instante exacto de vaciado.
33.20 Salida dependiente del nivel
Con un orificio:
La salida debe recalcularse en cada paso. Guardar una única Qout inicial y usarla durante todo el vaciado rompe el acoplamiento físico.
33.21 Cálculos directos e inversos
Una función directa obtiene salida desde parámetros. Un problema inverso despeja un parámetro desde el resultado deseado: profundidad desde presión, volumen desde empuje o diámetro desde caudal.
Cuando no hay despeje simple, puede usarse bisección. El intervalo debe contener un cambio de signo y la función debe ser continua dentro de él.
33.22 Resultados con significado
Devolver un objeto evita recordar posiciones:
En aplicaciones mayores puede emplearse un sistema de cantidades con unidades. En este curso mantendremos números SI y nombres descriptivos para no ocultar la física.
33.23 Redondear solo para mostrar
toFixed devuelve texto. Si se redondean resultados intermedios, el error se acumula. La regla práctica es calcular con el número completo y formatear al presentar.
const presionPa = 1000 * 9.81 * 3.4567;
const paraMostrar = `${(presionPa / 1000).toFixed(2)} kPa`;
console.log(presionPa); // número para cálculos posteriores
console.log(paraMostrar); // texto para la interfaz33.24 Comparar números con tolerancia
Las operaciones de punto flotante no siempre producen igualdad decimal exacta. Para pruebas:
function casiIguales(a, b, toleranciaRelativa = 1e-10, toleranciaAbsoluta = 1e-12) {
const diferencia = Math.abs(a - b);
return diferencia <= Math.max(toleranciaAbsoluta, toleranciaRelativa * Math.max(Math.abs(a), Math.abs(b)));
}
console.log(casiIguales(0.1 + 0.2, 0.3));La tolerancia debe responder a la escala y precisión del problema, no elegirse para hacer pasar cualquier resultado.
33.25 Pruebas con casos límite
- h = 0 debe producir presión manométrica cero;
- Vdesplazado = 0 debe producir empuje cero;
- áreas iguales deben conservar velocidad en continuidad;
- entrada igual a salida debe producir acumulación cero;
- duplicar profundidad debe duplicar Δp;
- duplicar diámetro debe cuadruplicar el área;
- entradas negativas no físicas deben generar error.
33.26 Pruebas de invariantes
Además de casos conocidos, pueden probarse propiedades generales: el empuje no disminuye al aumentar volumen, la presión hidrostática no disminuye con profundidad y un balance cerrado conserva masa.
Estas pruebas encuentran errores en muchos valores sin enumerarlos uno por uno.
33.27 Procesamiento por lotes
const profundidades = [0, 1, 2, 5, 10];
const densidad = 1000;
const tabla = profundidades.map(profundidad => ({
profundidad,
presionKPa: densidad * 9.81 * profundidad / 1000
}));
console.log(tabla);Separar la generación de datos de la tabla HTML permite exportar los mismos resultados a CSV, gráfico o prueba automatizada.
33.28 Sensibilidad e incertidumbre
Una calculadora no debería ocultar que sus entradas son aproximadas. En Δp = ρgh, los errores relativos pequeños de ρ, g y h se combinan aproximadamente.
Puede evaluarse sensibilidad repitiendo el cálculo en extremos plausibles. Esta técnica también revela qué medición conviene mejorar.
33.29 Biblioteca mínima integrada
const fisica = Object.freeze({
presion: ({ rho, h, g = 9.81 }) => rho * g * h,
empuje: ({ rho, volumen, g = 9.81 }) => rho * g * volumen,
areaCircular: diametro => Math.PI * diametro ** 2 / 4,
caudal: ({ area, velocidadMedia }) => area * velocidadMedia,
caudalMasico: ({ rho, caudal }) => rho * caudal,
acumulacion: ({ entradas, salidas }) =>
entradas.reduce((s, x) => s + x, 0) - salidas.reduce((s, x) => s + x, 0)
});
const area = fisica.areaCircular(0.08);
const Q = fisica.caudal({ area, velocidadMedia: 2 });
console.log(`Presión: ${fisica.presion({ rho: 1000, h: 5 })} Pa`);
console.log(`Empuje: ${fisica.empuje({ rho: 1000, volumen: 0.01 })} N`);
console.log(`Caudal: ${(Q * 1000).toFixed(3)} L/s`);
console.log(`Acumulación: ${fisica.acumulacion({ entradas: [0.012], salidas: [0.005] })} m³/s`);33.30 Laboratorio de cálculos
Elegí una magnitud. Todos los controles muestran unidades de entrada y los resultados se derivan nuevamente en cada cambio.
Δp = 1000·9,81·5,00 = 49,05 kPa
La fuerza neta supone la misma presión superficial actuando sobre la cara opuesta.
33.31 Flujo de trabajo recomendado
- Definir el sistema, las hipótesis y el sentido positivo.
- Convertir entradas a SI en la frontera del programa.
- Validar finitud, signo y rango.
- Ejecutar funciones puras que no dependan de la interfaz.
- Comprobar unidades, límites e invariantes.
- Mantener números completos para cálculos encadenados.
- Formatear y convertir unidades únicamente para presentar.
- Agregar pruebas antes de ampliar el modelo.
33.32 Ejercicio integrador
Un tanque cilíndrico vertical de área 1,50 m² recibe agua a 8,00 L/s. Descarga por un orificio inferior de diámetro 50 mm con Cd = 0,62. El nivel inicial es 0,80 m.
- Implementá Qout(h) con Torricelli.
- Calculá salida y dh/dt iniciales.
- Obtené el nivel de equilibrio.
- Simulá con Euler y dt = 1 s hasta alcanzar 95 % del cambio hacia el equilibrio.
- Comprobá que el balance tienda a cero.
Ver solución y explicación
const areaTanque = 1.5;
const entrada = 8e-3;
const diametroOrificio = 0.05;
const cd = 0.62;
const g = 9.81;
const areaOrificio = Math.PI * diametroOrificio ** 2 / 4;
const salida = nivel => cd * areaOrificio * Math.sqrt(2 * g * Math.max(nivel, 0));
let nivel = 0.8;
const salidaInicial = salida(nivel);
const dhdtInicial = (entrada - salidaInicial) / areaTanque;
const nivelEquilibrio = (entrada / (cd * areaOrificio)) ** 2 / (2 * g);
const objetivo95 = nivelEquilibrio - 0.05 * (nivelEquilibrio - nivel);
let tiempo = 0;
const dt = 1;
while (nivel < objetivo95 && tiempo < 10000) {
nivel += (entrada - salida(nivel)) / areaTanque * dt;
tiempo += dt;
}
console.log(`Salida inicial: ${(salidaInicial * 1000).toFixed(3)} L/s`);
console.log(`dh/dt inicial: ${dhdtInicial.toFixed(6)} m/s`);
console.log(`Nivel de equilibrio: ${nivelEquilibrio.toFixed(3)} m`);
console.log(`95 % alcanzado en ${tiempo} s, nivel ${nivel.toFixed(3)} m`);
console.log(`Balance final: ${((entrada - salida(nivel)) * 1000).toFixed(3)} L/s`);La salida inicial es 4,823 L/s, dh/dt inicial = 0,002118 m/s y el nivel de equilibrio es 2,201 m. Con Euler y dt = 1 s, se alcanza 95 % del cambio alrededor de 2333 s.
33.33 Ideas para recordar
- Las funciones físicas deben declarar entradas, salidas, unidades y supuestos.
- Presión absoluta y manométrica no son intercambiables sin una referencia.
- El empuje usa volumen desplazado y el caudal usa velocidad media.
- En un balance, entrada menos salida es acumulación.
- Un estado estacionario puede tener grandes flujos con acumulación cero.
- Los caudales dependientes del estado deben recalcularse en cada paso.
- El redondeo pertenece a la presentación, no a los cálculos intermedios.
- Casos límite, tolerancias e invariantes hacen confiable una biblioteca.
- Separar física e interfaz permite reutilizar y probar el código.
En el próximo tema utilizaremos estas funciones para simular partículas transportadas por un flujo.