Saltar al contenido principal
11DOM y Eventos

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.key devuelve 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, seleccionarOperador y calcular, 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:

HTML
<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:

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:

  1. Vacía el contenedor con innerHTML = ''.
  2. Crea un <div> nuevo por cada operación con createElement.
  3. Le pone la clase y el texto con className y textContent.
  4. Le añade un listener de clic para reutilizar ese resultado en la calculadora.
  5. 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.stringify convierte el array en un string JSON antes de guardarlo.
  • JSON.parse hace 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()
Infografía
Cómo sobrevive el historial a cerrar la pestaña
6.renderizarHistorial crea un <div> por operación con createElement y el panel vuelve a mostrarse.
Paso 6 de 6

Prueba todo junto#

  1. Haz varias operaciones: 5 + 3, 10 × 2, 100 ÷ 4.
  2. Mira el panel de historial: deben aparecer las tres, la más reciente arriba.
  3. Haz clic en una operación del historial: su resultado se carga en el display.
  4. Cierra el navegador y vuelve a abrirlo: el historial sigue ahí.
  5. Prueba el teclado: escribe 7*8 y 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 keydown en document permite 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 createElement y appendChild cada vez que cambia.
  • localStorage solo guarda strings: JSON.stringify al guardar y JSON.parse al leer.