Proyecto: Teclado e historial
Clase 6 de 67 min de lectura
Tu calculadora ya calcula, pero solo a golpe de clic. En esta última clase le añades los dos detalles que separan un proyecto básico de uno bien acabado:
- Soporte de teclado, para usarla sin tocar el ratón.
- Un panel de historial, para ver las operaciones anteriores y reutilizar sus resultados.
Soporte de teclado#
Quien tiene un teclado delante espera poder escribir 5+3 y pulsar Enter. Para conseguirlo, escucha el evento keydown en document, así da igual dónde esté el foco:
document.addEventListener('keydown', (evento) => { const tecla = evento.key // Números y punto decimal if ('0123456789.'.includes(tecla)) { evento.preventDefault() agregarNumero(tecla) } // Operadores else if (tecla === '+') { evento.preventDefault() seleccionarOperador('+') } else if (tecla === '-') { evento.preventDefault() seleccionarOperador('-') } else if (tecla === '*') { evento.preventDefault() seleccionarOperador('×') } else if (tecla === '/') { evento.preventDefault() seleccionarOperador('÷') } // Igual (Enter o =) else if (tecla === 'Enter' || tecla === '=') { evento.preventDefault() calcular() } // Limpiar (Escape) else if (tecla === 'Escape') { limpiar() } // Borrar (Backspace) else if (tecla === 'Backspace') { evento.preventDefault() if (numeroActual.length > 1) { numeroActual = numeroActual.slice(0, -1) } else { numeroActual = '0' } actualizarDisplay() }})Parece mucho código, pero es una lista de «si la tecla es esta, haz aquello». Lo que importa:
evento.keydevuelve la tecla pulsada como texto:'5','+','Enter'…evento.preventDefault()evita lo que el navegador haría por su cuenta. Por ejemplo, que/abra la búsqueda rápida.- No hay lógica nueva. Se reutilizan
agregarNumero,seleccionarOperadorycalcular, las mismas funciones que usan los botones. - El teclado escribe
*y/, pero la calculadora trabaja con×y÷. La traducción se hace aquí.
Pruébalo: abre la calculadora, escribe 5+3 y pulsa Enter. Debe funcionar igual que con los botones.
Añadir el panel de historial al HTML#
El historial necesita su sitio en la página. Envuelve la calculadora en un contenedor y añade el panel al lado:
<div class="calculadora-container"> <div class="calculadora"> <!-- ... lo que ya teníamos ... --> </div> <div class="historial"> <h3>Historial</h3> <div id="lista-historial"></div> <button id="limpiar-historial">Limpiar Historial</button> </div></div>Y estos estilos, al final de calculadora.css:
.calculadora-container { display: flex; gap: 30px; align-items: flex-start;}.historial { background: white; border-radius: 15px; padding: 20px; width: 250px; max-height: 400px; overflow-y: auto;}.historial h3 { margin-bottom: 15px; color: #2c3e50;}.operacion-historial { padding: 8px; margin: 5px 0; background: #f8f9fa; border-radius: 5px; font-family: monospace; cursor: pointer; transition: background 0.2s;}.operacion-historial:hover { background: #e9ecef;}#limpiar-historial { width: 100%; padding: 10px; background: #e74c3c; color: white; border: none; border-radius: 5px; cursor: pointer; margin-top: 10px;}Implementar el historial con createElement#
Empieza por lo de siempre: una referencia al contenedor y una variable para el estado. Esta vez, un array con las operaciones.
const listaHistorial = document.getElementById('lista-historial')let historial = []Ahora hacen falta dos funciones. Una guarda cada operación en el array y la otra pinta el array en la página, creando un elemento por cada operación:
function agregarAlHistorial(operacion) { historial.unshift(operacion) // Añadir al principio // Limitar a las últimas 10 operaciones if (historial.length > 10) { historial = historial.slice(0, 10) } renderizarHistorial()}function renderizarHistorial() { // Limpiar el contenedor listaHistorial.innerHTML = '' historial.forEach(operacion => { const div = document.createElement('div') div.className = 'operacion-historial' div.textContent = operacion // Al hacer click, usar el resultado de esa operación div.addEventListener('click', () => { const resultado = operacion.split(' = ')[1] if (resultado) { numeroActual = resultado numeroAnterior = '' operador = null debeReset = true actualizarDisplay() } }) listaHistorial.appendChild(div) })}renderizarHistorial es la interesante. Esto es lo que hace:
- Vacía el contenedor con
innerHTML = ''. - Crea un
<div>nuevo por cada operación concreateElement. - Le pone la clase y el texto con
classNameytextContent. - Le añade un listener de clic para reutilizar ese resultado en la calculadora.
- Lo mete en el DOM con
appendChild.
¿Borrarlo todo y volver a pintarlo en cada cambio? Sí. Con diez elementos como máximo es instantáneo, y el código queda mucho más sencillo que ir retocando lo que ya había.
Falta avisar al historial cuando haya un resultado. Añade esta línea dentro de calcular(), después del redondeo y justo antes de actualizar el estado:
// Dentro de calcular(), después de obtener el resultado:agregarAlHistorial(`${anterior} ${operador} ${actual} = ${resultado}`)Persistir el historial con localStorage#
Si recargas la página, el historial desaparece: vivía en una variable, y las variables no sobreviven a una recarga. localStorage sí, porque guarda los datos en el navegador.
function guardarHistorial() { localStorage.setItem('calculadora-historial', JSON.stringify(historial))}function cargarHistorial() { const guardado = localStorage.getItem('calculadora-historial') if (guardado) { historial = JSON.parse(guardado) renderizarHistorial() }}Solo hay una pega: localStorage únicamente guarda strings. Por eso el array pasa por dos conversiones:
JSON.stringifyconvierte el array en un string JSON antes de guardarlo.JSON.parsehace el camino contrario al leerlo: de string a array.
Ahora llama a guardarHistorial() cada vez que el historial cambie (al agregar y al limpiar) y a cargarHistorial() al arrancar:
// Al final de agregarAlHistorial:guardarHistorial()// Botón de limpiar historialdocument.getElementById('limpiar-historial').addEventListener('click', () => { historial = [] renderizarHistorial() guardarHistorial()})// Cargar historial al iniciocargarHistorial()renderizarHistorial crea un <div> por operación con createElement y el panel vuelve a mostrarse.Prueba todo junto#
- Haz varias operaciones:
5 + 3,10 × 2,100 ÷ 4. - Mira el panel de historial: deben aparecer las tres, la más reciente arriba.
- Haz clic en una operación del historial: su resultado se carga en el display.
- Cierra el navegador y vuelve a abrirlo: el historial sigue ahí.
- Prueba el teclado: escribe
7*8y pulsa Enter.
Lo que hemos aprendido en este proyecto#
En tres clases has usado, en un proyecto real, casi todo lo que hemos visto en este módulo:
- Selección de elementos:
getElementById,querySelectorAll. - Data attributes:
dataset.numero,dataset.operador. - Eventos de clic:
addEventListener('click', ...)en botones sueltos y en grupos. - Eventos de teclado:
addEventListener('keydown', ...),evento.key,preventDefault(). - Manipulación del DOM:
textContent,createElement,appendChild,innerHTML. - localStorage:
setItem,getItem,JSON.stringify,JSON.parse. - Manejo de errores:
try/catch,throw new Error().
En resumen
- Un listener de
keydownendocumentpermite manejar la calculadora entera con el teclado. - Teclado y botones comparten las mismas funciones: la lógica está escrita una sola vez.
- El historial es un array que se pinta con
createElementyappendChildcada vez que cambia. localStoragesolo guarda strings:JSON.stringifyal guardar yJSON.parseal leer.