OR_Training / running-dashboard.html
rogarces85's picture
Upload 13 files
9f87fac verified
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Running Training Dashboard - Osorno Runners</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
</head>
<body>
<style>
*{margin:0;padding:0;box-sizing:border-box}:root{--primary-color:#dc143c;--primary-dark:#8b0000;--secondary-color:#00a65a;--danger-color:#dd4b39;--warning-color:#f39c12;--info-color:#00c0ef;--dark-color:#222d32;--sidebar-bg:#222d32;--sidebar-hover:#1a2226;--content-bg:#ecf0f5;--white:#fff;--text-dark:#333;--text-light:#666;--border-color:#ddd}body{font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;background-color:var(--content-bg);color:var(--text-dark)}.login-container{display:flex;justify-content:center;align-items:center;min-height:100vh;background:linear-gradient(135deg,var(--primary-color) 0%,var(--primary-dark) 100%)}.login-box{background:var(--white);padding:40px;border-radius:10px;box-shadow:0 10px 40px rgba(0,0,0,.3);width:100%;max-width:400px}.login-header{text-align:center;margin-bottom:30px}.login-header i{font-size:60px;color:var(--primary-color);margin-bottom:15px}.login-header h2{color:var(--text-dark);margin-bottom:10px}.login-header p{color:var(--text-light);font-size:14px}.header{background-color:var(--primary-color);color:var(--white);padding:15px 20px;display:flex;justify-content:space-between;align-items:center;box-shadow:0 2px 4px rgba(0,0,0,.1);position:fixed;top:0;left:0;right:0;z-index:1000}.header-left{display:flex;align-items:center;gap:15px}.menu-toggle{background:0 0;border:none;color:var(--white);font-size:24px;cursor:pointer;padding:5px 10px}.logo{font-size:24px;font-weight:700;display:flex;align-items:center;gap:10px}.header-right{display:flex;align-items:center;gap:20px}.user-info{display:flex;align-items:center;gap:10px}.user-badge{padding:5px 12px;background-color:rgba(255,255,255,.2);border-radius:15px;font-size:12px;font-weight:600}.user-avatar{width:35px;height:35px;border-radius:50%;background-color:var(--white);display:flex;align-items:center;justify-content:center;color:var(--primary-color);font-weight:700}.btn-logout{background-color:var(--danger-color);color:var(--white);border:none;padding:8px 15px;border-radius:5px;cursor:pointer;font-size:14px;transition:background-color .3s}.btn-logout:hover{background-color:#c23321}.sidebar{position:fixed;top:60px;left:0;width:250px;height:calc(100vh - 60px);background-color:var(--sidebar-bg);color:var(--white);overflow-y:auto;transition:transform .3s ease;z-index:999}.sidebar.hidden{transform:translateX(-100%)}.sidebar-menu{list-style:none;padding:20px 0}.sidebar-menu li{margin:5px 0}.sidebar-menu a{display:flex;align-items:center;gap:15px;padding:12px 20px;color:var(--white);text-decoration:none;transition:background-color .3s;cursor:pointer}.sidebar-menu a.active,.sidebar-menu a:hover{background-color:var(--sidebar-hover);border-left:3px solid var(--primary-color)}.sidebar-menu i{width:20px;text-align:center}.main-content{margin-left:250px;margin-top:60px;padding:30px;transition:margin-left .3s ease}.main-content.expanded{margin-left:0}.stats-container{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px;margin-bottom:30px}.stat-card{background:var(--white);border-radius:8px;padding:20px;box-shadow:0 2px 10px rgba(0,0,0,.1);display:flex;justify-content:space-between;align-items:center;transition:transform .3s,box-shadow .3s}.stat-card:hover{transform:translateY(-5px);box-shadow:0 5px 20px rgba(0,0,0,.15)}.stat-info h3{font-size:14px;color:var(--text-light);margin-bottom:10px;text-transform:uppercase}.stat-info p{font-size:28px;font-weight:700;color:var(--text-dark)}.stat-icon{width:70px;height:70px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:30px;color:var(--white)}.stat-card.primary .stat-icon{background-color:var(--primary-color)}.stat-card.success .stat-icon{background-color:var(--secondary-color)}.stat-card.warning .stat-icon{background-color:var(--warning-color)}.stat-card.info .stat-icon{background-color:var(--info-color)}.box{background:var(--white);border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1);margin-bottom:30px;overflow:hidden}.box-header{background-color:#f7f7f7;padding:15px 20px;border-bottom:1px solid var(--border-color);display:flex;justify-content:space-between;align-items:center}.box-title{font-size:18px;font-weight:700;color:var(--text-dark);display:flex;align-items:center;gap:10px}.box-body{padding:20px}.form-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:20px;margin-bottom:20px}.form-group{display:flex;flex-direction:column}.form-group label{font-weight:600;margin-bottom:8px;color:var(--text-dark);font-size:14px}.form-group input,.form-group select,.form-group textarea{padding:10px 15px;border:1px solid var(--border-color);border-radius:5px;font-size:14px;transition:border-color .3s}.form-group input:focus,.form-group select:focus,.form-group textarea:focus{outline:0;border-color:var(--primary-color);box-shadow:0 0 0 3px rgba(220,20,60,.1)}.form-group textarea{resize:vertical;min-height:80px}.form-group small{margin-top:5px;font-size:12px;color:var(--text-light)}.info-box{background:#e8f4f8;border-left:4px solid var(--info-color);padding:12px 15px;margin-top:8px;border-radius:4px;font-size:13px;color:#0c5460}.checkbox-group{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:10px}.checkbox-item{display:flex;align-items:center;padding:8px;background:#f8f9fa;border-radius:5px;cursor:pointer;transition:all .3s}.checkbox-item:hover{background:#ffe5e5}.checkbox-item input{margin-right:8px;width:auto!important}.section-divider{margin:30px 0 20px;padding-bottom:12px;border-bottom:3px solid var(--primary-color);color:var(--primary-color);font-size:20px;font-weight:700;display:flex;align-items:center;gap:10px}.prep-race-list{margin-top:15px}.prep-race-item{background:#f8f9fa;padding:15px;border-radius:8px;margin-bottom:15px;border-left:4px solid var(--primary-color)}.prep-race-item .form-grid{margin-bottom:10px}.btn{padding:12px 30px;border:none;border-radius:5px;font-size:16px;font-weight:600;cursor:pointer;transition:all .3s;display:inline-flex;align-items:center;gap:10px;text-decoration:none}.btn-primary{background-color:var(--primary-color);color:var(--white)}.btn-primary:hover{background-color:var(--primary-dark);transform:translateY(-2px);box-shadow:0 5px 15px rgba(220,20,60,.3)}.btn-success{background-color:var(--secondary-color);color:var(--white)}.btn-success:hover{background-color:#008d4c}.btn-warning{background-color:var(--warning-color);color:var(--white)}.btn-warning:hover{background-color:#db8b0b}.btn-danger{background-color:var(--danger-color);color:var(--white)}.btn-danger:hover{background-color:#c23321}.btn-sm{padding:6px 15px;font-size:14px}.btn-secondary{background-color:#6c757d;color:var(--white)}.btn-secondary:hover{background-color:#5a6268}.plan-list{display:grid;gap:20px}.plan-card{background:var(--white);border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1);padding:20px;display:flex;justify-content:space-between;align-items:center;transition:transform .3s,box-shadow .3s}.plan-card:hover{transform:translateY(-3px);box-shadow:0 5px 20px rgba(0,0,0,.15)}.plan-info h3{font-size:20px;margin-bottom:10px;color:var(--text-dark)}.plan-info p{color:var(--text-light);font-size:14px;margin-bottom:5px}.plan-actions{display:flex;gap:10px;flex-wrap:wrap}.alert{padding:15px 20px;border-radius:5px;margin-bottom:20px;display:flex;align-items:center;gap:10px}.alert-info{background-color:#d1ecf1;color:#0c5460;border-left:4px solid var(--info-color)}.alert-success{background-color:#d4edda;color:#155724;border-left:4px solid var(--secondary-color)}.modal{display:none;position:fixed;z-index:2000;left:0;top:0;width:100%;height:100%;background-color:rgba(0,0,0,.5);overflow-y:auto}.modal.show{display:flex;justify-content:center;align-items:flex-start;padding:50px 20px}.modal-content{background-color:var(--white);border-radius:8px;width:100%;max-width:1200px;max-height:90vh;overflow-y:auto;box-shadow:0 10px 40px rgba(0,0,0,.3)}.modal-header{background-color:var(--primary-color);color:var(--white);padding:20px;display:flex;justify-content:space-between;align-items:center;position:sticky;top:0;z-index:10}.modal-header h2{margin:0;display:flex;align-items:center;gap:10px}.modal-close{background:0 0;border:none;color:var(--white);font-size:24px;cursor:pointer;padding:5px 10px}.modal-body{padding:30px}.period-section{margin-bottom:40px}.period-header{background:linear-gradient(135deg,var(--primary-color) 0%,var(--primary-dark) 100%);color:#fff;padding:15px 20px;border-radius:8px;font-size:20px;font-weight:700;margin-bottom:20px}.week-section{background:#f8f9fa;border-radius:8px;padding:20px;margin-bottom:20px;border-left:4px solid var(--primary-color)}.week-section.race-week{border-left:4px solid gold;background:linear-gradient(to right,#fff9e6 0%,#f8f9fa 100%)}.week-header{font-size:18px;font-weight:700;color:var(--primary-color);margin-bottom:15px;display:flex;justify-content:space-between;align-items:center}.session-card{background:#fff;padding:20px;border-radius:8px;margin-bottom:20px;box-shadow:0 2px 5px rgba(0,0,0,.05)}.session-card.race-day{border:3px solid gold;background:linear-gradient(to bottom,#fffef9 0%,#fff 100%)}.session-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:15px;padding-bottom:15px;border-bottom:2px solid #f0f0f0}.session-title{font-weight:700;color:var(--primary-color);font-size:18px}.session-distance{font-weight:700;color:var(--text-dark);font-size:16px}.motivational-message{background:linear-gradient(135deg,gold 0%,orange 100%);color:#1a1a1a;padding:20px;border-radius:8px;margin-bottom:20px;font-weight:600;font-size:16px;line-height:1.6;box-shadow:0 4px 15px rgba(255,215,0,.3);text-align:center}.session-details{margin-top:15px}.detail-section{margin-bottom:20px}.detail-section-title{font-weight:700;color:var(--primary-color);font-size:15px;margin-bottom:10px;display:flex;align-items:center;gap:8px}.detail-item{margin-bottom:12px;padding:12px;background:#f8f9fa;border-radius:5px;font-size:14px;line-height:1.6}.detail-label{font-weight:600;color:var(--primary-color);display:block;margin-bottom:5px}.purpose-box{background:linear-gradient(135deg,#e8f4f8 0%,#d1ecf1 100%);padding:15px;border-radius:8px;border-left:4px solid var(--info-color);margin-top:15px}.purpose-title{font-weight:700;color:var(--info-color);margin-bottom:8px;font-size:14px}.purpose-content{font-size:14px;color:#0c5460;line-height:1.6}.volume-summary{background:var(--dark-color);color:#fff;padding:12px 15px;border-radius:5px;text-align:center;font-weight:700;margin-top:15px;font-size:15px}.hr-zones{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-top:10px}.hr-zone{background:#f0f0f0;padding:8px 12px;border-radius:5px;text-align:center;font-size:13px}.hr-zone-label{font-weight:600;color:var(--text-dark);display:block;margin-bottom:3px}@media (max-width:768px){.sidebar{transform:translateX(-100%)}.sidebar.show{transform:translateX(0)}.main-content{margin-left:0}.header-right .user-info span{display:none}.form-grid{grid-template-columns:1fr}.stats-container{grid-template-columns:1fr}.plan-card{flex-direction:column;align-items:flex-start;gap:15px}.plan-actions{width:100%}.plan-actions button{flex:1}}.hidden{display:none!important}
</style>
<div id="loginScreen" class="login-container">
<div class="login-box">
<div class="login-header">
<i class="fas fa-running"></i>
<h2>Osorno Runners</h2>
<p>Sistema de Planes de Entrenamiento</p>
</div>
<form id="loginForm">
<div class="form-group">
<label for="loginUsername"><i class="fas fa-user"></i> Usuario</label>
<input type="text" id="loginUsername" required placeholder="USER o ADMIN">
</div>
<div class="form-group" style="margin-top:15px">
<label for="loginPassword"><i class="fas fa-lock"></i> Contraseña</label>
<input type="password" id="loginPassword" required placeholder="Contraseña">
</div>
<button type="submit" class="btn btn-primary" style="width:100%;margin-top:20px;justify-content:center">
<i class="fas fa-sign-in-alt"></i> Iniciar Sesión
</button>
</form>
<div style="margin-top:20px;text-align:center;color:var(--text-light);font-size:13px">
<p><strong>Usuario:</strong> USER / <strong>Contraseña:</strong> 123</p>
<p><strong>Administrador:</strong> ADMIN / <strong>Contraseña:</strong> 123</p>
</div>
</div>
</div>
<div id="appScreen" class="hidden">
<header class="header">
<div class="header-left">
<button class="menu-toggle" onclick="toggleSidebar()"><i class="fas fa-bars"></i></button>
<div class="logo"><i class="fas fa-running"></i><span>Osorno Runners</span></div>
</div>
<div class="header-right">
<div class="user-info">
<span class="user-badge" id="userRole">USUARIO</span>
<span id="currentUserName">Usuario</span>
<div class="user-avatar"><i class="fas fa-user"></i></div>
</div>
<button class="btn-logout" onclick="logout()"><i class="fas fa-sign-out-alt"></i> Salir</button>
</div>
</header>
<nav class="sidebar" id="sidebar">
<ul class="sidebar-menu">
<li><a href="#" class="active" onclick="showSection('dashboard')"><i class="fas fa-tachometer-alt"></i><span>Dashboard</span></a></li>
<li id="menu-new-plan"><a href="#" onclick="showSection('new-plan')"><i class="fas fa-plus-circle"></i><span>Nuevo Plan</span></a></li>
<li><a href="#" onclick="showSection('plan-list')"><i class="fas fa-list-ul"></i><span>Ver Planes</span></a></li>
</ul>
</nav>
<main class="main-content" id="mainContent">
<section id="dashboard-section">
<h1 style="margin-bottom:30px;color:var(--text-dark)"><i class="fas fa-tachometer-alt"></i> Dashboard</h1>
<div class="stats-container">
<div class="stat-card primary">
<div class="stat-info"><h3>Planes Creados</h3><p id="stat-plans">0</p></div>
<div class="stat-icon"><i class="fas fa-list-alt"></i></div>
</div>
<div class="stat-card success">
<div class="stat-info"><h3>Total Atletas</h3><p id="stat-users">0</p></div>
<div class="stat-icon"><i class="fas fa-users"></i></div>
</div>
<div class="stat-card warning">
<div class="stat-info"><h3>Semanas Totales</h3><p id="stat-weeks">0</p></div>
<div class="stat-icon"><i class="fas fa-calendar-week"></i></div>
</div>
<div class="stat-card info">
<div class="stat-info"><h3>Maratones</h3><p id="stat-marathons">0</p></div>
<div class="stat-icon"><i class="fas fa-medal"></i></div>
</div>
</div>
<div class="alert alert-info">
<i class="fas fa-info-circle"></i>
<span>Bienvenido al Sistema de Osorno Runners. <span id="dashboardMessage">Administra los planes de entrenamiento.</span></span>
</div>
<div class="box">
<div class="box-header"><h3 class="box-title"><i class="fas fa-rocket"></i> Inicio Rápido</h3></div>
<div class="box-body">
<p style="margin-bottom:20px;color:var(--text-light)"><span id="quickStartText">Crea planes personalizados de entrenamiento.</span></p>
<button class="btn btn-primary" id="quickStartBtn" onclick="showSection('new-plan')"><i class="fas fa-plus"></i> Crear Nuevo Plan</button>
</div>
</div>
</section>
<section id="new-plan-section" class="hidden">
<h1 style="margin-bottom:30px"><i class="fas fa-plus-circle"></i> Crear Nuevo Plan</h1>
<div class="box">
<div class="box-header"><h3 class="box-title"><i class="fas fa-user-circle"></i> Información del Atleta</h3></div>
<div class="box-body">
<form id="trainingForm">
<div class="form-grid">
<div class="form-group">
<label for="athleteName"><i class="fas fa-user"></i> Nombre Completo *</label>
<input type="text" id="athleteName" required placeholder="Ej: Rodrigo Garcés">
</div>
<div class="form-group">
<label for="athleteAge"><i class="fas fa-birthday-cake"></i> Edad *</label>
<input type="number" id="athleteAge" required placeholder="Ej: 40" min="10" max="100">
</div>
<div class="form-group">
<label for="experience"><i class="fas fa-layer-group"></i> Experiencia *</label>
<select id="experience" required>
<option value="">Selecciona...</option>
<option value="principiante">Principiante</option>
<option value="intermedio">Intermedio</option>
<option value="avanzado">Avanzado</option>
</select>
</div>
</div>
<div class="section-divider"><i class="fas fa-weight"></i> Antropometría</div>
<div class="form-grid">
<div class="form-group">
<label for="weight"><i class="fas fa-weight-hanging"></i> Peso (kg) *</label>
<input type="number" id="weight" required placeholder="Ej: 75" min="30" max="200" step="0.1" oninput="calculateIMC()">
</div>
<div class="form-group">
<label for="height"><i class="fas fa-ruler-vertical"></i> Talla (cm) *</label>
<input type="number" id="height" required placeholder="Ej: 175" min="100" max="250" oninput="calculateIMC()">
</div>
<div class="form-group">
<label><i class="fas fa-calculator"></i> IMC</label>
<input type="text" id="imc" readonly placeholder="Se calculará automáticamente" style="background:#e9ecef">
<small id="imcCategory"></small>
</div>
</div>
<div class="section-divider"><i class="fas fa-heartbeat"></i> Datos Fisiológicos</div>
<div class="form-grid">
<div class="form-group">
<label for="hrMax"><i class="fas fa-heart"></i> FC Máxima (bpm) *</label>
<input type="number" id="hrMax" required placeholder="Ej: 180" min="100" max="220">
<small>Sugerida: 220 - edad = <span id="suggestedHrMax">--</span> bpm</small>
</div>
<div class="form-group">
<label for="hrRest"><i class="fas fa-bed"></i> FC en Reposo (bpm) *</label>
<input type="number" id="hrRest" required placeholder="Ej: 60" min="30" max="100">
</div>
<div class="form-group">
<label for="vo2max"><i class="fas fa-lungs"></i> VO2Max</label>
<input type="number" id="vo2max" placeholder="Ej: 45" min="20" max="90" step="0.1">
<small>Opcional</small>
</div>
</div>
<div class="section-divider"><i class="fas fa-bullseye"></i> Objetivo de Carrera</div>
<div class="form-grid">
<div class="form-group">
<label for="distance"><i class="fas fa-flag-checkered"></i> Distancia *</label>
<select id="distance" required>
<option value="">Selecciona...</option>
<option value="5K">5K</option>
<option value="10K">10K</option>
<option value="21K">21K</option>
<option value="42K">42K</option>
</select>
</div>
<div class="form-group">
<label for="targetPace"><i class="fas fa-tachometer-alt"></i> Ritmo (min/km) *</label>
<input type="text" id="targetPace" required placeholder="Ej: 4:30" pattern="[0-9]:[0-5][0-9]">
</div>
<div class="form-group">
<label for="raceDate"><i class="fas fa-calendar-check"></i> Fecha Carrera *</label>
<input type="date" id="raceDate" required>
</div>
</div>
<div class="section-divider"><i class="fas fa-calendar-day"></i> Disponibilidad</div>
<div class="form-group">
<label>Días de Entrenamiento *</label>
<div class="checkbox-group">
<label class="checkbox-item"><input type="checkbox" name="trainingDays" value="lunes">Lunes</label>
<label class="checkbox-item"><input type="checkbox" name="trainingDays" value="martes">Martes</label>
<label class="checkbox-item"><input type="checkbox" name="trainingDays" value="miércoles">Miércoles</label>
<label class="checkbox-item"><input type="checkbox" name="trainingDays" value="jueves">Jueves</label>
<label class="checkbox-item"><input type="checkbox" name="trainingDays" value="viernes">Viernes</label>
<label class="checkbox-item"><input type="checkbox" name="trainingDays" value="sábado">Sábado</label>
<label class="checkbox-item"><input type="checkbox" name="trainingDays" value="domingo">Domingo</label>
</div>
</div>
<div class="section-divider"><i class="fas fa-dumbbell"></i> Cross Training</div>
<div class="form-group">
<label>Actividades Complementarias</label>
<div class="checkbox-group">
<label class="checkbox-item"><input type="checkbox" name="crossTraining" value="ciclismo">🚴 Ciclismo</label>
<label class="checkbox-item"><input type="checkbox" name="crossTraining" value="natacion">🏊 Natación</label>
<label class="checkbox-item"><input type="checkbox" name="crossTraining" value="gimnasio">💪 Gimnasio</label>
<label class="checkbox-item"><input type="checkbox" name="crossTraining" value="yoga">🧘 Yoga</label>
<label class="checkbox-item"><input type="checkbox" name="crossTraining" value="pilates">🤸 Pilates</label>
<label class="checkbox-item"><input type="checkbox" name="crossTraining" value="spinning">🚴 Spinning</label>
</div>
</div>
<div class="section-divider"><i class="fas fa-trophy"></i> Carreras Preparatorias</div>
<div id="prepRacesList" class="prep-race-list"></div>
<button type="button" class="btn btn-secondary btn-sm" onclick="addPrepRace()"><i class="fas fa-plus"></i> Agregar Carrera</button>
<div class="form-group" style="margin-top:30px">
<label for="notes"><i class="fas fa-sticky-note"></i> Notas</label>
<textarea id="notes" placeholder="Notas adicionales"></textarea>
</div>
<div style="margin-top:30px;display:flex;gap:15px;flex-wrap:wrap">
<button type="submit" class="btn btn-success"><i class="fas fa-check"></i> Generar Plan</button>
<button type="button" class="btn btn-primary" onclick="resetForm()"><i class="fas fa-redo"></i> Limpiar</button>
</div>
</form>
</div>
</div>
</section>
<section id="plan-list-section" class="hidden">
<h1 style="margin-bottom:30px"><i class="fas fa-list-ul"></i> Planes de Entrenamiento</h1>
<div id="planListContent" class="plan-list"></div>
<div id="noPlanPlaceholder" class="hidden">
<div class="alert alert-info"><i class="fas fa-info-circle"></i><span>No hay planes disponibles.</span></div>
</div>
</section>
</main>
</div>
<div id="planDetailModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h2><i class="fas fa-calendar-alt"></i> <span id="modalTitle">Detalle del Plan</span></h2>
<button class="modal-close" onclick="closeModal()"><i class="fas fa-times"></i></button>
</div>
<div class="modal-body" id="modalPlanContent"></div>
</div>
</div>
<script>
let currentUser=null,prepRaceCounter=0;const users={USER:{password:'123',role:'user',name:'Usuario'},ADMIN:{password:'123',role:'admin',name:'Administrador'}};document.getElementById('loginForm').addEventListener('submit',function(e){e.preventDefault();const username=document.getElementById('loginUsername').value.toUpperCase(),password=document.getElementById('loginPassword').value;if(users[username]&&users[username].password===password){currentUser={username:username,role:users[username].role,name:users[username].name};document.getElementById('loginScreen').classList.add('hidden');document.getElementById('appScreen').classList.remove('hidden');document.getElementById('currentUserName').textContent=currentUser.name;document.getElementById('userRole').textContent=currentUser.role==='admin'?'ADMINISTRADOR':'USUARIO';currentUser.role==='user'?(document.getElementById('menu-new-plan').style.display='none',document.getElementById('dashboardMessage').textContent='Consulta los planes de entrenamiento.',document.getElementById('quickStartText').textContent='Revisa los planes disponibles.',document.getElementById('quickStartBtn').innerHTML='<i class="fas fa-list"></i> Ver Planes',document.getElementById('quickStartBtn').onclick=function(){showSection('plan-list')}):(document.getElementById('menu-new-plan').style.display='block',document.getElementById('quickStartBtn').onclick=function(){showSection('new-plan')});updateDashboard();loadPlanList()}else alert('Usuario o contraseña incorrectos')});function logout(){currentUser=null;document.getElementById('appScreen').classList.add('hidden');document.getElementById('loginScreen').classList.remove('hidden');document.getElementById('loginForm').reset();showSection('dashboard')}function toggleSidebar(){const sidebar=document.getElementById('sidebar'),mainContent=document.getElementById('mainContent');window.innerWidth<=768?sidebar.classList.toggle('show'):(sidebar.classList.toggle('hidden'),mainContent.classList.toggle('expanded'))}function showSection(section){document.querySelectorAll('main > section').forEach(s=>s.classList.add('hidden'));document.querySelectorAll('.sidebar-menu a').forEach(a=>a.classList.remove('active'));'dashboard'===section?document.getElementById('dashboard-section').classList.remove('hidden'):'new-plan'===section?currentUser&&'admin'===currentUser.role?document.getElementById('new-plan-section').classList.remove('hidden'):(alert('Solo los administradores pueden crear planes'),void 0):'plan-list'===section&&(document.getElementById('plan-list-section').classList.remove('hidden'),loadPlanList());if(window.event&&window.event.target){const link=window.event.target.closest('a');link&&link.classList.add('active')}}function getPlans(){const plans=localStorage.getItem('osornoRunnerPlans');return plans?JSON.parse(plans):[]}function savePlan(plan){const plans=getPlans();plans.push(plan);localStorage.setItem('osornoRunnerPlans',JSON.stringify(plans))}function deletePlan(planId){const plans=getPlans(),filtered=plans.filter(p=>p.id!==planId);localStorage.setItem('osornoRunnerPlans',JSON.stringify(filtered))}document.getElementById('athleteAge').addEventListener('input',function(){const age=parseInt(this.value);if(age&&age>=10&&age<=100){const suggestedHR=220-age;document.getElementById('suggestedHrMax').textContent=suggestedHR}else document.getElementById('suggestedHrMax').textContent='--'});function calculateIMC(){const weight=parseFloat(document.getElementById('weight').value),height=parseFloat(document.getElementById('height').value);if(weight&&height&&height>0){const heightM=height/100,imc=weight/(heightM*heightM);document.getElementById('imc').value=imc.toFixed(1);let category='',color='';imc<18.5?(category='Bajo peso',color='#17a2b8'):imc<25?(category='Peso normal',color='#28a745'):imc<30?(category='Sobrepeso',color='#ffc107'):(category='Obesidad',color='#dc3545');document.getElementById('imcCategory').innerHTML=`<strong style="color: ${color};">${category}</strong>`}else document.getElementById('imc').value='',document.getElementById('imcCategory').textContent=''}function addPrepRace(){prepRaceCounter++;const raceHtml=`<div class="prep-race-item" id="prepRace${prepRaceCounter}"><div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px"><h4 style="margin:0;color:var(--primary-color)"><i class="fas fa-trophy"></i> Carrera #${prepRaceCounter}</h4><button type="button" class="btn btn-danger btn-sm" onclick="removePrepRace(${prepRaceCounter})"><i class="fas fa-trash"></i></button></div><div class="form-grid"><div class="form-group"><label>Nombre</label><input type="text" name="prepRaceName[]" placeholder="Ej: 10K Los Muermos" class="prep-race-name"></div><div class="form-group"><label>Distancia</label><select name="prepRaceDistance[]" class="prep-race-distance"><option value="5K">5K</option><option value="10K">10K</option><option value="15K">15K</option><option value="21K">21K</option></select></div><div class="form-group"><label>Fecha</label><input type="date" name="prepRaceDate[]" class="prep-race-date"></div></div></div>`;document.getElementById('prepRacesList').insertAdjacentHTML('beforeend',raceHtml)}function removePrepRace(id){const element=document.getElementById(`prepRace${id}`);element&&element.remove()}document.getElementById('trainingForm').addEventListener('submit',function(e){e.preventDefault();generateTrainingPlan()});function generateTrainingPlan(){const name=document.getElementById('athleteName').value,age=parseInt(document.getElementById('athleteAge').value),experience=document.getElementById('experience').value,weight=parseFloat(document.getElementById('weight').value),height=parseFloat(document.getElementById('height').value),imc=parseFloat(document.getElementById('imc').value),hrMax=parseInt(document.getElementById('hrMax').value),hrRest=parseInt(document.getElementById('hrRest').value),vo2max=document.getElementById('vo2max').value?parseFloat(document.getElementById('vo2max').value):null,distance=document.getElementById('distance').value,targetPace=document.getElementById('targetPace').value,raceDate=document.getElementById('raceDate').value,notes=document.getElementById('notes').value,trainingDays=Array.from(document.querySelectorAll('input[name="trainingDays"]:checked')).map(cb=>cb.value);if(trainingDays.length<3)return void alert('Debes seleccionar al menos 3 días de entrenamiento');const crossTraining=Array.from(document.querySelectorAll('input[name="crossTraining"]:checked')).map(cb=>cb.value),prepRaces=[];document.querySelectorAll('.prep-race-item').forEach(item=>{const raceName=item.querySelector('.prep-race-name').value,raceDistance=item.querySelector('.prep-race-distance').value,raceDate=item.querySelector('.prep-race-date').value;raceName&&raceDate&&prepRaces.push({name:raceName,distance:raceDistance,date:raceDate})});const weeks=calculateWeeks(distance,experience),weeklyPlans=generateWeeklyPlans(distance,experience,weeks,trainingDays,targetPace,name,hrMax,hrRest,crossTraining,prepRaces,raceDate),plan={id:Date.now().toString(),userData:{name:name,age:age,experience:experience,weight:weight,height:height,imc:imc,hrMax:hrMax,hrRest:hrRest,vo2max:vo2max,distance:distance,targetPace:targetPace,raceDate:raceDate,trainingDays:trainingDays,crossTraining:crossTraining,prepRaces:prepRaces,notes:notes},totalWeeks:weeks,weeklyPlans:weeklyPlans,createdBy:currentUser.username,createdAt:(new Date).toISOString()};savePlan(plan);alert('¡Plan creado con éxito!');resetForm();updateDashboard();showSection('plan-list')}function calculateWeeks(distance,experience){const weeksMap={'5K':{principiante:8,intermedio:6,avanzado:6},'10K':{principiante:10,intermedio:8,avanzado:8},'21K':{principiante:16,intermedio:14,avanzado:12},'42K':{principiante:24,intermedio:20,avanzado:18}};return weeksMap[distance][experience]}function generateWeeklyPlans(distance,experience,totalWeeks,trainingDays,targetPace,athleteName,hrMax,hrRest,crossTraining,prepRaces,mainRaceDate){const plans=[],basePeriod=Math.ceil(.35*totalWeeks),specificPeriod=Math.ceil(.4*totalWeeks),competitionPeriod=Math.ceil(.15*totalWeeks),transitionPeriod=totalWeeks-basePeriod-specificPeriod-competitionPeriod;let currentPeriod='basico';const mainRaceDateObj=new Date(mainRaceDate),startDate=new Date(mainRaceDateObj);startDate.setDate(startDate.getDate()-7*totalWeeks);for(let week=1;week<=totalWeeks;week++){const weekStart=new Date(startDate);weekStart.setDate(weekStart.getDate()+7*(week-1));week>basePeriod+specificPeriod+competitionPeriod?currentPeriod='transicion':week>basePeriod+specificPeriod?currentPeriod='competencia':week>basePeriod&&(currentPeriod='especifico');const isRaceWeek=week===totalWeeks,isRecoveryWeek=week%4==0&&!isRaceWeek;let prepRaceThisWeek=null;prepRaces.forEach(race=>{const raceDateObj=new Date(race.date),weekEnd=new Date(weekStart);weekEnd.setDate(weekEnd.getDate()+7);raceDateObj>=weekStart&&raceDateObj<weekEnd&&(prepRaceThisWeek=race)});const sessions=generateWeekSessions(week,totalWeeks,trainingDays,currentPeriod,isRaceWeek,isRecoveryWeek,distance,targetPace,experience,athleteName,hrMax,hrRest,crossTraining,prepRaceThisWeek),totalKm=sessions.reduce((sum,s)=>{const km=parseFloat(s.distance.replace(' km','').replace('km','')||0);return sum+km},0);plans.push({week:week,period:currentPeriod,isRaceWeek:isRaceWeek,isRecoveryWeek:isRecoveryWeek,prepRace:prepRaceThisWeek,sessions:sessions,totalKm:Math.round(totalKm)})}return plans}function generateWeekSessions(week,totalWeeks,trainingDays,period,isRaceWeek,isRecoveryWeek,distance,targetPace,experience,athleteName,hrMax,hrRest,crossTraining,prepRace){const sessions=[];if(isRaceWeek)trainingDays.forEach((day,index)=>{index===trainingDays.length-1?sessions.push(createRaceDay(day,distance,targetPace,athleteName,hrMax,hrRest)):index===trainingDays.length-2?sessions.push(createPreRaceSession(day,hrMax,hrRest,athleteName)):sessions.push(createTaperSession(day,hrMax,hrRest))});else if(prepRace)trainingDays.forEach((day,index)=>{if(index===trainingDays.length-1)sessions.push(createPrepRaceDay(day,prepRace,targetPace,hrMax,hrRest));else if(index===trainingDays.length-2)sessions.push(createPreRaceSession(day,hrMax,hrRest,athleteName));else{const baseKm=getBaseKm(distance,experience,week,totalWeeks),session=createRegularSession(day,index,trainingDays.length,period,baseKm,targetPace,hrMax,hrRest,crossTraining,isRecoveryWeek);sessions.push(session)}});else{const baseKm=getBaseKm(distance,experience,week,totalWeeks);trainingDays.forEach((day,index)=>{const session=createRegularSession(day,index,trainingDays.length,period,baseKm,targetPace,hrMax,hrRest,crossTraining,isRecoveryWeek);sessions.push(session)})}return sessions}function createRaceDay(day,distance,targetPace,athleteName,hrMax,hrRest){const motivationalMessages={'5K':${athleteName}, hoy es tu día! Los 5K son pura potencia. ¡Dalo TODO! 🏃‍♂️💪🔥`,'10K':${athleteName}, llegó el momento! Confía en tu entrenamiento. ¡Tu mejor versión está a punto de cruzar esa meta! 🏃‍♂️✨`,'21K':${athleteName}, hoy escribes tu historia! Los primeros 15K con la cabeza, los últimos 6K con el corazón. ¡El podio te espera! 🏃‍♂️💪🏅`,'42K':${athleteName}, hoy te conviertes en MARATONISTA! Los primeros 30K controla, los últimos 12K puro corazón. ¡TÚ PUEDES! 🏃‍♂️🔥🏆✨`},hrZones=calculateHRZones(hrMax,hrRest);return{day:day.toUpperCase(),type:`🏁 CARRERA OBJETIVO - ${distance}`,distance:distance,motivationalMessage:motivationalMessages[distance],details:{warmup:'20-30 min antes: Movilidad articular suave + 10 min trote suave + 4 progresivos de 80m',main:`¡ES TU MOMENTO! Sal con confianza a ${targetPace}/km. Hidrátate cada 5K. Geles según plan. ¡Disfruta!`,cooldown:'Post-carrera: Camina 10-15 min, hidrátate, estira suavemente',hrZones:hrZones,nutrition:`Desayuno 3-4h antes. Gel 15 min antes. Durante: hidratación cada 5K + gel cada 45 min`,gear:'Zapatilla de competencia. Ropa técnica ligera.',mentalPrep:'Confía en tu entrenamiento. ¡TÚ PUEDES!'},isRaceDay:!0,purpose:{title:'🎯 Tu Gran Día',content:'Este es el día para el que entrenaste. Confía en tu preparación y disfruta.'}}}function createPreRaceSession(day,hrMax,hrRest,athleteName){const hrZones=calculateHRZones(hrMax,hrRest);return{day:day.toUpperCase(),type:'🎯 Activación Pre-Carrera',distance:'4 km',motivationalMessage:`${athleteName}, mañana es TU DÍA! Hoy solo activamos y relajamos. ¡Mañana vas a brillar! 💪✨`,details:{warmup:'5 min caminata con respiraciones profundas',main:'Trote MUY fácil 4km. 4 progresivos de 60m',cooldown:'10 min estiramientos suaves',hrZones:{zone:'Zona 1-2',bpm:`${hrZones.zone1.min}-${hrZones.zone2.max} bpm`,percentage:'50-70% FCMax'},stretching:'Cuádriceps, isquiotibiales, gemelos, psoas. 20 seg cada grupo',nutrition:'Hidratación constante. Comida ligera. Cena temprano.',mentalPrep:'Visualización positiva. Repasa estrategia. Duerme 8+ horas'},purpose:{title:'🧘 Activación y Mentalización',content:'Mantener piernas activas sin fatiga. Preparación mental para la carrera.'}}}function createTaperSession(day,hrMax,hrRest){const hrZones=calculateHRZones(hrMax,hrRest);return{day:day.toUpperCase(),type:'Recuperación - Taper',distance:'6 km',details:{warmup:'5 min caminata suave',main:'Trote muy suave. Sin esfuerzo. Enfoque en recuperación',cooldown:'15 min estiramientos profundos + rodillo',hrZones:{zone:'Zona 1',bpm:`${hrZones.zone1.min}-${hrZones.zone1.max} bpm`,percentage:'50-60% FCMax'},stretching:'Cuádriceps (30 seg), Isquiotibiales (30 seg), Gemelos (30 seg)',hydration:'Mantén hidratación constante',recovery:'Masaje suave, baño de contraste, elevación de piernas'},purpose:{title:'💆 Recuperación y Preparación',content:'Reducir volumen para llegar fresco. Optimizar recuperación muscular.'}}}function createPrepRaceDay(day,prepRace,targetPace,hrMax,hrRest){const hrZones=calculateHRZones(hrMax,hrRest);return{day:day.toUpperCase(),type:`🏃 Carrera Preparatoria - ${prepRace.name}`,distance:prepRace.distance,isPrepRace:!0,details:{warmup:'20 min antes: Movilidad + 10 min trote + 3 progresivos',main:`Carrera a ritmo controlado (5-10 seg más lento). FC: ${hrZones.zone3.min}-${hrZones.zone4.max} bpm`,cooldown:'10 min caminata, hidratación, estiramientos',hrZones:hrZones,strategy:'Prueba tu estrategia de hidratación y nutrición',learning:'Anota qué funcionó para la carrera principal'},purpose:{title:'🎯 Simulacro y Aprendizaje',content:'Probar estrategia de hidratación/nutrición, sentir el ritmo, ganar confianza.'}}}function createRegularSession(day,dayIndex,totalDays,period,baseKm,targetPace,hrMax,hrRest,crossTraining,isRecovery){const hrZones=calculateHRZones(hrMax,hrRest),kmPerSession=baseKm/totalDays;return'basico'===period?generateBaseSession(day,kmPerSession,isRecovery,hrZones,dayIndex,crossTraining):'especifico'===period?generateSpecificSession(day,dayIndex,totalDays,kmPerSession,targetPace,hrZones,crossTraining):'competencia'===period?generateCompetitionSession(day,dayIndex,totalDays,kmPerSession,targetPace,hrZones,crossTraining):generateTransitionSession(day,kmPerSession,hrZones,crossTraining)}function calculateHRZones(hrMax,hrRest){const hrReserve=hrMax-hrRest;return{zone1:{min:Math.round(hrRest+.5*hrReserve),max:Math.round(hrRest+.6*hrReserve),name:'Recuperación'},zone2:{min:Math.round(hrRest+.6*hrReserve),max:Math.round(hrRest+.7*hrReserve),name:'Aeróbica'},zone3:{min:Math.round(hrRest+.7*hrReserve),max:Math.round(hrRest+.8*hrReserve),name:'Tempo'},zone4:{min:Math.round(hrRest+.8*hrReserve),max:Math.round(hrRest+.9*hrReserve),name:'Umbral'},zone5:{min:Math.round(hrRest+.9*hrReserve),max:hrMax,name:'Máxima'}}}function generateBaseSession(day,km,isRecovery,hrZones,dayIndex,crossTraining){const distance=isRecovery?Math.round(.7*km):Math.round(km);if(dayIndex%3==1&&crossTraining.length>0){const activities=crossTraining.join(', ');return{day:day.toUpperCase(),type:'Entrenamiento Cruzado',distance:'30-45 min',details:{warmup:'10 min movilidad',main:`${activities} a intensidad moderada. FC: ${hrZones.zone1.min}-${hrZones.zone2.max} bpm`,cooldown:'10 min estiramiento + rodillo',crossTraining:activities,benefits:'Reduce impacto, mejora fuerza, previene lesiones'},purpose:{title:'🔄 Recuperación Activa',content:`El cross training permite recuperación mientras mantienes fitness. ${activities} fortalece grupos musculares complementarios.`}}}return{day:day.toUpperCase(),type:'Trote Continuo Base',distance:`${distance} km`,details:{warmup:'10 min caminata + activación glúteos + 5 min trote suave',main:`Trote continuo conversacional. FC: ${hrZones.zone2.min}-${hrZones.zone2.max} bpm (Zona 2). Postura: cabeza erguida, hombros relajados`,cooldown:'5 min trote suave + 5 min caminata + 15 min estiramientos',hrZones:hrZones,technique:'Cadencia 170-180 pasos/min, contacto suave, respiración rítmica',stretching:'Cuádriceps (30 seg), Isquiotibiales (30 seg), Gemelos (30 seg), Psoas (30 seg)',gear:'Zapatilla con buena amortiguación',hydration:'Hidrátate antes y después. Si >60 min, lleva agua'},purpose:{title:'🎯 Construcción de Base Aeróbica',content:'Desarrollar resistencia aeróbica fundamental. Adaptación cardiovascular. Este es el pilar de tu entrenamiento.'}}}function generateSpecificSession(day,index,totalDays,km,targetPace,hrZones,crossTraining){const distance=Math.round(km);if(0===index||1===index){if(1===index&&crossTraining.length>0){const activities=crossTraining.join(', ');return{day:day.toUpperCase(),type:'Recuperación + Cross Training',distance:`${Math.round(.6*distance)} km + 30 min`,details:{warmup:'5 min movilidad',main:`Trote suave ${Math.round(.6*distance)}km + ${activities} 30 min. FC: ${hrZones.zone1.min}-${hrZones.zone2.max} bpm`,cooldown:'15 min estiramientos + rodillo',recovery:'Enfoque en recuperación. Baño de contraste si es posible'},purpose:{title:'💆 Recuperación Activa Multimodal',content:'Combina recuperación cardiovascular con fortalecimiento sin impacto.'}}}return{day:day.toUpperCase(),type:'Trote Regenerativo',distance:`${distance} km`,details:{warmup:'10 min trote muy suave + movilidad',main:`Trote muy cómodo. FC: ${hrZones.zone1.min}-${hrZones.zone2.max} bpm. Recuperación activa`,cooldown:'10 min estiramientos profundos',recovery:'Rodillo de espuma: Gemelos, cuádriceps, isquiotibiales, glúteos (5 min cada grupo)'},purpose:{title:'💆 Recuperación Muscular',content:'Facilitar recuperación activa, mejorar flujo sanguíneo. Prepara para sesiones intensas.'}}}else if(index===Math.floor(totalDays/2))return{day:day.toUpperCase(),type:'Intervalos en Pista/Ruta',distance:`${distance+3} km`,details:{warmup:'15 min trote + Ejercicios dinámicos + 4 progresivos 100m',main:`SESIÓN CLAVE: 8-10 x 400m a ${targetPace}/km con 90 seg recuperación. FC: ${hrZones.zone4.min}-${hrZones.zone5.min} bpm`,cooldown:'10 min trote suave + 15 min estiramientos + core (plancha 3x30seg)',technique:'Brazos activos, cadencia alta, respira profundo, mantén forma técnica',mentalFocus:'Cada intervalo con concentración. Último intervalo da tu mejor esfuerzo.',recovery:'Recuperación activa entre intervalos es CLAVE',nutrition:'Gel 30 min antes si entrenas en ayunas'},purpose:{title:'⚡ Desarrollo de Velocidad y VO2Max',content:'Mejorar capacidad anaeróbica, velocidad, economía de carrera. Entrenas al cuerpo a mantener ritmo objetivo con menor esfuerzo.'}};else if(index===totalDays-2)return{day:day.toUpperCase(),type:'Tempo Run (Ritmo Sostenido)',distance:`${distance+3} km`,details:{warmup:'15 min trote + 4 progresivos 100m',main:`SESIÓN CLAVE: 25-35 min a ritmo TEMPO (10-15 seg más rápido que objetivo). FC: ${hrZones.zone3.min}-${hrZones.zone4.max} bpm. Puedes decir frases cortas pero no conversación fluida.`,cooldown:'10 min trote fácil + 15 min estiramientos profundos',technique:'Postura erguida, respiración controlada profunda, relaja hombros',mentalFocus:'Divide en bloques de 5 min. Cada bloque es un mini-desafío.',gear:'Zapatilla mixta o tempo (más ligera)',nutrition:'Hidratación durante si >45 min totales'},purpose:{title:'🎯 Umbral Anaeróbico',content:'Elevar tu umbral de lactato. Mejora capacidad de sostener ritmos rápidos. Fundamental para resistencia.'}};else return{day:day.toUpperCase(),type:'Carrera Larga - Long Run',distance:`${Math.round(1.6*distance)} km`,details:{warmup:'15 min trote MUY fácil + movilidad completa + activación glúteos',main:`SESIÓN FUNDAMENTAL: Carrera continua aeróbica. FC: ${hrZones.zone2.min}-${hrZones.zone2.max} bpm. Últimos 3-5 km puedes acelerar ligeramente. Hidratación cada 20 min (200ml). Si >90 min: gel cada 45 min.`,cooldown:'10 min caminata + 20 min estiramientos COMPLETOS + rodillo 10 min + hidratación con electrolitos + plátano',nutrition:'Pre: Desayuno ligero 2-3h antes. Durante: Hidratación + geles. Post: Proteína + carbohidratos en 30 min',mentalFocus:'Disfruta el proceso. Meditación en movimiento. Los últimos 5km son mentales - aquí creces',gear:'Zapatilla maximalista con máxima amortiguación',recovery:'Post: Baño de contraste (3 min cada), elevación piernas 15 min, masaje suave',progression:'Empieza conservador. Siéntete bien en primeros 2/3. Acelera último tercio si te sientes fuerte'},purpose:{title:'🏃 Resistencia Muscular y Mental',content:'Adaptación muscular y tendinosa. Entrenamiento mental crucial. Enseña al cuerpo a usar grasas como combustible. El long run es donde se construyen los maratonistas.'}}}function generateCompetitionSession(day,index,totalDays,km,targetPace,hrZones,crossTraining){return generateSpecificSession(day,index,totalDays,Math.round(.85*km),targetPace,hrZones,crossTraining)}function generateTransitionSession(day,km,hrZones,crossTraining){if(crossTraining.length>0&&Math.random()>.5){const activities=crossTraining.join(' o ');return{day:day.toUpperCase(),type:'Recuperación Activa - Cross Training',distance:'30-40 min',details:{warmup:'10 min movilidad articular completa',main:`Sesión suave de ${activities}. Intensidad baja-moderada. FC: ${hrZones.zone1.min}-${hrZones.zone2.max} bpm`,cooldown:'15 min estiramientos profundos + trabajo de movilidad',focus:'Recuperación total, prevención de lesiones, mantener movilidad'},purpose:{title:'🔄 Recuperación y Mantenimiento',content:'Semana de transición para recuperación profunda. Mantener actividad sin impacto.'}}}return{day:day.toUpperCase(),type:'Trote Regenerativo Ligero',distance:`${Math.round(.6*km)} km`,details:{warmup:'5 min caminata con respiraciones profundas',main:`Trote MUY suave y relajado. FC: ${hrZones.zone1.min}-${hrZones.zone1.max} bpm. Sin reloj, sin presión`,cooldown:'15 min estiramientos + rodillo completo + yoga/pilates 15 min',recovery:'Enfoque total en recuperación. Escucha tu cuerpo',mentalHealth:'Disfruta correr sin presión. Reconecta con por qué amas correr'},purpose:{title:'💆 Recuperación Total',content:'Permitir regeneración profunda después del ciclo. Preparación para próximo ciclo o disfrute post-carrera.'}}}function getBaseKm(distance,experience,week,totalWeeks){const baseKmMap={'5K':{principiante:20,intermedio:25,avanzado:30},'10K':{principiante:30,intermedio:35,avanzado:40},'21K':{principiante:40,intermedio:55,avanzado:70},'42K':{principiante:50,intermedio:70,avanzado:90}},base=baseKmMap[distance][experience],progress=week/totalWeeks,peak=.75;if(progress<peak)return Math.round(base+.6*base*(progress/peak));else{const taperProgress=(progress-peak)/(1-peak),peakKm=base+.6*base;return Math.round(peakKm*(1-.3*taperProgress))}}function loadPlanList(){const plans=getPlans(),listContent=document.getElementById('planListContent'),placeholder=document.getElementById('noPlanPlaceholder');let filteredPlans=plans;if(currentUser&&'user'===currentUser.role&&(filteredPlans=plans.filter(p=>p.createdBy===currentUser.username)),0===filteredPlans.length)return placeholder.classList.remove('hidden'),void(listContent.innerHTML='');placeholder.classList.add('hidden');const experienceText={principiante:'Principiante',intermedio:'Intermedio',avanzado:'Avanzado'};let html='';filteredPlans.forEach(plan=>{const crossTrainingText=plan.userData.crossTraining&&plan.userData.crossTraining.length>0?plan.userData.crossTraining.join(', '):'No';html+=`<div class="plan-card"><div class="plan-info"><h3><i class="fas fa-user"></i> ${plan.userData.name}</h3><p><i class="fas fa-birthday-cake"></i> <strong>Edad:</strong> ${plan.userData.age} años | <strong>IMC:</strong> ${plan.userData.imc}</p><p><i class="fas fa-layer-group"></i> <strong>Experiencia:</strong> ${experienceText[plan.userData.experience]}</p><p><i class="fas fa-heartbeat"></i> <strong>FC Max:</strong> ${plan.userData.hrMax} bpm | <strong>FC Reposo:</strong> ${plan.userData.hrRest} bpm</p><p><i class="fas fa-flag-checkered"></i> <strong>Objetivo:</strong> ${plan.userData.distance} - Ritmo ${plan.userData.targetPace}/km</p><p><i class="fas fa-calendar-check"></i> <strong>Fecha:</strong> ${new Date(plan.userData.raceDate).toLocaleDateString('es-CL')}</p><p><i class="fas fa-calendar-week"></i> <strong>Duración:</strong> ${plan.totalWeeks} sem | <strong>Días:</strong> ${plan.userData.trainingDays.length}</p><p><i class="fas fa-dumbbell"></i> <strong>Cross Training:</strong> ${crossTrainingText}</p>${plan.userData.prepRaces&&plan.userData.prepRaces.length>0?`<p><i class="fas fa-trophy"></i> <strong>Carreras prep:</strong> ${plan.userData.prepRaces.length}</p>`:''}<p style="font-size:12px;color:#999"><i class="fas fa-clock"></i> ${new Date(plan.createdAt).toLocaleString()}</p></div><div class="plan-actions"><button class="btn btn-primary btn-sm" onclick="viewPlanDetail('${plan.id}')"><i class="fas fa-eye"></i> Ver</button><button class="btn btn-success btn-sm" onclick="generatePDF('${plan.id}')"><i class="fas fa-file-pdf"></i> PDF</button>${currentUser&&'admin'===currentUser.role?`<button class="btn btn-danger btn-sm" onclick="confirmDeletePlan('${plan.id}')"><i class="fas fa-trash"></i> Eliminar</button>`:''}</div></div>`});listContent.innerHTML=html}function viewPlanDetail(planId){const plans=getPlans(),plan=plans.find(p=>p.id===planId);if(!plan)return;currentViewPlan=plan;const experienceText={principiante:'Principiante',intermedio:'Intermedio',avanzado:'Avanzado'};document.getElementById('modalTitle').textContent=`Plan - ${plan.userData.name}`;let html=`<div style="background:linear-gradient(135deg,#f8f9fa 0%,#e9ecef 100%);padding:25px;border-radius:8px;margin-bottom:30px;border-left:5px solid var(--primary-color)"><h3 style="color:var(--primary-color);margin-bottom:20px;font-size:22px"><i class="fas fa-user-circle"></i> Perfil del Atleta</h3><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:15px"><p><strong>👤 Nombre:</strong> ${plan.userData.name}</p><p><strong>🎂 Edad:</strong> ${plan.userData.age} años</p><p><strong>📊 Experiencia:</strong> ${experienceText[plan.userData.experience]}</p><p><strong>⚖️ Peso:</strong> ${plan.userData.weight} kg</p><p><strong>📏 Talla:</strong> ${plan.userData.height} cm</p><p><strong>💪 IMC:</strong> ${plan.userData.imc}</p><p><strong>❤️ FC Máxima:</strong> ${plan.userData.hrMax} bpm</p><p><strong>🛌 FC Reposo:</strong> ${plan.userData.hrRest} bpm</p>${plan.userData.vo2max?`<p><strong>🫁 VO2Max:</strong> ${plan.userData.vo2max}</p>`:''}<p><strong>🎯 Objetivo:</strong> ${plan.userData.distance}</p><p><strong>⏱️ Ritmo:</strong> ${plan.userData.targetPace}/km</p><p><strong>📅 Fecha:</strong> <strong style="color:var(--primary-color);font-size:16px">${new Date(plan.userData.raceDate).toLocaleDateString('es-CL',{weekday:'long',year:'numeric',month:'long',day:'numeric'})}</strong></p><p><strong>📆 Duración:</strong> ${plan.totalWeeks} sem</p><p><strong>🏃 Días:</strong> ${plan.userData.trainingDays.join(', ')}</p></div>${plan.userData.crossTraining&&plan.userData.crossTraining.length>0?`<p style="margin-top:15px"><strong>🏋️ Cross Training:</strong> ${plan.userData.crossTraining.join(', ')}</p>`:''}${plan.userData.prepRaces&&plan.userData.prepRaces.length>0?`<div style="margin-top:15px"><p><strong>🏆 Carreras Preparatorias:</strong></p><ul style="margin-left:20px;margin-top:8px">${plan.userData.prepRaces.map(r=>`<li>${r.name} - ${r.distance} - ${new Date(r.date).toLocaleDateString('es-CL')}</li>`).join('')}</ul></div>`:''}${plan.userData.notes?`<p style="margin-top:15px"><strong>📝 Notas:</strong> ${plan.userData.notes}</p>`:''}</div>`;const periodGroups={basico:[],especifico:[],competencia:[],transicion:[]};plan.weeklyPlans.forEach(week=>{periodGroups[week.period].push(week)});const periodNames={basico:'PERÍODO BASE',especifico:'PERÍODO ESPECÍFICO',competencia:'PERÍODO DE COMPETENCIA',transicion:'PERÍODO DE TRANSICIÓN'};Object.keys(periodGroups).forEach(periodKey=>{if(0===periodGroups[periodKey].length)return;html+=`<div class="period-section"><div class="period-header"><i class="fas fa-calendar-alt"></i> ${periodNames[periodKey]}</div>`;periodGroups[periodKey].forEach(week=>{html+=`<div class="week-section ${week.isRaceWeek?'race-week':''}"><div class="week-header"><span>${week.isRaceWeek?'🏆':'📅'} SEMANA ${week.week}${week.isRaceWeek?' - 🎯 COMPETENCIA':''}${week.prepRace?' - 🏃 '+week.prepRace.name:''}${week.isRecoveryWeek&&!week.isRaceWeek?' (💆 Recuperación)':''}</span><span style="color:var(--secondary-color);font-weight:bold">${week.totalKm} km</span></div>`;week.sessions.forEach(session=>{html+=`<div class="session-card ${session.isRaceDay?'race-day':''}"><div class="session-header"><div class="session-title">${session.isRaceDay?'🏁':session.isPrepRace?'🏃':'💪'} ${session.day} - ${session.type}</div><div class="session-distance">${session.distance}</div></div>${session.motivationalMessage?`<div class="motivational-message">${session.motivationalMessage}</div>`:''}<div class="session-details"><div class="detail-section"><div class="detail-section-title">🔥 Calentamiento</div><div class="detail-item">${session.details.warmup}</div></div><div class="detail-section"><div class="detail-section-title">🏃 Desarrollo Principal</div><div class="detail-item">${session.details.main}</div></div><div class="detail-section"><div class="detail-section-title">🧘 Enfriamiento</div><div class="detail-item">${session.details.cooldown}</div></div>${session.details.hrZones?`<div class="detail-section"><div class="detail-section-title">❤️ Zonas FC</div><div class="hr-zones">${session.details.hrZones.zone?`<div class="hr-zone"><span class="hr-zone-label">${session.details.hrZones.zone}</span>${session.details.hrZones.bpm}<br>${session.details.hrZones.percentage||''}</div>`:`<div class="hr-zone"><span class="hr-zone-label">Zona 1 - Recuperación</span>${session.details.hrZones.zone1.min}-${session.details.hrZones.zone1.max} bpm</div><div class="hr-zone"><span class="hr-zone-label">Zona 2 - Aeróbica</span>${session.details.hrZones.zone2.min}-${session.details.hrZones.zone2.max} bpm</div><div class="hr-zone"><span class="hr-zone-label">Zona 3 - Tempo</span>${session.details.hrZones.zone3.min}-${session.details.hrZones.zone3.max} bpm</div><div class="hr-zone"><span class="hr-zone-label">Zona 4 - Umbral</span>${session.details.hrZones.zone4.min}-${session.details.hrZones.zone4.max} bpm</div>`}</div></div>`:''}${session.details.stretching?`<div class="detail-section"><div class="detail-section-title">🤸 Estiramientos</div><div class="detail-item">${session.details.stretching}</div></div>`:''}${session.details.technique?`<div class="detail-section"><div class="detail-section-title">🎯 Técnica</div><div class="detail-item">${session.details.technique}</div></div>`:''}${session.details.mentalFocus?`<div class="detail-section"><div class="detail-section-title">🧠 Enfoque Mental</div><div class="detail-item">${session.details.mentalFocus}</div></div>`:''}${session.details.nutrition?`<div class="detail-section"><div class="detail-section-title">🍎 Nutrición</div><div class="detail-item">${session.details.nutrition}</div></div>`:''}${session.details.gear?`<div class="detail-section"><div class="detail-section-title">👟 Equipo</div><div class="detail-item">${session.details.gear}</div></div>`:''}${session.details.recovery?`<div class="detail-section"><div class="detail-section-title">💆 Recuperación</div><div class="detail-item">${session.details.recovery}</div></div>`:''}</div>${session.purpose?`<div class="purpose-box"><div class="purpose-title">${session.purpose.title}</div><div class="purpose-content">${session.purpose.content}</div></div>`:''}</div>`});html+=`<div class="volume-summary">📊 VOLUMEN: ${week.totalKm} KM${week.isRecoveryWeek&&!week.isRaceWeek?' | 💆 SEMANA DE RECUPERACIÓN':''}</div></div>`});html+='</div>'});document.getElementById('modalPlanContent').innerHTML=html;document.getElementById('planDetailModal').classList.add('show')}function closeModal(){document.getElementById('planDetailModal').classList.remove('show')}function confirmDeletePlan(planId){currentUser&&'admin'===currentUser.role?confirm('¿Eliminar este plan?')&&(deletePlan(planId),loadPlanList(),updateDashboard(),alert('Plan eliminado')):alert('Solo administradores pueden eliminar')}function generatePDF(planId){const plans=getPlans(),plan=plans.find(p=>p.id===planId);if(!plan)return void alert('Plan no encontrado');if(!window.jspdf||!window.jspdf.jsPDF)return void alert('Error: jsPDF no cargada');const loadingMsg=document.createElement('div');loadingMsg.style.cssText='position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background:#fff;padding:30px;border-radius:10px;box-shadow:0 10px 40px rgba(0,0,0,.3);z-index:10000;text-align:center';loadingMsg.innerHTML='<div style="border:4px solid #f3f3f3;border-top:4px solid #dc143c;border-radius:50%;width:50px;height:50px;animation:spin 1s linear infinite;margin:0 auto 20px"></div><h3>Generando PDF...</h3><p>Espera por favor</p><style>@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}</style>';document.body.appendChild(loadingMsg);setTimeout(()=>{try{const{jsPDF:jsPDF}=window.jspdf,doc=new jsPDF;let yPos=20;const pageHeight=doc.internal.pageSize.height,margin=20;doc.setFontSize(20);doc.setTextColor(220,20,60);doc.setFont(void 0,'bold');doc.text('OSORNO RUNNERS',105,yPos,{align:'center'});yPos+=8;doc.setFontSize(16);doc.text('PLAN DE ENTRENAMIENTO PROFESIONAL',105,yPos,{align:'center'});yPos+=15;doc.setFontSize(9);doc.setTextColor(0,0,0);doc.setFont(void 0,'normal');const experienceText={principiante:'Principiante',intermedio:'Intermedio',avanzado:'Avanzado'},raceDateObj=new Date(plan.userData.raceDate),raceDay=raceDateObj.toLocaleDateString('es-CL'),userInfo=[['Atleta:',plan.userData.name,'Edad:',`${plan.userData.age} años`],['Experiencia:',experienceText[plan.userData.experience]||plan.userData.experience,'Objetivo:',plan.userData.distance],['Ritmo:',`${plan.userData.targetPace}/km`,'Fecha:',raceDay],['Duración:',`${plan.totalWeeks} sem.`,'Días/sem:',`${plan.userData.trainingDays?plan.userData.trainingDays.length:'N/A'}`]];plan.userData.weight&&plan.userData.height&&(userInfo.push(['Peso:',`${plan.userData.weight} kg`,'Talla:',`${plan.userData.height} cm`]),userInfo.push(['IMC:',`${plan.userData.imc}`,'FC Max:',`${plan.userData.hrMax} bpm`]),userInfo.push(['FC Reposo:',`${plan.userData.hrRest} bpm`,'VO2Max:',`${plan.userData.vo2max||'N/A'}`]));userInfo.forEach(row=>{yPos>pageHeight-20&&(doc.addPage(),yPos=20);doc.setFont(void 0,'bold');doc.text(row[0],margin,yPos);doc.setFont(void 0,'normal');doc.text(String(row[1]),margin+30,yPos);row[2]&&(doc.setFont(void 0,'bold'),doc.text(row[2],110,yPos),doc.setFont(void 0,'normal'),doc.text(String(row[3]),135,yPos));yPos+=6});yPos+=5;const periodGroups={basico:[],especifico:[],competencia:[],transicion:[]};plan.weeklyPlans.forEach(week=>{periodGroups[week.period].push(week)});const periodNames={basico:'PERÍODO BASE',especifico:'PERÍODO ESPECÍFICO',competencia:'PERÍODO DE COMPETENCIA',transicion:'PERÍODO DE TRANSICIÓN'};Object.keys(periodGroups).forEach(periodKey=>{if(0===periodGroups[periodKey].length)return;yPos>pageHeight-30&&(doc.addPage(),yPos=20);doc.setFillColor(26,26,26);doc.rect(margin,yPos-5,170,8,'F');doc.setTextColor(255,255,255);doc.setFontSize(11);doc.setFont(void 0,'bold');doc.text(periodNames[periodKey],105,yPos,{align:'center'});yPos+=10;periodGroups[periodKey].forEach(week=>{yPos>pageHeight-40&&(doc.addPage(),yPos=20);week.isRaceWeek?(doc.setFillColor(255,215,0),doc.rect(margin,yPos-4,170,8,'F'),doc.setTextColor(26,26,26)):(doc.setFillColor(220,20,60),doc.rect(margin,yPos-4,170,7,'F'),doc.setTextColor(255,255,255));doc.setFontSize(10);doc.setFont(void 0,'bold');let weekText=`SEMANA ${week.week}`;week.isRaceWeek?weekText+=' - COMPETENCIA':week.prepRace?weekText+=` - ${week.prepRace.name}`:week.isRecoveryWeek&&(weekText+=' (Recuperación)');doc.text(weekText,105,yPos,{align:'center'});yPos+=9;week.sessions.forEach(session=>{yPos>pageHeight-30&&(doc.addPage(),yPos=20);doc.setFontSize(8);session.isRaceDay?doc.setTextColor(255,140,0):doc.setTextColor(220,20,60);doc.setFont(void 0,'bold');const sessionTitle=`${session.day} - ${session.type}`;doc.text(sessionTitle,margin,yPos);doc.setTextColor(0,0,0);doc.text(String(session.distance),180,yPos,{align:'right'});yPos+=4;if(session.motivationalMessage){doc.setFontSize(7);doc.setFont(void 0,'italic');doc.setTextColor(255,140,0);const msgLines=doc.splitTextToSize(session.motivationalMessage,170);msgLines.forEach(line=>{yPos>pageHeight-15&&(doc.addPage(),yPos=20);doc.text(line,margin+2,yPos);yPos+=3.5});yPos+=2;doc.setTextColor(0,0,0)}doc.setFontSize(7);doc.setFont(void 0,'normal');const details=[`Cal: ${session.details.warmup}`,`Des: ${session.details.main}`,`Enf: ${session.details.cooldown}`];details.forEach(detail=>{yPos>pageHeight-15&&(doc.addPage(),yPos=20);const lines=doc.splitTextToSize(detail,170);lines.forEach(line=>{yPos>pageHeight-10&&(doc.addPage(),yPos=20);doc.text(line,margin+2,yPos);yPos+=3.5})});yPos+=2});yPos>pageHeight-15&&(doc.addPage(),yPos=20);doc.setFillColor(26,26,26);doc.rect(margin,yPos,170,6,'F');doc.setTextColor(255,255,255);doc.setFontSize(8);doc.setFont(void 0,'bold');doc.text(`VOLUMEN: ${week.totalKm} KM`,105,yPos+4,{align:'center'});yPos+=10})});const fileName=`Plan_OsornoRunners_${plan.userData.name.replace(/\s+/g,'_')}_${plan.userData.distance}.pdf`;doc.save(fileName);document.body.removeChild(loadingMsg);alert('¡PDF generado exitosamente!')}catch(error){console.error('Error PDF:',error);document.body.removeChild(loadingMsg);alert('Error al generar PDF: '+error.message)}},100)}function resetForm(){document.getElementById('trainingForm').reset();document.getElementById('prepRacesList').innerHTML='';prepRaceCounter=0;document.getElementById('imc').value='';document.getElementById('imcCategory').textContent='';document.getElementById('suggestedHrMax').textContent='--'}function updateDashboard(){const plans=getPlans();let filteredPlans=plans;currentUser&&'user'===currentUser.role&&(filteredPlans=plans.filter(p=>p.createdBy===currentUser.username));const uniqueUsers=new Set(plans.map(p=>p.userData.name)).size,totalWeeks=filteredPlans.reduce((sum,p)=>sum+p.totalWeeks,0),marathons=plans.filter(p=>'42K'===p.userData.distance).length;document.getElementById('stat-plans').textContent=filteredPlans.length;document.getElementById('stat-users').textContent=uniqueUsers;document.getElementById('stat-weeks').textContent=totalWeeks;document.getElementById('stat-marathons').textContent=marathons}window.addEventListener('resize',function(){const sidebar=document.getElementById('sidebar');window.innerWidth>768&&sidebar.classList.remove('show')});window.onclick=function(event){const modal=document.getElementById('planDetailModal');event.target===modal&&closeModal()};
</script>
</body>
</html>