
Aprende a crear un planificador de presupuesto (Budget Planner) interactivo usando HTML, CSS y JavaScript, con gráficos, alertas y un diseño moderno con Bootstrap 5. Ideal para controlar tus gastos mensuales y visualizar tus finanzas.
Tecnologías necesarias:
Paso 1: Crear la estructura HTML (index.html)
Creamos el archivo index.html que contendrá la estructura de nuestra aplicación, incluyendo la navbar, secciones de presupuesto, gastos, historial y análisis. También enlazamos el CSS y JavaScript externos.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 |
<!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Budget Planner - Planificador de Presupuesto</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet"> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <link href="style.css" rel="stylesheet"> <style> </style> </head> <body> <div class="floating-shapes"> <div class="shape"></div> <div class="shape"></div> <div class="shape"></div> </div> <!-- Navbar --> <nav class="navbar navbar-expand-lg navbar-glass fixed-top"> <div class="container"> <a class="navbar-brand text-white fw-bold" href="#"> <i class="fas fa-wallet me-2"></i>Budget Planner </a> <button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"> <i class="fas fa-bars text-white"></i> </button> <div class="collapse navbar-collapse" id="navbarNav"> <ul class="navbar-nav ms-auto"> <li class="nav-item"> <a class="nav-link text-white" href="#dashboard">Dashboard</a> </li> <li class="nav-item"> <a class="nav-link text-white" href="#expenses">Gastos</a> </li> <li class="nav-item"> <a class="nav-link text-white" href="#analytics">Análisis</a> </li> </ul> </div> </div> </nav> <div class="container mt-5 pt-5" style="position: relative; z-index: 2;"> <!-- Budget Setup Section --> <section id="dashboard" class="mb-5"> <div class="row"> <div class="col-12 text-center mb-4"> <h1 class="display-4 fw-bold text-white mb-3"> <span class="text-gradient">Budget</span> Planner </h1> <p class="lead text-white-50">Controla tus finanzas de manera inteligente</p> </div> </div> <div class="row g-4"> <!-- Budget Setup Card --> <div class="col-lg-6"> <div class="glass-card p-4"> <h3 class="text-white mb-4"> <i class="fas fa-cog me-2"></i>Configurar Presupuesto </h3> <form onsubmit="setBudget(event)"> <div class="mb-3"> <label class="form-label text-white-50">Presupuesto Mensual</label> <div class="input-group"> <span class="input-group-text bg-transparent border-light text-white">$</span> <input type="number" id="budgetInput" class="form-control form-control-modern" placeholder="5000" required> </div> </div> <button type="submit" class="btn btn-modern w-100"> <i class="fas fa-save me-2"></i>Establecer Presupuesto </button> </form> </div> </div> <!-- Budget Overview Card --> <div class="col-lg-6"> <div class="glass-card p-4 text-center"> <h3 class="text-white mb-3"> <i class="fas fa-chart-line me-2"></i>Resumen </h3> <div class="budget-display" id="budgetDisplay">$0</div> <p class="text-white-50 mb-3">Presupuesto Total</p> <div class="row text-center"> <div class="col-6"> <h4 class="text-success" id="remainingAmount">$0</h4> <small class="text-white-50">Disponible</small> </div> <div class="col-6"> <h4 class="text-danger" id="spentAmount">$0</h4> <small class="text-white-50">Gastado</small> </div> </div> </div> </div> </div> <!-- Progress Bar --> <div class="row mt-4"> <div class="col-12"> <div class="glass-card p-4"> <div class="d-flex justify-content-between align-items-center mb-3"> <h5 class="text-white mb-0">Progreso del Presupuesto</h5> <span class="text-white-50" id="progressPercentage">0%</span> </div> <div class="progress-modern"> <div class="progress-bar-modern" id="progressBar" style="width: 0%"></div> </div> </div> </div> </div> <!-- Alert Area --> <div id="alertArea" class="mt-3"></div> </section> <!-- Add Expense Section --> <section id="expenses" class="mb-5"> <div class="row"> <div class="col-lg-8"> <div class="glass-card p-4"> <h3 class="text-white mb-4"> <i class="fas fa-plus-circle me-2"></i>Agregar Gasto </h3> <form onsubmit="addExpense(event)"> <div class="row g-3"> <div class="col-md-6"> <label class="form-label text-white-50">Descripción</label> <input type="text" id="expenseDescription" class="form-control form-control-modern" placeholder="Ej: Supermercado" required> </div> <div class="col-md-3"> <label class="form-label text-white-50">Monto</label> <div class="input-group"> <span class="input-group-text bg-transparent border-light text-white">$</span> <input type="number" id="expenseAmount" class="form-control form-control-modern" placeholder="100" step="0.01" required> </div> </div> <div class="col-md-3"> <label class="form-label text-white-50">Categoría</label> <select id="expenseCategory" class="form-control form-control-modern" required> <option value="">Seleccionar</option> <option value="comida"> Comida</option> <option value="transporte"> Transporte</option> <option value="entretenimiento"> Entretenimiento</option> <option value="servicios"> Servicios</option> <option value="compras"> Compras</option> <option value="salud">⚕️ Salud</option> <option value="otros"> Otros</option> </select> </div> <div class="col-12"> <button type="submit" class="btn btn-modern"> <i class="fas fa-plus me-2"></i>Agregar Gasto </button> </div> </div> </form> </div> </div> <!-- Chart Section --> <div class="col-lg-4"> <div class="glass-card p-4 text-center"> <h5 class="text-white mb-4">Distribución del Presupuesto</h5> <div class="chart-container"> <canvas id="budgetChart"></canvas> </div> </div> </div> </div> </section> <!-- Expenses List --> <section class="mb-5"> <div class="glass-card p-4"> <div class="d-flex justify-content-between align-items-center mb-4"> <h3 class="text-white mb-0"> <i class="fas fa-list me-2"></i>Historial de Gastos </h3> <button class="btn btn-secondary-modern" onclick="clearAllExpenses()"> <i class="fas fa-trash me-2"></i>Limpiar Todo </button> </div> <div id="expensesList"> <div class="text-center text-white-50 py-4"> <i class="fas fa-receipt fa-3x mb-3"></i> <p>No hay gastos registrados aún</p> </div> </div> </div> </section> <!-- Analytics Section --> <section id="analytics" class="mb-5"> <div class="row g-4"> <div class="col-md-3"> <div class="glass-card p-4 text-center"> <i class="fas fa-shopping-cart fa-2x text-gradient mb-3"></i> <h4 class="text-white" id="totalExpenses">0</h4> <p class="text-white-50 mb-0">Total Gastos</p> </div> </div> <div class="col-md-3"> <div class="glass-card p-4 text-center"> <i class="fas fa-calendar-day fa-2x text-gradient mb-3"></i> <h4 class="text-white" id="dailyAverage">$0</h4> <p class="text-white-50 mb-0">Promedio Diario</p> </div> </div> <div class="col-md-3"> <div class="glass-card p-4 text-center"> <i class="fas fa-tags fa-2x text-gradient mb-3"></i> <h4 class="text-white" id="topCategory">-</h4> <p class="text-white-50 mb-0">Categoría Top</p> </div> </div> <div class="col-md-3"> <div class="glass-card p-4 text-center"> <i class="fas fa-calendar-alt fa-2x text-gradient mb-3"></i> <h4 class="text-white" id="daysLeft">30</h4> <p class="text-white-50 mb-0">Días Restantes</p> </div> </div> </div> </section> </div> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="script.js"></script> </body> </html> |
En esta primera parte creamos la estructura principal de la app. Usamos Bootstrap 5 para el diseño, FontAwesome para iconos y Chart.js para los gráficos de distribución de presupuesto. La página incluye un navbar, secciones para el dashboard, gastos, historial y análisis.
Paso 2: Estilos modernos con CSS (style.css)
Creamos style.css para darle un estilo moderno y glassmorphism, con colores degradados, botones estilizados y animaciones de formas flotantes.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
body { box-sizing: border-box; font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; } .glass-card { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 20px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); } .floating-shapes { position: fixed; width: 100%; height: 100%; overflow: hidden; z-index: 1; pointer-events: none; } .shape { position: absolute; background: rgba(255, 255, 255, 0.1); border-radius: 50%; animation: float 6s ease-in-out infinite; } .shape:nth-child(1) { width: 80px; height: 80px; top: 20%; left: 10%; animation-delay: 0s; } .shape:nth-child(2) { width: 120px; height: 120px; top: 60%; right: 15%; animation-delay: 2s; } .shape:nth-child(3) { width: 60px; height: 60px; bottom: 20%; left: 20%; animation-delay: 4s; } @keyframes float { 0%, 100% { transform: translateY(0px) rotate(0deg); } 50% { transform: translateY(-20px) rotate(180deg); } } .btn-modern { background: linear-gradient(45deg, #ff6b6b, #feca57); border: none; border-radius: 50px; padding: 12px 30px; color: white; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; transition: all 0.3s ease; box-shadow: 0 4px 15px rgba(255, 107, 107, 0.3); } .btn-modern:hover { transform: translateY(-2px); box-shadow: 0 8px 25px rgba(255, 107, 107, 0.4); color: white; } .btn-secondary-modern { background: transparent; border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 50px; padding: 12px 30px; color: white; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; transition: all 0.3s ease; } .btn-secondary-modern:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.5); transform: translateY(-2px); color: white; } .navbar-glass { background: rgba(255, 255, 255, 0.1); backdrop-filter: blur(10px); border-bottom: 1px solid rgba(255, 255, 255, 0.1); } .text-gradient { background: linear-gradient(45deg, #ff6b6b, #feca57); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .budget-display { font-size: 3rem; font-weight: 700; background: linear-gradient(45deg, #ff6b6b, #feca57); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; } .expense-item { background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 15px; padding: 15px; margin-bottom: 10px; transition: all 0.3s ease; } .expense-item:hover { background: rgba(255, 255, 255, 0.1); transform: translateX(5px); } .progress-modern { height: 20px; border-radius: 10px; background: rgba(255, 255, 255, 0.1); overflow: hidden; position: relative; } .progress-bar-modern { background: linear-gradient(45deg, #ff6b6b, #feca57); border-radius: 10px; transition: width 0.5s ease; height: 100%; display: block; } .alert-modern { background: rgba(255, 107, 107, 0.2); border: 1px solid rgba(255, 107, 107, 0.3); border-radius: 15px; color: white; } .chart-container { position: relative; height: 300px; width: 300px; margin: 0 auto; } .form-control-modern { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 15px; color: white; padding: 12px 20px; } .form-control-modern::placeholder { color: rgba(255, 255, 255, 0.6); } .form-control-modern:focus { background: rgba(255, 255, 255, 0.15); border-color: #feca57; box-shadow: 0 0 0 0.2rem rgba(254, 202, 87, 0.25); color: grey; } .category-badge { background: linear-gradient(45deg, #667eea, #764ba2); color: white; padding: 5px 15px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; } @media (max-width: 768px) { .budget-display { font-size: 2rem; } .chart-container { height: 250px; width: 250px; } } |
Este CSS le da un estilo moderno tipo glassmorphism con degradados y sombras suaves. Los botones y formularios tienen animaciones sutiles, mientras que las formas flotantes le dan dinamismo a la interfaz.
Paso 3: Funcionalidad con JavaScript (script.js)
Creamos script.js para manejar el presupuesto, gastos, historial, gráficos y alertas dinámicas.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
let budget = 0; let expenses = []; let chart = null; // Inicializar el gráfico function initChart() { const ctx = document.getElementById('budgetChart').getContext('2d'); chart = new Chart(ctx, { type: 'doughnut', data: { labels: ['Gastado', 'Disponible'], datasets: [{ data: [0, 100], backgroundColor: [ 'rgba(255, 107, 107, 0.8)', 'rgba(102, 126, 234, 0.8)' ], borderColor: [ 'rgba(255, 107, 107, 1)', 'rgba(102, 126, 234, 1)' ], borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { labels: { color: 'white' } } } } }); } // Establecer presupuesto function setBudget(event) { event.preventDefault(); const budgetInput = document.getElementById('budgetInput'); budget = parseFloat(budgetInput.value); document.getElementById('budgetDisplay').textContent = `$${budget.toLocaleString()}`; updateBudgetDisplay(); showAlert('¡Presupuesto establecido correctamente!', 'success'); } // Agregar gasto function addExpense(event) { event.preventDefault(); const description = document.getElementById('expenseDescription').value; const amount = parseFloat(document.getElementById('expenseAmount').value); const category = document.getElementById('expenseCategory').value; const expense = { id: Date.now(), description, amount, category, date: new Date().toLocaleDateString() }; expenses.push(expense); updateBudgetDisplay(); renderExpenses(); updateAnalytics(); // Limpiar el formulario event.target.reset(); // Comprobar si se ha excedido el presupuesto const totalSpent = expenses.reduce((sum, exp) => sum + exp.amount, 0); if (totalSpent > budget && budget > 0) { showAlert('¡Atención! Has superado tu presupuesto mensual', 'danger'); } showAlert('Gasto agregado correctamente', 'success'); } // Actualizar la visualización del presupuesto function updateBudgetDisplay() { const totalSpent = expenses.reduce((sum, exp) => sum + exp.amount, 0); const remaining = budget - totalSpent; const percentage = budget > 0 ? (totalSpent / budget) * 100 : 0; document.getElementById('spentAmount').textContent = `$${totalSpent.toLocaleString()}`; document.getElementById('remainingAmount').textContent = `$${remaining.toLocaleString()}`; document.getElementById('progressPercentage').textContent = `${Math.round(percentage)}%`; const progressBar = document.getElementById('progressBar'); progressBar.style.width = `${Math.min(percentage, 100)}%`; // Cambiar de color si se supera el presupuesto if (percentage > 100) { progressBar.style.background = 'linear-gradient(45deg, #ff4757, #ff3838)'; } else if (percentage > 80) { progressBar.style.background = 'linear-gradient(45deg, #ffa502, #ff6348)'; } else { progressBar.style.background = 'linear-gradient(45deg, #ff6b6b, #feca57)'; } // Actualizar el gráfico if (chart && budget > 0) { chart.data.datasets[0].data = [totalSpent, Math.max(0, remaining)]; chart.update(); } } // Renderizas la lista de gastos function renderExpenses() { const expensesList = document.getElementById('expensesList'); if (expenses.length === 0) { expensesList.innerHTML = ` <div class="text-center text-white-50 py-4"> <i class="fas fa-receipt fa-3x mb-3"></i> <p>No hay gastos registrados aún</p> </div> `; return; } expensesList.innerHTML = expenses.map(expense => ` <div class="expense-item"> <div class="d-flex justify-content-between align-items-center"> <div> <h6 class="text-white mb-1">${expense.description}</h6> <small class="text-white-50">${expense.date}</small> <span class="category-badge ms-2">${expense.category}</span> </div> <div class="text-end"> <h5 class="text-danger mb-0">-$${expense.amount.toLocaleString()}</h5> <button class="btn btn-sm btn-outline-danger mt-1" onclick="deleteExpense(${expense.id})"> <i class="fas fa-trash"></i> </button> </div> </div> </div> `).join(''); } // Eliminar gasto function deleteExpense(id) { if (confirm("Estas seguro que deseas eliminar este gasto?")){ expenses = expenses.filter(exp => exp.id !== id); updateBudgetDisplay(); renderExpenses(); updateAnalytics(); showAlert('Gasto eliminado', 'info'); } } // Eliminar todos los gastos function clearAllExpenses() { if (confirm('¿Estás seguro de que quieres eliminar todos los gastos?')) { expenses = []; updateBudgetDisplay(); renderExpenses(); updateAnalytics(); showAlert('Todos los gastos han sido eliminados', 'info'); } } // Actualizar analíticas function updateAnalytics() { const totalExpenses = expenses.length; const totalSpent = expenses.reduce((sum, exp) => sum + exp.amount, 0); const dailyAverage = totalExpenses > 0 ? totalSpent / 30 : 0; // Encontrar la categoría principal const categoryTotals = {}; expenses.forEach(exp => { categoryTotals[exp.category] = (categoryTotals[exp.category] || 0) + exp.amount; }); const topCategory = Object.keys(categoryTotals).reduce((a, b) => categoryTotals[a] > categoryTotals[b] ? a : b, '-' ); document.getElementById('totalExpenses').textContent = totalExpenses; document.getElementById('dailyAverage').textContent = `$${dailyAverage.toFixed(2)}`; document.getElementById('topCategory').textContent = topCategory; // Calcular los días que quedan en el mes const now = new Date(); const daysInMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0).getDate(); const daysLeft = daysInMonth - now.getDate(); document.getElementById('daysLeft').textContent = daysLeft; } // Muestra alertas function showAlert(message, type = 'info') { const alertArea = document.getElementById('alertArea'); const alertClass = type === 'danger' ? 'alert-modern' : 'alert-info'; const icon = type === 'danger' ? 'fa-exclamation-triangle' : type === 'success' ? 'fa-check-circle' : 'fa-info-circle'; const alert = document.createElement('div'); alert.className = `alert ${alertClass} alert-dismissible fade show`; alert.innerHTML = ` <i class="fas ${icon} me-2"></i> ${message} <button type="button" class="btn-close btn-close-white" data-bs-dismiss="alert"></button> `; alertArea.appendChild(alert); // Eliminar automáticamente después de 5 segundos setTimeout(() => { if (alert.parentNode) { alert.remove(); } }, 5000); } // Desplazamiento suave para la navegación document.querySelectorAll('a[href^="#"]').forEach(anchor => { anchor.addEventListener('click', function (e) { e.preventDefault(); const target = document.querySelector(this.getAttribute('href')); if (target) { target.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // Inicializar la aplicación document.addEventListener('DOMContentLoaded', function() { initChart(); updateAnalytics(); }); // Fondo de la barra de navegación al desplazarse window.addEventListener('scroll', () => { const navbar = document.querySelector('.navbar'); if (window.scrollY > 50) { navbar.style.background = 'rgba(255, 255, 255, 0.15)'; } else { navbar.style.background = 'rgba(255, 255, 255, 0.1)'; } }); |
Con este script manejamos todas las funcionalidades: establecer presupuesto, agregar/eliminar gastos, actualizar gráficos de Chart.js, mostrar alertas y calcular métricas como gasto diario, categoría top y días restantes.
Apuntes del tutorial
Con estos tres archivos (index.html, style.css, script.js) tendrás un Budget Planner completo con:
- Configuración de presupuesto mensual
- Registro de gastos con categoría y fecha
- Visualización de progreso y gráficos
- Historial de gastos
- Alertas y análisis diario
- El diseño es moderno, responsive y atractivo, listo para integrarse
VER DEMOSTRACIÓN DESCARGAR ARCHIVOS