26.1 Por qué validar
Los datos de una interfaz llegan como texto y pueden estar incompletos, mal escritos o fuera del dominio financiero adoptado. JavaScript también posee valores especiales como NaN e Infinity que pueden propagarse silenciosamente.
Si falla la entrada, el programa debe detener el cálculo y explicar el problema.
Validar no demuestra que una decisión financiera sea conveniente; comprueba que los datos cumplen el contrato matemático y técnico de la función.
26.2 Cuatro etapas separadas
El orden es importante. No aplicamos la fórmula hasta saber que los valores convertidos son finitos y satisfacen las reglas del modelo.
26.3 Una entrada vacía no es cero
La conversión Number("") devuelve 0. Sin una comprobación previa, un campo vacío podría confundirse con un cero ingresado intencionalmente.
function convertirNumero(texto) {
if (typeof texto !== "string" || texto.trim() === "") {
return { ok: false, error: "El valor está vacío" };
}
const valor = Number(texto);
if (!Number.isFinite(valor)) {
return { ok: false, error: "El valor no es un número finito" };
}
return { ok: true, valor };
}
for (const entrada of ["", "abc", "2.5"]) {
console.log(JSON.stringify({ entrada, resultado: convertirNumero(entrada) }));
}El objeto devuelto distingue explícitamente éxito y error. Una cadena como "2.5" se convierte; "2,5" no es reconocida por Number y requiere una política de normalización específica.
26.4 Comprobar números finitos
typeof NaN devuelve "number", por lo que verificar solamente el tipo no alcanza. Utilizamos Number.isFinite:
| Valor | Number.isFinite | Interpretación |
|---|---|---|
| 250000 | true | Número finito |
| NaN | false | Resultado numérico indefinido |
| Infinity | false | Valor sin límite finito |
| "250000" | false | Es texto; debe convertirse primero |
No conviene reemplazar esta prueba con la función global isFinite, porque realiza conversiones implícitas que pueden ocultar errores.
26.5 Tipo válido y dominio válido
Un dato puede ser numéricamente válido y, aun así, no pertenecer al dominio del problema. Para los ejemplos elementales de crecimiento del curso adoptamos:
Número finito mayor que cero.
Número finito mayor o igual que cero.
Entero mayor o igual que cero.
Una tasa negativa puede tener sentido en otros contextos. Rechazarla aquí es una decisión del alcance didáctico, no una limitación universal de JavaScript.
26.6 Validar cantidades de períodos
Number.isInteger permite exigir períodos completos cuando el algoritmo itera una vez por cierre:
function periodosValidos(periodos, maximo = 1200) {
return Number.isInteger(periodos) &&
periodos >= 0 &&
periodos <= maximo;
}
console.log("12:", periodosValidos(12));
console.log("6.5:", periodosValidos(6.5));
console.log("-1:", periodosValidos(-1));
console.log("5000:", periodosValidos(5000));El límite superior no pertenece a la fórmula; protege recursos de la aplicación. Otro sistema puede elegir un máximo diferente.
26.7 Acumular errores útiles
Detenerse en el primer problema es sencillo, pero en un formulario puede resultar más cómodo informar todos los campos que deben corregirse:
function validarDatos({ capital, tasa, periodos }) {
const errores = [];
if (!Number.isFinite(capital) || capital <= 0) {
errores.push("El capital debe ser un número positivo");
}
if (!Number.isFinite(tasa) || tasa < 0) {
errores.push("La tasa debe ser finita y no negativa");
}
if (!Number.isInteger(periodos) || periodos < 0) {
errores.push("Los períodos deben ser enteros y no negativos");
}
return errores;
}
const errores = validarDatos({ capital: 0, tasa: NaN, periodos: 2.5 });
errores.forEach(error => console.log(error));Los mensajes describen cómo corregir el dato; un simple “valor incorrecto” obliga al usuario a adivinar.
26.8 Un contrato de resultado explícito
En lugar de devolver a veces un número y otras veces un texto, podemos utilizar siempre un objeto con una propiedad ok:
function dividirSeguro(dividendo, divisor) {
if (!Number.isFinite(dividendo) || !Number.isFinite(divisor)) {
return { ok: false, errores: ["Los valores deben ser finitos"] };
}
if (divisor === 0) {
return { ok: false, errores: ["El divisor no puede ser cero"] };
}
return { ok: true, valor: dividendo / divisor };
}
console.log(JSON.stringify(dividirSeguro(36000, 0.12)));
console.log(JSON.stringify(dividirSeguro(36000, 0)));Quien utiliza la función consulta primero ok. Así evita confundir un mensaje de error con un resultado financiero.
26.9 Devolver errores o lanzar una excepción
| Estrategia | Uso habitual | Responsabilidad de quien llama |
|---|---|---|
{ ok, valor, errores } | Datos esperablemente incorrectos de un formulario | Mostrar mensajes y no calcular |
throw new Error(...) | Incumplimiento inesperado del contrato interno | Capturar o propagar la excepción |
No existe una única estrategia para toda aplicación. Lo importante es que el contrato sea estable y que los errores no se transformen silenciosamente en resultados.
26.10 Validar opciones permitidas
Los textos que seleccionan una estrategia también se validan. No debemos invocar una propiedad inexistente:
const modelos = {
simple: (capital, tasa, periodos) => capital * (1 + tasa * periodos),
compuesto: (capital, tasa, periodos) => capital * (1 + tasa) ** periodos
};
function modeloValido(nombre) {
return Object.hasOwn(modelos, nombre);
}
console.log("simple:", modeloValido("simple"));
console.log("compuesto:", modeloValido("compuesto"));
console.log("desconocido:", modeloValido("desconocido"));Object.hasOwn comprueba que el nombre corresponda a una propiedad propia del catálogo.
26.11 Una función financiera protegida
Reunimos conversión, validación y cálculo sin mezclar el resultado correcto con los mensajes:
function calcularCompuestoSeguro(capital, tasa, periodos) {
const errores = [];
if (!Number.isFinite(capital) || capital <= 0) {
errores.push("Capital no válido");
}
if (!Number.isFinite(tasa) || tasa < 0) {
errores.push("Tasa no válida");
}
if (!Number.isInteger(periodos) || periodos < 0) {
errores.push("Períodos no válidos");
}
if (errores.length > 0) return { ok: false, errores };
const monto = capital * (1 + tasa) ** periodos;
if (!Number.isFinite(monto)) {
return { ok: false, errores: ["El resultado excede el rango numérico"] };
}
return { ok: true, valor: { capital, monto, interes: monto - capital } };
}
console.log(JSON.stringify(calcularCompuestoSeguro(200000, 0.05, 4)));
console.log(JSON.stringify(calcularCompuestoSeguro(-10, NaN, 2.5)));También verificamos la salida: entradas finitas pueden producir un desbordamiento cuando la potencia es extremadamente grande.
26.12 Probar caminos válidos e inválidos
Las pruebas deben recorrer ambos lados de cada condición, no solamente el caso ideal:
function datosValidos(capital, tasa, periodos) {
return Number.isFinite(capital) && capital > 0 &&
Number.isFinite(tasa) && tasa >= 0 &&
Number.isInteger(periodos) && periodos >= 0;
}
const pruebas = [
{ nombre: "caso normal", datos: [100000, 0.05, 6], esperado: true },
{ nombre: "capital vacío convertido", datos: [0, 0.05, 6], esperado: false },
{ nombre: "tasa indefinida", datos: [100000, NaN, 6], esperado: false },
{ nombre: "plazo decimal", datos: [100000, 0.05, 6.5], esperado: false }
];
pruebas.forEach(prueba => {
const obtenido = datosValidos(...prueba.datos);
console.log(`${prueba.nombre}: ${obtenido === prueba.esperado ? "correcta" : "falló"}`);
});Cada error corregido debería convertirse en un nuevo caso de prueba para evitar que reaparezca.
26.13 Laboratorio de validación
Escribí valores válidos o deliberadamente incorrectos. El laboratorio aplica cada regla, marca los campos y solo calcula cuando todas las comprobaciones se cumplen.
Puerta de entrada al cálculo
Leer, convertir, validar y recién entonces aplicar la fórmula.
| Campo | Texto recibido | Regla | Resultado |
|---|
Mensajes de validación
- No se encontraron errores.
calcular("compuesto", 200000, 0.05, 4) → 243101.25El cálculo se ejecutó porque las cuatro entradas cumplen el contrato.
26.14 Ejercicio propuesto
Implementá validarOperacion para un capital, una tasa porcentual y una cantidad de períodos recibidos como texto.
- Rechazá cadenas vacías antes de convertir.
- Exigí números finitos.
- Exigí capital positivo, tasa entre 0 % y 100 % y períodos enteros entre 0 y 120.
- Devolvé todos los mensajes encontrados.
- Calculá el monto simple únicamente cuando
oksea verdadero.
Ver solución y código
function validarOperacion(entrada) {
const errores = [];
function leer(campo, etiqueta) {
const texto = entrada[campo];
if (typeof texto !== "string" || texto.trim() === "") {
errores.push(`${etiqueta}: valor requerido`);
return NaN;
}
const numero = Number(texto);
if (!Number.isFinite(numero)) errores.push(`${etiqueta}: número no válido`);
return numero;
}
const capital = leer("capital", "Capital");
const tasaPorcentaje = leer("tasa", "Tasa");
const periodos = leer("periodos", "Períodos");
if (Number.isFinite(capital) && capital <= 0) errores.push("Capital: debe ser positivo");
if (Number.isFinite(tasaPorcentaje) && (tasaPorcentaje < 0 || tasaPorcentaje > 100)) errores.push("Tasa: fuera de rango");
if (Number.isFinite(periodos) && (!Number.isInteger(periodos) || periodos < 0 || periodos > 120)) errores.push("Períodos: deben ser enteros entre 0 y 120");
if (errores.length) return { ok: false, errores };
const tasa = tasaPorcentaje / 100;
const monto = capital * (1 + tasa * periodos);
return { ok: true, valor: { capital, tasa, periodos, monto } };
}
console.log(JSON.stringify(validarOperacion({ capital: "300000", tasa: "2", periodos: "12" })));
console.log(JSON.stringify(validarOperacion({ capital: "", tasa: "abc", periodos: "2.5" })));26.15 Ideas para recordar
- Los datos de una interfaz comienzan como texto y deben convertirse explícitamente.
- Un campo vacío no equivale necesariamente a cero.
Number.isFiniteexcluyeNaNe infinitos sin conversiones implícitas.- Validar el tipo no reemplaza las reglas del dominio financiero.
- Los límites de iteración protegen los recursos de la aplicación.
- Un contrato con
ok,valoryerroressepara resultados de fallos. - Los mensajes deben indicar cómo corregir el dato.
- Las pruebas incluyen casos válidos, fronteras y entradas incorrectas.
En el próximo tema construiremos tablas de evolución y comparación de escenarios.