28.1 Objetivo del proyecto
Construiremos una calculadora capaz de recibir un capital, una tasa periódica, un plazo y un modelo. La aplicación mostrará monto, interés, factor, composición y evolución período a período.
Cada etapa posee una responsabilidad concreta y puede comprobarse por separado.
28.2 Requisitos funcionales
Modelo, capital, tasa porcentual, períodos y unidad.
Monto e interés simple y compuesto sin redondeos intermedios.
Tabla del modelo elegido y comparación final.
Campos vacíos, números finitos, rangos y períodos enteros.
Actualizar resultados cuando cambia una entrada.
Recuperar un escenario conocido de manera explícita.
28.3 Arquitectura por capas
Las fórmulas no deberían conocer los elementos HTML, y los campos no deberían contener fórmulas duplicadas. Esta separación permite cambiar la interfaz sin reescribir el modelo financiero.
28.4 Modelo de estado
Después de leer y validar, representamos la operación con un objeto coherente:
const estado = {
modelo: "compuesto",
capital: 350000,
tasa: 0.025,
periodos: 12,
unidad: "meses"
};
console.log("Modelo:", estado.modelo);
console.log("Capital:", estado.capital.toFixed(2));
console.log("Tasa decimal:", estado.tasa);
console.log("Horizonte:", estado.periodos, estado.unidad);Este estado ya contiene números interpretados. Los textos crudos permanecen en la capa de entrada.
28.5 Leer y validar los campos
La lectura devuelve un resultado explícito. El cálculo solo continúa cuando ok es verdadero:
function leerPositivo(texto, nombre) {
if (typeof texto !== "string" || texto.trim() === "") {
return { ok: false, error: `${nombre}: valor requerido` };
}
const valor = Number(texto);
if (!Number.isFinite(valor) || valor <= 0) {
return { ok: false, error: `${nombre}: debe ser positivo` };
}
return { ok: true, valor };
}
console.log(JSON.stringify(leerPositivo("350000", "Capital")));
console.log(JSON.stringify(leerPositivo("", "Capital")));
console.log(JSON.stringify(leerPositivo("-10", "Capital")));La función distingue ausencia, conversión y dominio. La calculadora completa aplica reglas específicas a cada campo.
28.6 Funciones del dominio
Ambos modelos comparten la misma firma y devuelven un monto:
function montoSimple(capital, tasa, periodos) {
return capital * (1 + tasa * periodos);
}
function montoCompuesto(capital, tasa, periodos) {
return capital * (1 + tasa) ** periodos;
}
const modelos = { simple: montoSimple, compuesto: montoCompuesto };
for (const nombre of Object.keys(modelos)) {
const monto = modelos[nombre](350000, 0.025, 12);
console.log(`${nombre}: ${monto.toFixed(2)}`);
}La interfaz selecciona una función del catálogo; no necesita conocer la expresión matemática interna.
28.7 Generar la trayectoria
Una única función puede construir filas para ambos modelos al cambiar la regla de la base:
function crearTrayectoria(modelo, capital, tasa, periodos) {
const filas = [{ periodo: 0, base: null, interes: 0, monto: capital }];
let monto = capital;
for (let periodo = 1; periodo <= periodos; periodo++) {
const base = modelo === "simple" ? capital : monto;
const interes = base * tasa;
monto = modelo === "simple"
? capital + interes * periodo
: base + interes;
filas.push({ periodo, base, interes, monto });
}
return filas;
}
const filas = crearTrayectoria("compuesto", 100000, 0.03, 3);
filas.forEach(fila => console.log(JSON.stringify(fila)));28.8 Derivar el resumen
No recalculamos lo que ya contiene la trayectoria. La última fila proporciona el monto final:
function resumir(capital, trayectoria) {
const monto = trayectoria.at(-1).monto;
return {
capital,
monto,
interes: monto - capital,
factor: monto / capital
};
}
const trayectoria = [
{ periodo: 0, monto: 100000 },
{ periodo: 1, monto: 103000 },
{ periodo: 2, monto: 106090 }
];
const resumen = resumir(100000, trayectoria);
console.log(JSON.stringify(resumen));Utilizar una única fuente de verdad evita que el resumen y la tabla se contradigan.
28.9 Renderizar sin mezclar cálculos
La función de presentación recibe resultados ya calculados. Formatea moneda, crea filas y actualiza textos; no vuelve a aplicar fórmulas financieras.
La separación evita usar cadenas monetarias como datos de una operación posterior.
28.10 Coordinar los eventos
Cada cambio de entrada ejecuta el mismo flujo de actualización. El botón de reinicio restaura valores y vuelve a calcular:
const estadoInicial = {
modelo: "compuesto",
capital: 350000,
tasaPorcentaje: 2.5,
periodos: 12
};
function actualizar(datos) {
const tasa = datos.tasaPorcentaje / 100;
const monto = datos.modelo === "simple"
? datos.capital * (1 + tasa * datos.periodos)
: datos.capital * (1 + tasa) ** datos.periodos;
return monto;
}
console.log("Monto inicial:", actualizar(estadoInicial).toFixed(2));
console.log("Monto modificado:", actualizar({ ...estadoInicial, periodos: 6 }).toFixed(2));En la página, los eventos llaman a una función coordinadora; no contienen cálculos diferentes entre sí.
28.11 Estados de error y accesibilidad
- Los campos poseen etiquetas asociadas mediante
foreid. aria-invalididentifica entradas que no cumplen su regla.- Los errores aparecen como texto y no dependen solo del color.
- La región de resultados utiliza
aria-live="polite". - Cuando hay errores, la tabla se vacía y el cálculo figura como no disponible.
- El botón de reinicio funciona con teclado y conserva un foco visible.
Un estado inválido no debe dejar en pantalla números anteriores como si todavía correspondieran a las entradas actuales.
28.12 Pruebas de aceptación
Antes de considerar terminada la calculadora comprobamos resultados y comportamiento:
function monto(capital, tasa, periodos, modelo) {
return modelo === "simple"
? capital * (1 + tasa * periodos)
: capital * (1 + tasa) ** periodos;
}
const pruebas = [
[monto(100000, 0.10, 0, "simple") === 100000, "simple n=0"],
[monto(100000, 0.10, 0, "compuesto") === 100000, "compuesto n=0"],
[Math.abs(monto(100000, 0.10, 5, "simple") - 150000) < 0.005, "simple n=5"],
[Math.abs(monto(100000, 0.10, 5, "compuesto") - 161051) < 0.005, "compuesto n=5"]
];
pruebas.forEach(([ok, nombre]) => console.log(`${nombre}: ${ok ? "correcta" : "falló"}`));Además se prueban campos vacíos, tasas fuera de rango, períodos decimales, reinicio y visualización en pantallas angostas.
28.13 Calculadora integrada
Usá la aplicación completa. La tabla corresponde al modelo seleccionado; el panel comparativo mantiene visibles los dos resultados bajo los mismos datos.
Crecimiento de un capital
Modelos simple y compuesto con trayectoria verificable.
Composición del monto seleccionado
| Período | Base | Interés del período | Monto |
|---|
Resultado válido: 13 filas, incluido el estado inicial.
28.14 Desafío de ampliación
La calculadora satisface el alcance del curso. Como práctica, podés ampliarla sin modificar las funciones financieras existentes:
- Agregar un botón que copie la tabla como CSV.
- Permitir nombres personalizados para los escenarios.
- Guardar el último escenario en el navegador.
- Incorporar pruebas para los límites mínimo y máximo.
- Mostrar una advertencia cuando la tasa y la unidad temporal no estén documentadas.
Ver criterios de aceptación
- La exportación contiene encabezados y
n + 1filas. - Los números exportados no incluyen símbolos monetarios.
- Un escenario guardado vuelve a validarse antes de calcular.
- Los nuevos controles tienen etiquetas y foco visible.
- Una ampliación no duplica las fórmulas de monto.
28.15 Cierre del recorrido
- Tradujimos problemas financieros a variables, funciones y estructuras.
- Representamos el valor del dinero mediante puntos, intervalos y flujos.
- Implementamos interés simple y compuesto sin confundir sus bases.
- Comparamos fórmulas cerradas con recorridos iterativos.
- Separamos cálculo, validación, formato e interfaz.
- Construimos tablas para explicar y comprobar resultados.
- Integramos todo en una calculadora reutilizable y accesible.
La herramienta final no reemplaza las condiciones de una operación real, pero establece una base sólida para continuar con tasas equivalentes, descuentos, rentas, sistemas de amortización y evaluación de proyectos.