.progress-container {
max-width: 1000px;
margin: 0 auto;
padding: 32px 24px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: #f8fafc;
min-height: 100vh;
}
.progress-header {
text-align: center;
margin-bottom: 40px;
}
.progress-title {
font-size: 36px;
font-weight: 700;
color: #1a202c;
margin: 0 0 12px 0;
background: linear-gradient(135deg, #667eea, #764ba2);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.progress-subtitle {
font-size: 16px;
color: #6b7280;
margin: 0;
}
.progress-content {
display: flex;
flex-direction: column;
gap: 32px;
}
/* Progreso General */
.overall-progress {
background: white;
border-radius: 16px;
padding: 32px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.progress-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.progress-info h2 {
font-size: 24px;
font-weight: 600;
color: #1a202c;
margin: 0;
}
.progress-percentage {
font-size: 32px;
font-weight: 700;
color: #667eea;
}
.progress-bar-container {
margin-bottom: 24px;
}
.progress-bar {
width: 100%;
height: 12px;
background: #e5e7eb;
border-radius: 6px;
overflow: hidden;
position: relative;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 6px;
transition: width 0.8s ease;
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.3), transparent);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% { transform: translateX(-100%); }
100% { transform: translateX(100%); }
}
.progress-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.stat-item {
text-align: center;
padding: 16px;
background: #f8fafc;
border-radius: 12px;
}
.stat-number {
display: block;
font-size: 28px;
font-weight: 700;
color: #1a202c;
margin-bottom: 4px;
}
.stat-label {
font-size: 14px;
color: #6b7280;
font-weight: 500;
}
/* Progreso por Pasos */
.step-progress {
background: white;
border-radius: 16px;
padding: 32px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.step-progress h2 {
font-size: 24px;
font-weight: 600;
color: #1a202c;
margin: 0 0 32px 0;
}
.steps-container {
display: flex;
flex-direction: column;
gap: 24px;
}
.step {
display: flex;
align-items: flex-start;
gap: 20px;
padding: 24px;
border-radius: 12px;
transition: all 0.3s ease;
position: relative;
}
.step::before {
content: '';
position: absolute;
left: 31px;
top: 80px;
width: 2px;
height: calc(100% + 24px);
background: #e5e7eb;
z-index: 1;
}
.step:last-child::before {
display: none;
}
.step.completed {
background: #f0fdf4;
border: 1px solid #bbf7d0;
}
.step.completed::before {
background: #22c55e;
}
.step.active {
background: #eff6ff;
border: 1px solid #bfdbfe;
}
.step.active::before {
background: linear-gradient(to bottom, #3b82f6, #e5e7eb);
}
.step.pending {
background: #f9fafb;
border: 1px solid #e5e7eb;
}
.step-indicator {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
position: relative;
z-index: 2;
font-weight: 600;
font-size: 18px;
}
.step.completed .step-indicator {
background: #22c55e;
color: white;
}
.step.active .step-indicator {
background: #3b82f6;
color: white;
}
.step.pending .step-indicator {
background: #e5e7eb;
color: #6b7280;
}
.step-icon {
width: 24px;
height: 24px;
}
.step-content {
flex: 1;
}
.step-title {
font-size: 18px;
font-weight: 600;
color: #1a202c;
margin: 0 0 8px 0;
}
.step-description {
font-size: 14px;
color: #6b7280;
margin: 0 0 16px 0;
line-height: 1.5;
}
.step-progress-bar {
width: 100%;
height: 6px;
background: #e5e7eb;
border-radius: 3px;
overflow: hidden;
margin-bottom: 12px;
}
.step-progress-fill {
height: 100%;
background: linear-gradient(90deg, #667eea, #764ba2);
border-radius: 3px;
transition: width 0.6s ease;
}
.step-status {
font-size: 12px;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
display: inline-block;
}
.step.completed .step-status {
background: #dcfce7;
color: #166534;
}
.step.active .step-status {
background: #dbeafe;
color: #1e40af;
}
.step.pending .step-status {
background: #f3f4f6;
color: #6b7280;
}
/* Lista de Tareas */
.task-list {
background: white;
border-radius: 16px;
padding: 32px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.task-header h2 {
font-size: 24px;
font-weight: 600;
color: #1a202c;
margin: 0;
}
.add-task-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: #667eea;
color: white;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.add-task-btn:hover {
background: #5a67d8;
transform: translateY(-1px);
}
.add-task-btn svg {
width: 16px;
height: 16px;
}
.tasks-container {
display: flex;
flex-direction: column;
gap: 16px;
}
.task-item {
display: flex;
align-items: flex-start;
gap: 16px;
padding: 20px;
border-radius: 12px;
transition: all 0.3s ease;
cursor: pointer;
}
.task-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.task-item.completed {
background: #f0fdf4;
border: 1px solid #bbf7d0;
opacity: 0.8;
}
.task-item.active {
background: #eff6ff;
border: 1px solid #bfdbfe;
}
.task-item.pending {
background: #f9fafb;
border: 1px solid #e5e7eb;
}
.task-checkbox {
position: relative;
width: 20px;
height: 20px;
margin-top: 2px;
}
.task-checkbox input {
opacity: 0;
position: absolute;
width: 100%;
height: 100%;
cursor: pointer;
}
.checkmark {
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 20px;
background: white;
border: 2px solid #d1d5db;
border-radius: 4px;
transition: all 0.3s ease;
}
.task-checkbox input:checked + .checkmark {
background: #22c55e;
border-color: #22c55e;
}
.task-checkbox input:checked + .checkmark::after {
content: '✓';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
font-size: 12px;
font-weight: bold;
}
.task-content {
flex: 1;
}
.task-title {
font-size: 16px;
font-weight: 600;
color: #1a202c;
margin: 0 0 8px 0;
}
.task-item.completed .task-title {
text-decoration: line-through;
color: #6b7280;
}
.task-description {
font-size: 14px;
color: #6b7280;
margin: 0 0 12px 0;
line-height: 1.5;
}
.task-meta {
display: flex;
align-items: center;
gap: 12px;
}
.task-priority {
font-size: 12px;
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
text-transform: uppercase;
}
.task-priority.high {
background: #fef2f2;
color: #dc2626;
}
.task-priority.medium {
background: #fef3c7;
color: #d97706;
}
.task-priority.low {
background: #f0fdf4;
color: #16a34a;
}
.task-date {
font-size: 12px;
color: #9ca3af;
}
/* Acciones de Progreso */
.progress-actions {
display: flex;
justify-content: center;
gap: 16px;
margin-top: 16px;
}
.action-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s ease;
}
.action-btn svg {
width: 18px;
height: 18px;
}
.action-btn.primary {
background: #667eea;
color: white;
}
.action-btn.primary:hover {
background: #5a67d8;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.action-btn.secondary {
background: #f3f4f6;
color: #6b7280;
border: 1px solid #d1d5db;
}
.action-btn.secondary:hover {
background: #e5e7eb;
transform: translateY(-2px);
}
/* Diseño Responsivo */
@media (max-width: 768px) {
.progress-container {
padding: 24px 16px;
}
.progress-title {
font-size: 28px;
}
.overall-progress,
.step-progress,
.task-list {
padding: 24px 16px;
}
.progress-stats {
grid-template-columns: 1fr;
gap: 16px;
}
.step {
flex-direction: column;
align-items: center;
text-align: center;
gap: 16px;
}
.step::before {
display: none;
}
.task-header {
flex-direction: column;
gap: 16px;
align-items: stretch;
}
.progress-actions {
flex-direction: column;
}
}
@media (max-width: 480px) {
.progress-info {
flex-direction: column;
gap: 16px;
text-align: center;
}
.task-item {
padding: 16px;
}
.task-meta {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
}
document.addEventListener('DOMContentLoaded', () => {
const overallProgressBar = document.getElementById('overallProgressBar');
const overallPercentage = document.getElementById('overallPercentage');
const completedTasks = document.getElementById('completedTasks');
const totalTasks = document.getElementById('totalTasks');
const remainingDays = document.getElementById('remainingDays');
const tasksContainer = document.getElementById('tasksContainer');
const addTaskBtn = document.getElementById('addTaskBtn');
const resetBtn = document.getElementById('resetBtn');
const updateBtn = document.getElementById('updateBtn');
let tasks = [
{
id: 1,
title: 'Configurar entorno de desarrollo',
description: 'Configurar herramientas y dependencias',
priority: 'high',
completed: true,
date: 'Completado hace 2 días'
},
{
id: 2,
title: 'Crear esquema de base de datos',
description: 'Diseñar e implementar estructura de datos',
priority: 'medium',
completed: true,
date: 'Completado hace 1 día'
},
{
id: 3,
title: 'Implementar autenticación de usuario',
description: 'Construir sistema de login y registro',
priority: 'high',
completed: false,
date: 'Vence en 2 días'
},
{
id: 4,
title: 'Diseñar interfaz de usuario',
description: 'Crear componentes de UI responsivos',
priority: 'medium',
completed: false,
date: 'Vence en 5 días'
}
];
let steps = [
{ id: 1, title: 'Planificación e Investigación', progress: 100, status: 'completed' },
{ id: 2, title: 'Diseño y Prototipado', progress: 100, status: 'completed' },
{ id: 3, title: 'Desarrollo', progress: 75, status: 'active' },
{ id: 4, title: 'Pruebas y Control de Calidad', progress: 0, status: 'pending' },
{ id: 5, title: 'Despliegue', progress: 0, status: 'pending' }
];
// Calcular progreso general
function calculateOverallProgress() {
const completedCount = tasks.filter(task => task.completed).length;
const totalCount = tasks.length;
const percentage = Math.round((completedCount / totalCount) * 100);
return {
percentage,
completed: completedCount,
total: totalCount,
remaining: Math.max(0, 10 - Math.floor(percentage / 10)) // Días simulados
};
}
// Actualizar visualización del progreso
function updateProgressDisplay() {
const progress = calculateOverallProgress();
// Animar porcentaje
animateNumber(overallPercentage, parseInt(overallPercentage.textContent), progress.percentage, '%');
// Animar barra de progreso
const progressFill = overallProgressBar.querySelector('.progress-fill');
progressFill.style.width = progress.percentage + '%';
// Actualizar estadísticas
animateNumber(completedTasks, parseInt(completedTasks.textContent), progress.completed);
animateNumber(totalTasks, parseInt(totalTasks.textContent), progress.total);
animateNumber(remainingDays, parseInt(remainingDays.textContent), progress.remaining);
}
// Animar cambios de números
function animateNumber(element, start, end, suffix = '') {
const duration = 1000;
const startTime = performance.now();
function update(currentTime) {
const elapsed = currentTime - startTime;
const progress = Math.min(elapsed / duration, 1);
const current = Math.round(start + (end - start) * easeOutCubic(progress));
element.textContent = current + suffix;
if (progress < 1) {
requestAnimationFrame(update);
}
}
requestAnimationFrame(update);
}
function easeOutCubic(t) {
return 1 - Math.pow(1 - t, 3);
}
// Renderizar tareas
function renderTasks() {
tasksContainer.innerHTML = '';
tasks.forEach(task => {
const taskElement = document.createElement('div');
taskElement.className = `task-item ${task.completed ? 'completed' : 'pending'}`;
taskElement.innerHTML = `
<div class="task-checkbox">
<input type="checkbox" ${task.completed ? 'checked' : ''} data-task-id="${task.id}">
<span class="checkmark"></span>
</div>
<div class="task-content">
<h4 class="task-title">${task.title}</h4>
<p class="task-description">${task.description}</p>
<div class="task-meta">
<span class="task-priority ${task.priority}">${task.priority === 'high' ? 'Alta' : task.priority === 'medium' ? 'Media' : 'Baja'}</span>
<span class="task-date">${task.date}</span>
</div>
</div>
`;
tasksContainer.appendChild(taskElement);
});
// Agregar event listeners a checkboxes
const checkboxes = tasksContainer.querySelectorAll('input[type="checkbox"]');
checkboxes.forEach(checkbox => {
checkbox.addEventListener('change', handleTaskToggle);
});
}
// Manejar cambio de estado de tarea
function handleTaskToggle(e) {
const taskId = parseInt(e.target.dataset.taskId);
const task = tasks.find(t => t.id === taskId);
if (task) {
task.completed = e.target.checked;
// Actualizar fecha de tarea
if (task.completed) {
task.date = 'Completado ahora mismo';
} else {
task.date = 'Vence en 3 días'; // Reiniciar a pendiente
}
// Re-renderizar tareas y actualizar progreso
renderTasks();
updateProgressDisplay();
// Agregar animación de completado
if (task.completed) {
showCompletionAnimation(e.target.closest('.task-item'));
}
}
}
// Mostrar animación de completado
function showCompletionAnimation(taskElement) {
taskElement.style.transform = 'scale(1.02)';
taskElement.style.transition = 'transform 0.3s ease';
setTimeout(() => {
taskElement.style.transform = 'scale(1)';
}, 300);
}
// Agregar nueva tarea
function addNewTask() {
const taskTitles = [
'Escribir pruebas unitarias',
'Optimizar consultas de base de datos',
'Implementar manejo de errores',
'Crear documentación de API',
'Configurar monitoreo',
'Configurar pipeline CI/CD',
'Realizar auditoría de seguridad',
'Actualizar dependencias'
];
const taskDescriptions = [
'Asegurar calidad y confiabilidad del código',
'Mejorar rendimiento de la aplicación',
'Manejar casos extremos con elegancia',
'Documentar endpoints y uso de API',
'Configurar logging y alertas',
'Automatizar proceso de despliegue',
'Verificar vulnerabilidades de seguridad',
'Mantener paquetes actualizados'
];
const priorities = ['high', 'medium', 'low'];
const randomIndex = Math.floor(Math.random() * taskTitles.length);
const newTask = {
id: Date.now(),
title: taskTitles[randomIndex],
description: taskDescriptions[randomIndex],
priority: priorities[Math.floor(Math.random() * priorities.length)],
completed: false,
date: `Vence en ${Math.floor(Math.random() * 7) + 1} días`
};
tasks.push(newTask);
renderTasks();
updateProgressDisplay();
// Desplazarse a nueva tarea
setTimeout(() => {
const newTaskElement = tasksContainer.lastElementChild;
newTaskElement.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
newTaskElement.style.transform = 'scale(1.05)';
setTimeout(() => {
newTaskElement.style.transform = 'scale(1)';
}, 300);
}, 100);
}
// Reiniciar progreso
function resetProgress() {
if (confirm('¿Estás seguro de que quieres reiniciar todo el progreso? Esta acción no se puede deshacer.')) {
tasks.forEach(task => {
task.completed = false;
task.date = `Vence en ${Math.floor(Math.random() * 7) + 1} días`;
});
steps.forEach(step => {
if (step.id > 2) {
step.progress = 0;
step.status = 'pending';
}
});
renderTasks();
updateProgressDisplay();
updateStepProgress();
showNotification('El progreso ha sido reiniciado', 'info');
}
}
// Actualizar progreso de pasos
function updateStepProgress() {
const stepElements = document.querySelectorAll('.step');
stepElements.forEach((element, index) => {
const step = steps[index];
const progressFill = element.querySelector('.step-progress-fill');
const statusElement = element.querySelector('.step-status');
progressFill.style.width = step.progress + '%';
if (step.status === 'completed') {
statusElement.textContent = 'Completado';
} else if (step.status === 'active') {
statusElement.textContent = `En Progreso (${step.progress}%)`;
} else {
statusElement.textContent = 'Pendiente';
}
});
}
// Simular actualización de progreso
function simulateProgressUpdate() {
const activeStep = steps.find(step => step.status === 'active');
if (activeStep && activeStep.progress < 100) {
activeStep.progress = Math.min(100, activeStep.progress + Math.floor(Math.random() * 20) + 5);
if (activeStep.progress >= 100) {
activeStep.status = 'completed';
// Mover al siguiente paso
const nextStepIndex = steps.findIndex(step => step.id === activeStep.id) + 1;
if (nextStepIndex < steps.length) {
steps[nextStepIndex].status = 'active';
steps[nextStepIndex].progress = Math.floor(Math.random() * 30) + 10;
}
}
updateStepProgress();
updateProgressDisplay();
showNotification('¡Progreso actualizado exitosamente!', 'success');
} else {
showNotification('No hay tareas activas para actualizar', 'info');
}
}
// Mostrar notificación
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 12px 20px;
border-radius: 8px;
color: white;
font-weight: 500;
z-index: 1000;
transform: translateX(100%);
transition: transform 0.3s ease;
`;
if (type === 'success') {
notification.style.background = '#22c55e';
} else if (type === 'info') {
notification.style.background = '#3b82f6';
} else if (type === 'warning') {
notification.style.background = '#f59e0b';
}
document.body.appendChild(notification);
// Animar entrada
setTimeout(() => {
notification.style.transform = 'translateX(0)';
}, 100);
// Animar salida y remover
setTimeout(() => {
notification.style.transform = 'translateX(100%)';
setTimeout(() => {
document.body.removeChild(notification);
}, 300);
}, 3000);
}
// Event listeners
addTaskBtn.addEventListener('click', addNewTask);
resetBtn.addEventListener('click', resetProgress);
updateBtn.addEventListener('click', simulateProgressUpdate);
// Inicializar
renderTasks();
updateProgressDisplay();
updateStepProgress();
// Simulación de auto-actualización (opcional)
setInterval(() => {
if (Math.random() < 0.1) { // 10% de probabilidad cada 5 segundos
simulateProgressUpdate();
}
}, 5000);
});