F1N2G6 commited on
Commit
b26ff21
·
verified ·
1 Parent(s): cbde90d

Create README.md

Browse files
Files changed (1) hide show
  1. README.md +802 -0
README.md ADDED
@@ -0,0 +1,802 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: bigscience-openrail-m
3
+ datasets:
4
+ - google/mobile-actions
5
+ language:
6
+ - es
7
+ metrics:
8
+ - accuracy
9
+ base_model:
10
+ - zai-org/GLM-4.7
11
+ pipeline_tag: text-classification
12
+ ---
13
+ <!DOCTYPE html>
14
+ <html lang="es" data-color-scheme="light">
15
+ <head>
16
+ <meta charset="UTF-8" />
17
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
18
+ <title>Planificador Mensual de Hábitos</title>
19
+ <style>
20
+ /* ===== PERPLEXITY DESIGN SYSTEM (recortado a lo necesario) ===== */
21
+ :root {
22
+ --color-white: rgba(255, 255, 255, 1);
23
+ --color-cream-50: rgba(252, 252, 249, 1);
24
+ --color-cream-100: rgba(255, 255, 253, 1);
25
+ --color-gray-200: rgba(245, 245, 245, 1);
26
+ --color-gray-300: rgba(167, 169, 169, 1);
27
+ --color-gray-400: rgba(119, 124, 124, 1);
28
+ --color-slate-500: rgba(98, 108, 113, 1);
29
+ --color-brown-600: rgba(94, 82, 64, 1);
30
+ --color-charcoal-700: rgba(31, 33, 33, 1);
31
+ --color-charcoal-800: rgba(38, 40, 40, 1);
32
+ --color-slate-900: rgba(19, 52, 59, 1);
33
+ --color-teal-300: rgba(50, 184, 198, 1);
34
+ --color-teal-400: rgba(45, 166, 178, 1);
35
+ --color-teal-500: rgba(33, 128, 141, 1);
36
+ --color-teal-600: rgba(29, 116, 128, 1);
37
+ --color-teal-700: rgba(26, 104, 115, 1);
38
+ --color-red-400: rgba(255, 84, 89, 1);
39
+ --color-orange-400: rgba(230, 129, 97, 1);
40
+
41
+ --color-brown-600-rgb: 94, 82, 64;
42
+ --color-teal-500-rgb: 33, 128, 141;
43
+ --color-slate-900-rgb: 19, 52, 59;
44
+ --color-slate-500-rgb: 98, 108, 113;
45
+
46
+ --color-background: var(--color-cream-50);
47
+ --color-surface: var(--color-cream-100);
48
+ --color-text: var(--color-slate-900);
49
+ --color-text-secondary: var(--color-slate-500);
50
+ --color-primary: var(--color-teal-500);
51
+ --color-primary-hover: var(--color-teal-600);
52
+ --color-primary-active: var(--color-teal-700);
53
+ --color-secondary: rgba(var(--color-brown-600-rgb), 0.12);
54
+ --color-secondary-hover: rgba(var(--color-brown-600-rgb), 0.2);
55
+ --color-secondary-active: rgba(var(--color-brown-600-rgb), 0.25);
56
+ --color-border: rgba(var(--color-brown-600-rgb), 0.2);
57
+ --color-btn-primary-text: var(--color-cream-50);
58
+ --color-card-border: rgba(var(--color-brown-600-rgb), 0.12);
59
+ --color-card-border-inner: rgba(var(--color-brown-600-rgb), 0.12);
60
+ --color-focus-ring: rgba(var(--color-teal-500-rgb), 0.4);
61
+
62
+ --font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
63
+ --font-family-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
64
+ --font-size-sm: 12px;
65
+ --font-size-base: 14px;
66
+ --font-size-lg: 16px;
67
+ --font-size-2xl: 20px;
68
+ --font-weight-medium: 500;
69
+ --font-weight-semibold: 550;
70
+
71
+ --space-4: 4px;
72
+ --space-8: 8px;
73
+ --space-12: 12px;
74
+ --space-16: 16px;
75
+ --space-20: 20px;
76
+ --space-24: 24px;
77
+ --space-32: 32px;
78
+
79
+ --radius-sm: 6px;
80
+ --radius-base: 8px;
81
+ --radius-lg: 12px;
82
+
83
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02);
84
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.04),
85
+ 0 2px 4px -1px rgba(0, 0, 0, 0.02);
86
+
87
+ --duration-fast: 150ms;
88
+ --duration-normal: 250ms;
89
+ --ease-standard: cubic-bezier(0.16, 1, 0.3, 1);
90
+ }
91
+
92
+ html {
93
+ font-size: var(--font-size-base);
94
+ font-family: var(--font-family-base);
95
+ color: var(--color-text);
96
+ background-color: var(--color-background);
97
+ -webkit-font-smoothing: antialiased;
98
+ box-sizing: border-box;
99
+ }
100
+ *, *::before, *::after { box-sizing: inherit; }
101
+ body { margin: 0; }
102
+
103
+ h1, h2, h3 {
104
+ margin: 0;
105
+ font-weight: var(--font-weight-semibold);
106
+ }
107
+ h1 { font-size: 24px; }
108
+ h2 { font-size: 20px; }
109
+ h3 { font-size: 16px; }
110
+
111
+ .container {
112
+ max-width: 1200px;
113
+ margin: 0 auto;
114
+ padding: var(--space-16);
115
+ }
116
+
117
+ .card {
118
+ background-color: var(--color-surface);
119
+ border-radius: var(--radius-lg);
120
+ border: 1px solid var(--color-card-border);
121
+ box-shadow: var(--shadow-sm);
122
+ padding: var(--space-16);
123
+ margin-bottom: var(--space-16);
124
+ }
125
+
126
+ .btn {
127
+ display: inline-flex;
128
+ align-items: center;
129
+ justify-content: center;
130
+ padding: var(--space-8) var(--space-16);
131
+ border-radius: var(--radius-base);
132
+ font-size: var(--font-size-base);
133
+ font-weight: var(--font-weight-medium);
134
+ cursor: pointer;
135
+ border: none;
136
+ transition: all var(--duration-normal) var(--ease-standard);
137
+ }
138
+ .btn-primary {
139
+ background: var(--color-primary);
140
+ color: var(--color-btn-primary-text);
141
+ }
142
+ .btn-primary:hover { background: var(--color-primary-hover); }
143
+ .btn-secondary {
144
+ background: var(--color-secondary);
145
+ color: var(--color-text);
146
+ }
147
+ .btn-secondary:hover { background: var(--color-secondary-hover); }
148
+
149
+ .form-group { margin-bottom: var(--space-12); }
150
+ .form-label {
151
+ display: block;
152
+ margin-bottom: var(--space-4);
153
+ font-size: var(--font-size-sm);
154
+ color: var(--color-text-secondary);
155
+ }
156
+ .form-control {
157
+ width: 100%;
158
+ padding: var(--space-8) var(--space-12);
159
+ border-radius: var(--radius-base);
160
+ border: 1px solid var(--color-border);
161
+ background-color: var(--color-surface);
162
+ font-size: var(--font-size-base);
163
+ }
164
+ .form-control:focus {
165
+ outline: 2px solid var(--color-primary);
166
+ outline-offset: 1px;
167
+ }
168
+ textarea.form-control {
169
+ min-height: 120px;
170
+ resize: vertical;
171
+ }
172
+
173
+ .layout-main {
174
+ display: grid;
175
+ grid-template-columns: 260px minmax(260px, 1fr) 300px;
176
+ gap: var(--space-16);
177
+ }
178
+ @media (max-width: 1024px) {
179
+ .layout-main {
180
+ grid-template-columns: 1fr;
181
+ }
182
+ }
183
+
184
+ .habit-list-item {
185
+ display: grid;
186
+ grid-template-columns: auto 1fr auto;
187
+ gap: var(--space-8);
188
+ align-items: center;
189
+ padding: var(--space-8);
190
+ border-radius: var(--radius-base);
191
+ border: 1px solid rgba(0,0,0,0.03);
192
+ margin-bottom: var(--space-8);
193
+ background-color: rgba(0,0,0,0.01);
194
+ }
195
+ .habit-number {
196
+ font-weight: var(--font-weight-semibold);
197
+ font-size: var(--font-size-sm);
198
+ color: var(--color-text-secondary);
199
+ width: 24px;
200
+ text-align: right;
201
+ }
202
+ .habit-name-input {
203
+ border-radius: var(--radius-sm);
204
+ border: 1px solid var(--color-border);
205
+ padding: var(--space-4) var(--space-8);
206
+ font-size: var(--font-size-sm);
207
+ width: 100%;
208
+ }
209
+ .habit-meta {
210
+ display: flex;
211
+ flex-direction: column;
212
+ gap: var(--space-4);
213
+ align-items: flex-end;
214
+ }
215
+ .habit-toggle-row {
216
+ display: flex;
217
+ align-items: center;
218
+ gap: var(--space-4);
219
+ font-size: var(--font-size-sm);
220
+ }
221
+ .habit-toggle-row input[type="checkbox"] {
222
+ width: 16px;
223
+ height: 16px;
224
+ }
225
+ .habit-color-input {
226
+ width: 24px;
227
+ height: 24px;
228
+ padding: 0;
229
+ border-radius: var(--radius-sm);
230
+ border: 1px solid var(--color-border);
231
+ background: transparent;
232
+ cursor: pointer;
233
+ }
234
+
235
+ .month-header {
236
+ display: flex;
237
+ flex-wrap: wrap;
238
+ align-items: center;
239
+ gap: var(--space-12);
240
+ margin-bottom: var(--space-16);
241
+ }
242
+ .month-header > * {
243
+ flex: 0 0 auto;
244
+ }
245
+ .month-header-title {
246
+ font-size: var(--font-size-2xl);
247
+ font-weight: var(--font-weight-semibold);
248
+ }
249
+
250
+ .month-select-row {
251
+ display: flex;
252
+ gap: var(--space-8);
253
+ flex-wrap: wrap;
254
+ margin-bottom: var(--space-12);
255
+ }
256
+
257
+ .month-list {
258
+ display: flex;
259
+ flex-wrap: wrap;
260
+ gap: var(--space-8);
261
+ }
262
+ .month-pill {
263
+ padding: var(--space-4) var(--space-12);
264
+ border-radius: 999px;
265
+ border: 1px solid var(--color-border);
266
+ font-size: var(--font-size-sm);
267
+ cursor: pointer;
268
+ background-color: var(--color-surface);
269
+ }
270
+ .month-pill.active {
271
+ background-color: var(--color-primary);
272
+ color: var(--color-btn-primary-text);
273
+ border-color: var(--color-primary);
274
+ }
275
+
276
+ /* ===== CÍRCULO MENSUAL ===== */
277
+ .circle-container-card {
278
+ display: flex;
279
+ flex-direction: column;
280
+ align-items: center;
281
+ justify-content: center;
282
+ }
283
+ .circle-wrapper {
284
+ position: relative;
285
+ width: min(420px, 90vw);
286
+ height: min(420px, 90vw);
287
+ }
288
+ .circle-svg {
289
+ width: 100%;
290
+ height: 100%;
291
+ display: block;
292
+ }
293
+ .circle-legend {
294
+ margin-top: var(--space-12);
295
+ display: flex;
296
+ flex-wrap: wrap;
297
+ gap: var(--space-8);
298
+ justify-content: center;
299
+ font-size: var(--font-size-sm);
300
+ }
301
+ .circle-legend-item {
302
+ display: inline-flex;
303
+ align-items: center;
304
+ gap: var(--space-4);
305
+ padding: 2px 8px;
306
+ border-radius: 999px;
307
+ background-color: rgba(0,0,0,0.02);
308
+ }
309
+ .circle-legend-color {
310
+ width: 10px;
311
+ height: 10px;
312
+ border-radius: 999px;
313
+ border: 1px solid rgba(0,0,0,0.1);
314
+ }
315
+
316
+ .circle-center-label {
317
+ position: absolute;
318
+ top: 50%;
319
+ left: 50%;
320
+ transform: translate(-50%, -50%);
321
+ text-align: center;
322
+ font-size: var(--font-size-sm);
323
+ pointer-events: none;
324
+ }
325
+
326
+ /* Tooltip simple para día/hábito */
327
+ .tooltip {
328
+ position: fixed;
329
+ background: var(--color-charcoal-800);
330
+ color: white;
331
+ padding: 4px 8px;
332
+ border-radius: 4px;
333
+ font-size: 11px;
334
+ pointer-events: none;
335
+ z-index: 1000;
336
+ display: none;
337
+ white-space: nowrap;
338
+ }
339
+ </style>
340
+ </head>
341
+ <body>
342
+ <div class="container">
343
+ <h1>Planificador mensual de hábitos</h1>
344
+
345
+ <!-- Selección de mes -->
346
+ <div class="card">
347
+ <div class="month-select-row">
348
+ <div class="form-group" style="min-width: 160px;">
349
+ <label class="form-label" for="select-mes-num">Mes</label>
350
+ <select id="select-mes-num" class="form-control">
351
+ <option value="1">01 - Enero</option>
352
+ <option value="2">02 - Febrero</option>
353
+ <option value="3">03 - Marzo</option>
354
+ <option value="4">04 - Abril</option>
355
+ <option value="5">05 - Mayo</option>
356
+ <option value="6">06 - Junio</option>
357
+ <option value="7">07 - Julio</option>
358
+ <option value="8">08 - Agosto</option>
359
+ <option value="9">09 - Septiembre</option>
360
+ <option value="10">10 - Octubre</option>
361
+ <option value="11">11 - Noviembre</option>
362
+ <option value="12">12 - Diciembre</option>
363
+ </select>
364
+ </div>
365
+ <div class="form-group" style="min-width: 120px;">
366
+ <label class="form-label" for="input-anio">Año</label>
367
+ <input type="number" id="input-anio" class="form-control" value="2026" />
368
+ </div>
369
+ <div class="form-group" style="align-self: flex-end;">
370
+ <button id="btn-nuevo-mes" class="btn btn-primary">+ Nuevo mes</button>
371
+ </div>
372
+ </div>
373
+ <div class="form-group">
374
+ <label class="form-label">Meses creados</label>
375
+ <div id="lista-meses" class="month-list"></div>
376
+ </div>
377
+ </div>
378
+
379
+ <!-- Pantalla principal Mes -->
380
+ <div class="card">
381
+ <div id="mes-activo-header" class="month-header" style="margin-bottom: 0;">
382
+ <div class="month-header-title" id="titulo-mes-visual">Sin mes seleccionado</div>
383
+ </div>
384
+ <div class="layout-main" style="margin-top: var(--space-16);">
385
+ <!-- Zona izquierda: hábitos -->
386
+ <div>
387
+ <h3 style="margin-bottom: var(--space-8);">Hábitos (1–8)</h3>
388
+ <div id="lista-habitos"></div>
389
+ </div>
390
+
391
+ <!-- Zona central: círculo mensual -->
392
+ <div class="circle-container-card">
393
+ <div class="circle-wrapper">
394
+ <svg id="habit-circle" class="circle-svg" viewBox="0 0 400 400"></svg>
395
+ <div class="circle-center-label" id="circle-center-label"></div>
396
+ </div>
397
+ <div class="circle-legend" id="circle-legend"></div>
398
+ </div>
399
+
400
+ <!-- Zona derecha: observaciones -->
401
+ <div>
402
+ <h3 style="margin-bottom: var(--space-8);">Observaciones</h3>
403
+ <textarea id="textarea-observaciones" class="form-control" placeholder="Notas, reflexiones, objetivos del mes..."></textarea>
404
+ </div>
405
+ </div>
406
+ </div>
407
+ </div>
408
+
409
+ <div id="tooltip" class="tooltip"></div>
410
+
411
+ <script>
412
+ // =====================================
413
+ // MODELO EN MEMORIA (Mes, HabitoMes, RegistroDia)
414
+ // =====================================
415
+
416
+ function uuid() {
417
+ return crypto.randomUUID
418
+ ? crypto.randomUUID()
419
+ : "xxxx-4xxx-yxxx-xxxx".replace(/[xy]/g, function (c) {
420
+ var r = (Math.random() * 16) | 0,
421
+ v = c === "x" ? r : (r & 0x3) | 0x8;
422
+ return v.toString(16);
423
+ });
424
+ }
425
+
426
+ /**
427
+ * Mes: {
428
+ * id_mes,
429
+ * mes (1-12),
430
+ * anio,
431
+ * titulo_mes,
432
+ * observaciones
433
+ * }
434
+ *
435
+ * HabitoMes: {
436
+ * id_habito_mes,
437
+ * id_mes,
438
+ * numero_habito (1-8),
439
+ * nombre_habito,
440
+ * activo (bool),
441
+ * color (string)
442
+ * }
443
+ *
444
+ * RegistroDia: {
445
+ * id_registro,
446
+ * id_mes,
447
+ * numero_habito,
448
+ * dia (1-31),
449
+ * cumplido (bool)
450
+ * }
451
+ */
452
+
453
+ const DB = {
454
+ meses: [],
455
+ habitosMes: [],
456
+ registrosDia: []
457
+ };
458
+
459
+ let mesActivoId = null;
460
+
461
+ const COLOR_POR_DEFECTO = [
462
+ "#3B82F6", // azul
463
+ "#10B981", // verde
464
+ "#F59E0B", // amarillo
465
+ "#EF4444", // rojo
466
+ "#8B5CF6", // violeta
467
+ "#EC4899", // rosa
468
+ "#14B8A6", // teal
469
+ "#6366F1" // índigo
470
+ ];
471
+
472
+ function crearMes(mesNumero, anio) {
473
+ const id_mes = uuid();
474
+ const nombreMeses = [
475
+ "Enero","Febrero","Marzo","Abril","Mayo","Junio",
476
+ "Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"
477
+ ];
478
+ const titulo_mes = `${nombreMeses[mesNumero - 1]} ${anio}`;
479
+
480
+ const nuevoMes = {
481
+ id_mes,
482
+ mes: mesNumero,
483
+ anio,
484
+ titulo_mes,
485
+ observaciones: ""
486
+ };
487
+ DB.meses.push(nuevoMes);
488
+
489
+ // Crear 8 filas en HabitoMes
490
+ for (let i = 1; i <= 8; i++) {
491
+ DB.habitosMes.push({
492
+ id_habito_mes: uuid(),
493
+ id_mes,
494
+ numero_habito: i,
495
+ nombre_habito: `Hábito ${i}`,
496
+ activo: false,
497
+ color: COLOR_POR_DEFECTO[i - 1] || "#999999"
498
+ });
499
+ }
500
+
501
+ return nuevoMes;
502
+ }
503
+
504
+ function obtenerMesesOrdenados() {
505
+ return [...DB.meses].sort((a, b) => {
506
+ if (a.anio !== b.anio) return a.anio - b.anio;
507
+ return a.mes - b.mes;
508
+ });
509
+ }
510
+
511
+ function getMesActivo() {
512
+ if (!mesActivoId) return null;
513
+ return DB.meses.find(m => m.id_mes === mesActivoId) || null;
514
+ }
515
+
516
+ function getHabitosDeMes(id_mes) {
517
+ return DB.habitosMes
518
+ .filter(h => h.id_mes === id_mes)
519
+ .sort((a, b) => a.numero_habito - b.numero_habito);
520
+ }
521
+
522
+ function getRegistroDia(id_mes, numero_habito, dia) {
523
+ return DB.registrosDia.find(
524
+ r => r.id_mes === id_mes && r.numero_habito === numero_habito && r.dia === dia
525
+ ) || null;
526
+ }
527
+
528
+ function toggleRegistroDia(id_mes, numero_habito, dia) {
529
+ const existente = getRegistroDia(id_mes, numero_habito, dia);
530
+ if (!existente) {
531
+ const nuevo = {
532
+ id_registro: uuid(),
533
+ id_mes,
534
+ numero_habito,
535
+ dia,
536
+ cumplido: true
537
+ };
538
+ DB.registrosDia.push(nuevo);
539
+ } else {
540
+ existente.cumplido = !existente.cumplido;
541
+ }
542
+ }
543
+
544
+ function getDiasDelMes(mes, anio) {
545
+ return new Date(anio, mes, 0).getDate();
546
+ }
547
+
548
+ // =====================================
549
+ // RENDER UI
550
+ // =====================================
551
+
552
+ const listaMesesEl = document.getElementById("lista-meses");
553
+ const selectMesNumEl = document.getElementById("select-mes-num");
554
+ const inputAnioEl = document.getElementById("input-anio");
555
+ const btnNuevoMesEl = document.getElementById("btn-nuevo-mes");
556
+
557
+ const tituloMesVisualEl = document.getElementById("titulo-mes-visual");
558
+ const listaHabitosEl = document.getElementById("lista-habitos");
559
+ const textareaObsEl = document.getElementById("textarea-observaciones");
560
+
561
+ const svgCircleEl = document.getElementById("habit-circle");
562
+ const circleCenterLabelEl = document.getElementById("circle-center-label");
563
+ const circleLegendEl = document.getElementById("circle-legend");
564
+ const tooltipEl = document.getElementById("tooltip");
565
+
566
+ function renderMeses() {
567
+ listaMesesEl.innerHTML = "";
568
+ const meses = obtenerMesesOrdenados();
569
+ if (meses.length === 0) {
570
+ const span = document.createElement("span");
571
+ span.style.fontSize = "12px";
572
+ span.style.color = "var(--color-text-secondary)";
573
+ span.textContent = "Aún no hay meses creados. Usa \"+ Nuevo mes\" para empezar.";
574
+ listaMesesEl.appendChild(span);
575
+ return;
576
+ }
577
+
578
+ meses.forEach(m => {
579
+ const btn = document.createElement("button");
580
+ btn.className = "month-pill" + (m.id_mes === mesActivoId ? " active" : "");
581
+ btn.textContent = m.titulo_mes;
582
+ btn.addEventListener("click", () => {
583
+ mesActivoId = m.id_mes;
584
+ renderMeses();
585
+ renderPantallaMes();
586
+ });
587
+ listaMesesEl.appendChild(btn);
588
+ });
589
+ }
590
+
591
+ function renderPantallaMes() {
592
+ const mes = getMesActivo();
593
+ if (!mes) {
594
+ tituloMesVisualEl.textContent = "Sin mes seleccionado";
595
+ listaHabitosEl.innerHTML = "";
596
+ textareaObsEl.value = "";
597
+ svgCircleEl.innerHTML = "";
598
+ circleCenterLabelEl.textContent = "";
599
+ circleLegendEl.innerHTML = "";
600
+ return;
601
+ }
602
+
603
+ tituloMesVisualEl.textContent = mes.titulo_mes;
604
+ textareaObsEl.value = mes.observaciones || "";
605
+ renderHabitos(mes);
606
+ renderCircle(mes);
607
+ }
608
+
609
+ function renderHabitos(mes) {
610
+ const habitos = getHabitosDeMes(mes.id_mes);
611
+ listaHabitosEl.innerHTML = "";
612
+
613
+ habitos.forEach(h => {
614
+ const row = document.createElement("div");
615
+ row.className = "habit-list-item";
616
+
617
+ const numEl = document.createElement("div");
618
+ numEl.className = "habit-number";
619
+ numEl.textContent = h.numero_habito;
620
+
621
+ const inputNombre = document.createElement("input");
622
+ inputNombre.className = "habit-name-input";
623
+ inputNombre.value = h.nombre_habito || "";
624
+ inputNombre.placeholder = `Hábito ${h.numero_habito}`;
625
+ inputNombre.addEventListener("input", () => {
626
+ h.nombre_habito = inputNombre.value;
627
+ renderCircle(mes);
628
+ });
629
+
630
+ const meta = document.createElement("div");
631
+ meta.className = "habit-meta";
632
+
633
+ const toggleRow = document.createElement("div");
634
+ toggleRow.className = "habit-toggle-row";
635
+ const chk = document.createElement("input");
636
+ chk.type = "checkbox";
637
+ chk.checked = !!h.activo;
638
+ chk.addEventListener("change", () => {
639
+ h.activo = chk.checked;
640
+ renderCircle(mes);
641
+ });
642
+ const lbl = document.createElement("span");
643
+ lbl.textContent = "Activo";
644
+ toggleRow.appendChild(chk);
645
+ toggleRow.appendChild(lbl);
646
+
647
+ const colorInput = document.createElement("input");
648
+ colorInput.type = "color";
649
+ colorInput.className = "habit-color-input";
650
+ colorInput.value = h.color || "#999999";
651
+ colorInput.addEventListener("input", () => {
652
+ h.color = colorInput.value;
653
+ renderCircle(mes);
654
+ });
655
+
656
+ meta.appendChild(toggleRow);
657
+ meta.appendChild(colorInput);
658
+
659
+ row.appendChild(numEl);
660
+ row.appendChild(inputNombre);
661
+ row.appendChild(meta);
662
+
663
+ listaHabitosEl.appendChild(row);
664
+ });
665
+ }
666
+
667
+ function renderCircle(mes) {
668
+ const habitos = getHabitosDeMes(mes.id_mes).filter(h => h.activo);
669
+ const diasMes = getDiasDelMes(mes.mes, mes.anio);
670
+
671
+ svgCircleEl.innerHTML = "";
672
+ circleLegendEl.innerHTML = "";
673
+ if (habitos.length === 0) {
674
+ circleCenterLabelEl.textContent = "Activa algún hábito\npara ver el círculo";
675
+ return;
676
+ } else {
677
+ circleCenterLabelEl.textContent = `${diasMes} días`;
678
+ }
679
+
680
+ const centerX = 200;
681
+ const centerY = 200;
682
+ const innerRadius = 60;
683
+ const bandWidth = 20;
684
+ const gapBetweenBands = 4;
685
+ const totalDays = 31; // estructura de 31 segmentos, pero se "grisearán" los días > diasMes
686
+ const angleStep = (2 * Math.PI) / totalDays;
687
+
688
+ habitos.forEach((habito, index) => {
689
+ const r0 = innerRadius + index * (bandWidth + gapBetweenBands);
690
+ const r1 = r0 + bandWidth;
691
+
692
+ // Leyenda
693
+ const legItem = document.createElement("div");
694
+ legItem.className = "circle-legend-item";
695
+ const col = document.createElement("div");
696
+ col.className = "circle-legend-color";
697
+ col.style.backgroundColor = habito.color || "#999999";
698
+ const txt = document.createElement("span");
699
+ txt.textContent = habito.nombre_habito || `Hábito ${habito.numero_habito}`;
700
+ legItem.appendChild(col);
701
+ legItem.appendChild(txt);
702
+ circleLegendEl.appendChild(legItem);
703
+
704
+ for (let dia = 1; dia <= totalDays; dia++) {
705
+ const startAngle = (dia - 1) * angleStep - Math.PI / 2;
706
+ const endAngle = dia * angleStep - Math.PI / 2;
707
+
708
+ const x0 = centerX + r0 * Math.cos(startAngle);
709
+ const y0 = centerY + r0 * Math.sin(startAngle);
710
+ const x1 = centerX + r1 * Math.cos(startAngle);
711
+ const y1 = centerY + r1 * Math.sin(startAngle);
712
+ const x2 = centerX + r1 * Math.cos(endAngle);
713
+ const y2 = centerY + r1 * Math.sin(endAngle);
714
+ const x3 = centerX + r0 * Math.cos(endAngle);
715
+ const y3 = centerY + r0 * Math.sin(endAngle);
716
+
717
+ const largeArcFlag = 0;
718
+
719
+ const pathData = [
720
+ "M", x0, y0,
721
+ "L", x1, y1,
722
+ "A", r1, r1, 0, largeArcFlag, 1, x2, y2,
723
+ "L", x3, y3,
724
+ "A", r0, r0, 0, largeArcFlag, 0, x0, y0,
725
+ "Z"
726
+ ].join(" ");
727
+
728
+ const registro = getRegistroDia(mes.id_mes, habito.numero_habito, dia);
729
+ const isCumplido = registro && registro.cumplido;
730
+
731
+ const pathEl = document.createElementNS("http://www.w3.org/2000/svg", "path");
732
+ pathEl.setAttribute("d", pathData);
733
+
734
+ if (dia > diasMes) {
735
+ pathEl.setAttribute("fill", "#f1f5f9");
736
+ pathEl.setAttribute("stroke", "#e5e7eb");
737
+ pathEl.setAttribute("stroke-width", "0.5");
738
+ pathEl.setAttribute("fill-opacity", "0.6");
739
+ } else if (isCumplido) {
740
+ pathEl.setAttribute("fill", habito.color || "#22c55e");
741
+ pathEl.setAttribute("fill-opacity", "0.9");
742
+ } else {
743
+ pathEl.setAttribute("fill", "#ffffff");
744
+ pathEl.setAttribute("fill-opacity", "0.9");
745
+ pathEl.setAttribute("stroke", "#e2e8f0");
746
+ pathEl.setAttribute("stroke-width", "0.5");
747
+ }
748
+
749
+ pathEl.style.cursor = dia <= diasMes ? "pointer" : "default";
750
+
751
+ pathEl.addEventListener("click", () => {
752
+ if (dia > diasMes) return;
753
+ toggleRegistroDia(mes.id_mes, habito.numero_habito, dia);
754
+ renderCircle(mes);
755
+ });
756
+
757
+ const habitName = habito.nombre_habito || `Hábito ${habito.numero_habito}`;
758
+ pathEl.addEventListener("mousemove", (ev) => {
759
+ tooltipEl.style.display = "block";
760
+ tooltipEl.textContent = `${habitName} · Día ${dia}`;
761
+ tooltipEl.style.left = ev.clientX + 12 + "px";
762
+ tooltipEl.style.top = ev.clientY + 12 + "px";
763
+ });
764
+ pathEl.addEventListener("mouseleave", () => {
765
+ tooltipEl.style.display = "none";
766
+ });
767
+
768
+ svgCircleEl.appendChild(pathEl);
769
+ }
770
+ });
771
+ }
772
+
773
+ // Observaciones
774
+ textareaObsEl.addEventListener("input", () => {
775
+ const mes = getMesActivo();
776
+ if (!mes) return;
777
+ mes.observaciones = textareaObsEl.value;
778
+ });
779
+
780
+ // Botón "Nuevo mes"
781
+ btnNuevoMesEl.addEventListener("click", () => {
782
+ const mesNum = parseInt(selectMesNumEl.value, 10);
783
+ const anio = parseInt(inputAnioEl.value, 10) || new Date().getFullYear();
784
+ const nuevoMes = crearMes(mesNum, anio);
785
+ mesActivoId = nuevoMes.id_mes;
786
+ renderMeses();
787
+ renderPantallaMes();
788
+ });
789
+
790
+ // Estado inicial: crear un mes por defecto (mes actual)
791
+ (function init() {
792
+ const hoy = new Date();
793
+ const mesNum = hoy.getMonth() + 1;
794
+ const anio = hoy.getFullYear();
795
+ const mesInicial = crearMes(mesNum, anio);
796
+ mesActivoId = mesInicial.id_mes;
797
+ renderMeses();
798
+ renderPantallaMes();
799
+ })();
800
+ </script>
801
+ </body>
802
+ </html>