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.
<!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.
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.
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
Comentarios