¡Es hora de poner en práctica todo lo que hemos aprendido! Vamos a crear una calculadora completamente funcional que combine selección de elementos, manipulación del DOM y manejo de eventos. Este proyecto te dará una base sólida para crear aplicaciones web interactivas.
Una calculadora que tenga:
Primero, creemos la estructura HTML:
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Calculadora Interactiva</title>
<link rel="stylesheet" href="calculadora.css">
</head>
<body>
<div class="calculadora-container">
<div class="calculadora">
<!-- Display -->
<div class="display">
<div class="operacion-anterior" id="operacion-anterior"></div>
<div class="operacion-actual" id="operacion-actual">0</div>
</div>
<!-- Botones -->
<div class="botones">
<!-- Fila 1: Funciones -->
<button class="btn btn-funcion" id="clear">C</button>
<button class="btn btn-funcion" id="delete">⌫</button>
<button class="btn btn-operador" data-operador="÷">÷</button>
<button class="btn btn-operador" data-operador="×">×</button>
<!-- Fila 2: Números -->
<button class="btn btn-numero" data-numero="7">7</button>
<button class="btn btn-numero" data-numero="8">8</button>
<button class="btn btn-numero" data-numero="9">9</button>
<button class="btn btn-operador" data-operador="-">-</button>
<!-- Fila 3: Números -->
<button class="btn btn-numero" data-numero="4">4</button>
<button class="btn btn-numero" data-numero="5">5</button>
<button class="btn btn-numero" data-numero="6">6</button>
<button class="btn btn-operador" data-operador="+">+</button>
<!-- Fila 4: Números -->
<button class="btn btn-numero" data-numero="1">1</button>
<button class="btn btn-numero" data-numero="2">2</button>
<button class="btn btn-numero" data-numero="3">3</button>
<button class="btn btn-igual" id="igual" rowspan="2">=</button>
<!-- Fila 5: Cero y decimal -->
<button class="btn btn-numero btn-cero" data-numero="0">0</button>
<button class="btn btn-numero" data-numero=".">.</button>
</div>
</div>
<!-- Historial -->
<div class="historial">
<h3>Historial</h3>
<div id="lista-historial"></div>
<button id="limpiar-historial">Limpiar Historial</button>
</div>
</div>
<script src="calculadora.js"></script>
</body>
</html>
/* calculadora.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.calculadora-container {
display: flex;
gap: 30px;
align-items: flex-start;
}
.calculadora {
background: #2c3e50;
border-radius: 20px;
padding: 20px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
max-width: 300px;
}
.display {
background: #34495e;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
text-align: right;
min-height: 80px;
}
.operacion-anterior {
color: #bdc3c7;
font-size: 14px;
min-height: 20px;
}
.operacion-actual {
color: white;
font-size: 28px;
font-weight: bold;
min-height: 40px;
}
.botones {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
}
.btn {
border: none;
border-radius: 10px;
font-size: 18px;
font-weight: bold;
padding: 20px;
cursor: pointer;
transition: all 0.2s;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.btn:active {
transform: translateY(0);
}
.btn-numero {
background: #ecf0f1;
color: #2c3e50;
}
.btn-numero:hover {
background: #d5dbdb;
}
.btn-operador {
background: #f39c12;
color: white;
}
.btn-operador:hover {
background: #e67e22;
}
.btn-funcion {
background: #e74c3c;
color: white;
}
.btn-funcion:hover {
background: #c0392b;
}
.btn-igual {
background: #27ae60;
color: white;
grid-row: span 2;
}
.btn-igual:hover {
background: #229954;
}
.btn-cero {
grid-column: span 2;
}
.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;
}
// calculadora.js
class Calculadora {
constructor() {
// Elementos del DOM
this.operacionAnteriorElement = document.getElementById('operacion-anterior')
this.operacionActualElement = document.getElementById('operacion-actual')
this.listaHistorial = document.getElementById('lista-historial')
// Estado de la calculadora
this.operacionActual = '0'
this.operacionAnterior = ''
this.operador = null
this.debeReset = false
this.historial = []
this.inicializar()
}
inicializar() {
this.configurarEventListeners()
this.cargarHistorialDesdeStorage()
this.actualizarDisplay()
}
configurarEventListeners() {
// Botones numéricos
const botonesNumero = document.querySelectorAll('.btn-numero')
botonesNumero.forEach(boton => {
boton.addEventListener('click', () => {
const numero = boton.dataset.numero
this.agregarNumero(numero)
})
})
// Botones de operadores
const botonesOperador = document.querySelectorAll('.btn-operador')
botonesOperador.forEach(boton => {
boton.addEventListener('click', () => {
const operador = boton.dataset.operador
this.seleccionarOperador(operador)
})
})
// Botón igual
document.getElementById('igual').addEventListener('click', () => {
this.calcular()
})
// Botón clear
document.getElementById('clear').addEventListener('click', () => {
this.limpiar()
})
// Botón delete
document.getElementById('delete').addEventListener('click', () => {
this.borrar()
})
// Limpiar historial
document.getElementById('limpiar-historial').addEventListener('click', () => {
this.limpiarHistorial()
})
// Eventos de teclado
document.addEventListener('keydown', (evento) => {
this.manejarTeclado(evento)
})
}
agregarNumero(numero) {
// Si debe resetear, limpiar el display
if (this.debeReset) {
this.operacionActual = '0'
this.debeReset = false
}
// Evitar múltiples puntos decimales
if (numero === '.' && this.operacionActual.includes('.')) {
return
}
// Reemplazar 0 inicial o concatenar
if (this.operacionActual === '0' && numero !== '.') {
this.operacionActual = numero
} else {
this.operacionActual += numero
}
this.actualizarDisplay()
}
seleccionarOperador(operador) {
// Si ya hay una operación pendiente, calcularla primero
if (this.operador && !this.debeReset) {
this.calcular()
}
this.operador = operador
this.operacionAnterior = this.operacionActual + ' ' + operador
this.debeReset = true
this.actualizarDisplay()
}
calcular() {
if (!this.operador || this.debeReset) return
const anterior = parseFloat(this.operacionAnterior)
const actual = parseFloat(this.operacionActual)
if (isNaN(anterior) || isNaN(actual)) return
let resultado
const operacionCompleta = `${anterior} ${this.operador} ${actual}`
try {
switch (this.operador) {
case '+':
resultado = anterior + actual
break
case '-':
resultado = anterior - actual
break
case '×':
resultado = anterior * actual
break
case '÷':
if (actual === 0) {
throw new Error('No se puede dividir por cero')
}
resultado = anterior / actual
break
default:
return
}
// Redondear resultado para evitar problemas de punto flotante
resultado = Math.round((resultado + Number.EPSILON) * 100000000) / 100000000
// Agregar al historial
this.agregarAlHistorial(`${operacionCompleta} = ${resultado}`)
// Actualizar estado
this.operacionActual = resultado.toString()
this.operacionAnterior = ''
this.operador = null
this.debeReset = true
} catch (error) {
this.mostrarError(error.message)
return
}
this.actualizarDisplay()
}
limpiar() {
this.operacionActual = '0'
this.operacionAnterior = ''
this.operador = null
this.debeReset = false
this.actualizarDisplay()
}
borrar() {
if (this.operacionActual.length > 1) {
this.operacionActual = this.operacionActual.slice(0, -1)
} else {
this.operacionActual = '0'
}
this.actualizarDisplay()
}
actualizarDisplay() {
this.operacionActualElement.textContent = this.operacionActual
this.operacionAnteriorElement.textContent = this.operacionAnterior
}
mostrarError(mensaje) {
this.operacionActualElement.textContent = 'Error'
this.operacionAnteriorElement.textContent = mensaje
// Limpiar después de 2 segundos
setTimeout(() => {
this.limpiar()
}, 2000)
}
agregarAlHistorial(operacion) {
this.historial.unshift(operacion) // Añadir al principio
// Limitar historial a 10 operaciones
if (this.historial.length > 10) {
this.historial = this.historial.slice(0, 10)
}
this.actualizarHistorialDisplay()
this.guardarHistorialEnStorage()
}
actualizarHistorialDisplay() {
this.listaHistorial.innerHTML = ''
this.historial.forEach((operacion, index) => {
const div = document.createElement('div')
div.className = 'operacion-historial'
div.textContent = operacion
// Permitir usar operaciones del historial
div.addEventListener('click', () => {
const resultado = operacion.split(' = ')[1]
if (resultado) {
this.operacionActual = resultado
this.operacionAnterior = ''
this.operador = null
this.debeReset = true
this.actualizarDisplay()
}
})
this.listaHistorial.appendChild(div)
})
}
limpiarHistorial() {
this.historial = []
this.actualizarHistorialDisplay()
this.guardarHistorialEnStorage()
}
guardarHistorialEnStorage() {
localStorage.setItem('calculadora-historial', JSON.stringify(this.historial))
}
cargarHistorialDesdeStorage() {
const historialGuardado = localStorage.getItem('calculadora-historial')
if (historialGuardado) {
this.historial = JSON.parse(historialGuardado)
this.actualizarHistorialDisplay()
}
}
manejarTeclado(evento) {
const tecla = evento.key
// Prevenir comportamiento por defecto para ciertas teclas
if ('0123456789+-*/=.'.includes(tecla) ||
tecla === 'Enter' ||
tecla === 'Escape' ||
tecla === 'Backspace') {
evento.preventDefault()
}
// Números y punto decimal
if ('0123456789.'.includes(tecla)) {
this.agregarNumero(tecla)
}
// Operadores
else if (tecla === '+') {
this.seleccionarOperador('+')
}
else if (tecla === '-') {
this.seleccionarOperador('-')
}
else if (tecla === '*') {
this.seleccionarOperador('×')
}
else if (tecla === '/') {
this.seleccionarOperador('÷')
}
// Igual (Enter o =)
else if (tecla === 'Enter' || tecla === '=') {
this.calcular()
}
// Limpiar (Escape)
else if (tecla === 'Escape') {
this.limpiar()
}
// Borrar (Backspace)
else if (tecla === 'Backspace') {
this.borrar()
}
}
}
// Inicializar la calculadora cuando se carga la página
document.addEventListener('DOMContentLoaded', () => {
new Calculadora()
})
// Funciones científicas
agregarFuncionCientifica() {
// Añadir botones para sin, cos, tan, log, etc.
}
// Memoria de calculadora
guardarEnMemoria() {
this.memoria = parseFloat(this.operacionActual)
}
recuperarDeMemoria() {
this.operacionActual = this.memoria.toString()
this.actualizarDisplay()
}
// Soporte para expresiones complejas
evaluarExpresion(expresion) {
// Implementar parser para expresiones como "2 + 3 * 4"
}
// Modo programador
convertirBase(numero, baseOrigen, baseDestino) {
// Conversiones entre binario, octal, decimal, hexadecimal
}
// Gráficos
mostrarGrafico(funcion) {
// Integrar con Canvas para mostrar gráficos de funciones
}
Con este proyecto has puesto en práctica:
querySelector, querySelectorAll, getElementByIdtextContent, innerHTML, createElement, appendChildaddEventListener, manejo de teclado, delegación de eventosclassList.add/remove/toggletry/catch, validaciones¡Felicidades! Has creado una aplicación web completamente funcional. Ahora estás listo para:
Inicia sesión para guardar tu progreso