Spaces:
Configuration error
Configuration error
File size: 62,062 Bytes
9f87fac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 | <!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>
|