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>