Tu Nombre commited on
Commit
683e056
·
1 Parent(s): f4d8f37

Actualización: Cambio a modelo Llama 3.1 8B

Browse files
Files changed (1) hide show
  1. index.html +742 -66
index.html CHANGED
@@ -1,74 +1,750 @@
1
  <!DOCTYPE html>
2
  <html lang="es">
3
  <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>IA Web Re-Designer</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <style> body { overflow: hidden; } </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  </head>
10
- <body class="bg-slate-50">
11
-
12
- <div id="setup-panel" class="absolute inset-0 flex items-center justify-center z-50 p-4">
13
- <div class="bg-white p-8 md:p-12 rounded-[40px] shadow-2xl shadow-blue-100 w-full max-w-md text-center border border-slate-100">
14
- <div class="w-16 h-16 bg-emerald-500 rounded-2xl mx-auto mb-6 flex items-center justify-center text-white text-3xl font-bold shadow-lg shadow-emerald-200">🎨</div>
15
- <h1 class="text-3xl font-bold text-slate-900 mb-2">Web Re-Designer</h1>
16
- <p class="text-slate-500 mb-8 font-medium">Clona y cambia de color cualquier web</p>
17
-
18
- <input type="text" id="url" placeholder="URL original (Ej: https://...)" class="w-full p-4 rounded-2xl bg-slate-50 border border-slate-200 mb-4 outline-none focus:ring-2 focus:ring-emerald-500">
19
- <input type="text" id="nombre" placeholder="Nuevo Nombre del Negocio" class="w-full p-4 rounded-2xl bg-slate-50 border border-slate-200 mb-4 outline-none focus:ring-2 focus:ring-emerald-500">
20
- <input type="text" id="color" placeholder="Color (blue, red, emerald, purple...)" class="w-full p-4 rounded-2xl bg-slate-50 border border-slate-200 mb-6 outline-none focus:ring-2 focus:ring-emerald-500">
21
-
22
- <button onclick="generarWeb()" id="btn" class="w-full bg-emerald-500 text-white py-4 rounded-2xl font-bold shadow-xl shadow-emerald-200 hover:bg-emerald-600 transition-all">
23
- CLONAR Y REDISEÑAR
24
- </button>
25
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  </div>
 
27
 
28
- <iframe id="preview" class="w-full h-screen border-none hidden absolute top-0 left-0 z-10"></iframe>
29
-
30
- <script>
31
- async function generarWeb() {
32
- const btn = document.getElementById('btn');
33
- const panel = document.getElementById('setup-panel');
34
- const preview = document.getElementById('preview');
35
-
36
- const data = {
37
- url: document.getElementById('url').value,
38
- nuevo_nombre: document.getElementById('nombre').value,
39
- color_primario: document.getElementById('color').value
40
- };
41
-
42
- if(!data.url || !data.nuevo_nombre) return alert("Faltan datos por rellenar");
43
-
44
- btn.innerHTML = "⏳ Escaneando y programando...";
45
- btn.disabled = true;
46
-
47
- try {
48
- const res = await fetch('/api/generate', {
49
- method: 'POST',
50
- headers: {'Content-Type': 'application/json'},
51
- body: JSON.stringify(data)
52
- });
53
- const result = await res.json();
54
-
55
- if(result.status === "success") {
56
- // Ocultar panel y mostrar web a pantalla completa
57
- panel.style.display = 'none';
58
- preview.style.display = 'block';
59
- preview.srcdoc = result.html;
60
- } else {
61
- alert("Error al clonar la web.");
62
- btn.innerHTML = "CLONAR Y REDISEÑAR";
63
- btn.disabled = false;
64
- }
65
- } catch(e) {
66
- alert("Error de conexión con la IA.");
67
- btn.innerHTML = "CLONAR Y REDISEÑAR";
68
- btn.disabled = false;
69
- }
70
- }
71
- </script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  </body>
73
  </html>
74
-
 
1
  <!DOCTYPE html>
2
  <html lang="es">
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Vigilante Paciente</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Exo+2:wght@300;600;800&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --bg: #030a0e;
11
+ --panel: #0a1a22;
12
+ --border: #0e3a50;
13
+ --cyan: #00e5ff;
14
+ --green: #00ff88;
15
+ --red: #ff2244;
16
+ --yellow: #ffcc00;
17
+ --text: #c8e8f0;
18
+ --mono: 'Share Tech Mono', monospace;
19
+ --display: 'Exo 2', sans-serif;
20
+ }
21
+
22
+ * { margin: 0; padding: 0; box-sizing: border-box; }
23
+
24
+ body {
25
+ background: var(--bg);
26
+ color: var(--text);
27
+ font-family: var(--display);
28
+ min-height: 100vh;
29
+ overflow-x: hidden;
30
+ }
31
+
32
+ /* Fondo animado */
33
+ body::before {
34
+ content: '';
35
+ position: fixed;
36
+ inset: 0;
37
+ background:
38
+ radial-gradient(ellipse at 20% 50%, rgba(0,229,255,0.04) 0%, transparent 60%),
39
+ radial-gradient(ellipse at 80% 20%, rgba(0,255,136,0.03) 0%, transparent 50%);
40
+ pointer-events: none;
41
+ z-index: 0;
42
+ }
43
+
44
+ .grid-bg {
45
+ position: fixed;
46
+ inset: 0;
47
+ background-image:
48
+ linear-gradient(rgba(0,229,255,0.04) 1px, transparent 1px),
49
+ linear-gradient(90deg, rgba(0,229,255,0.04) 1px, transparent 1px);
50
+ background-size: 40px 40px;
51
+ pointer-events: none;
52
+ z-index: 0;
53
+ }
54
+
55
+ .container {
56
+ position: relative;
57
+ z-index: 1;
58
+ max-width: 480px;
59
+ margin: 0 auto;
60
+ padding: 16px;
61
+ }
62
+
63
+ /* Header */
64
+ .header {
65
+ text-align: center;
66
+ padding: 20px 0 16px;
67
+ border-bottom: 1px solid var(--border);
68
+ margin-bottom: 16px;
69
+ }
70
+
71
+ .header h1 {
72
+ font-size: 1.4rem;
73
+ font-weight: 800;
74
+ letter-spacing: 0.15em;
75
+ color: var(--cyan);
76
+ text-shadow: 0 0 20px rgba(0,229,255,0.5);
77
+ text-transform: uppercase;
78
+ }
79
+
80
+ .header p {
81
+ font-family: var(--mono);
82
+ font-size: 0.65rem;
83
+ color: #4a7a8a;
84
+ margin-top: 4px;
85
+ letter-spacing: 0.1em;
86
+ }
87
+
88
+ /* Estado principal */
89
+ .estado-box {
90
+ border: 2px solid var(--border);
91
+ border-radius: 8px;
92
+ padding: 16px;
93
+ margin-bottom: 14px;
94
+ background: var(--panel);
95
+ position: relative;
96
+ overflow: hidden;
97
+ transition: border-color 0.3s, box-shadow 0.3s;
98
+ }
99
+
100
+ .estado-box.normal { border-color: var(--green); box-shadow: 0 0 20px rgba(0,255,136,0.1); }
101
+ .estado-box.critico { border-color: var(--red); box-shadow: 0 0 30px rgba(255,34,68,0.3); animation: pulseRed 0.8s infinite; }
102
+ .estado-box.esperando { border-color: var(--border); }
103
+
104
+ @keyframes pulseRed {
105
+ 0%, 100% { box-shadow: 0 0 20px rgba(255,34,68,0.3); }
106
+ 50% { box-shadow: 0 0 50px rgba(255,34,68,0.7); }
107
+ }
108
+
109
+ .estado-label {
110
+ font-family: var(--mono);
111
+ font-size: 0.6rem;
112
+ letter-spacing: 0.2em;
113
+ color: #4a7a8a;
114
+ text-transform: uppercase;
115
+ margin-bottom: 6px;
116
+ }
117
+
118
+ .estado-valor {
119
+ font-size: 2rem;
120
+ font-weight: 800;
121
+ letter-spacing: 0.1em;
122
+ text-transform: uppercase;
123
+ }
124
+
125
+ .estado-valor.normal { color: var(--green); text-shadow: 0 0 15px rgba(0,255,136,0.5); }
126
+ .estado-valor.critico { color: var(--red); text-shadow: 0 0 15px rgba(255,34,68,0.7); }
127
+ .estado-valor.esperando { color: #4a7a8a; }
128
+
129
+ .motivo-texto {
130
+ font-family: var(--mono);
131
+ font-size: 0.75rem;
132
+ color: var(--yellow);
133
+ margin-top: 8px;
134
+ min-height: 16px;
135
+ }
136
+
137
+ /* Cámara */
138
+ .camara-container {
139
+ border: 1px solid var(--border);
140
+ border-radius: 8px;
141
+ overflow: hidden;
142
+ margin-bottom: 14px;
143
+ position: relative;
144
+ background: #000;
145
+ aspect-ratio: 4/3;
146
+ }
147
+
148
+ #video {
149
+ width: 100%;
150
+ height: 100%;
151
+ object-fit: cover;
152
+ display: block;
153
+ transform: scaleX(-1);
154
+ }
155
+
156
+ .camara-overlay {
157
+ position: absolute;
158
+ inset: 0;
159
+ pointer-events: none;
160
+ }
161
+
162
+ /* Esquinas decorativas */
163
+ .corner {
164
+ position: absolute;
165
+ width: 20px;
166
+ height: 20px;
167
+ border-color: var(--cyan);
168
+ border-style: solid;
169
+ opacity: 0.7;
170
+ }
171
+ .corner.tl { top: 8px; left: 8px; border-width: 2px 0 0 2px; }
172
+ .corner.tr { top: 8px; right: 8px; border-width: 2px 2px 0 0; }
173
+ .corner.bl { bottom: 8px; left: 8px; border-width: 0 0 2px 2px; }
174
+ .corner.br { bottom: 8px; right: 8px; border-width: 0 2px 2px 0; }
175
+
176
+ .camara-label {
177
+ position: absolute;
178
+ top: 10px;
179
+ left: 50%;
180
+ transform: translateX(-50%);
181
+ font-family: var(--mono);
182
+ font-size: 0.6rem;
183
+ color: var(--cyan);
184
+ letter-spacing: 0.15em;
185
+ opacity: 0.8;
186
+ }
187
+
188
+ .rec-dot {
189
+ position: absolute;
190
+ top: 10px;
191
+ right: 32px;
192
+ width: 8px;
193
+ height: 8px;
194
+ background: var(--red);
195
+ border-radius: 50%;
196
+ animation: blink 1.2s infinite;
197
+ }
198
+
199
+ @keyframes blink {
200
+ 0%, 100% { opacity: 1; }
201
+ 50% { opacity: 0.2; }
202
+ }
203
+
204
+ /* Panel de constantes */
205
+ .constantes {
206
+ display: grid;
207
+ grid-template-columns: 1fr 1fr;
208
+ gap: 10px;
209
+ margin-bottom: 14px;
210
+ }
211
+
212
+ .constante-card {
213
+ background: var(--panel);
214
+ border: 1px solid var(--border);
215
+ border-radius: 8px;
216
+ padding: 12px;
217
+ }
218
+
219
+ .constante-label {
220
+ font-family: var(--mono);
221
+ font-size: 0.55rem;
222
+ color: #4a7a8a;
223
+ letter-spacing: 0.15em;
224
+ text-transform: uppercase;
225
+ margin-bottom: 4px;
226
+ }
227
+
228
+ .constante-valor {
229
+ font-family: var(--mono);
230
+ font-size: 1.6rem;
231
+ font-weight: bold;
232
+ color: var(--cyan);
233
+ }
234
+
235
+ .constante-unidad {
236
+ font-size: 0.6rem;
237
+ color: #4a7a8a;
238
+ margin-left: 2px;
239
+ }
240
+
241
+ /* Sonido */
242
+ .sonido-card {
243
+ background: var(--panel);
244
+ border: 1px solid var(--border);
245
+ border-radius: 8px;
246
+ padding: 12px;
247
+ margin-bottom: 14px;
248
+ }
249
+
250
+ .sonido-label {
251
+ font-family: var(--mono);
252
+ font-size: 0.55rem;
253
+ color: #4a7a8a;
254
+ letter-spacing: 0.15em;
255
+ text-transform: uppercase;
256
+ margin-bottom: 8px;
257
+ }
258
+
259
+ .sonido-barra-container {
260
+ display: flex;
261
+ align-items: center;
262
+ gap: 10px;
263
+ }
264
+
265
+ .sonido-barra {
266
+ flex: 1;
267
+ height: 6px;
268
+ background: var(--border);
269
+ border-radius: 3px;
270
+ overflow: hidden;
271
+ }
272
+
273
+ .sonido-fill {
274
+ height: 100%;
275
+ background: linear-gradient(90deg, var(--green), var(--cyan));
276
+ border-radius: 3px;
277
+ transition: width 0.15s;
278
+ width: 0%;
279
+ }
280
+
281
+ .sonido-estado {
282
+ font-family: var(--mono);
283
+ font-size: 0.7rem;
284
+ color: var(--green);
285
+ min-width: 60px;
286
+ text-align: right;
287
+ }
288
+
289
+ /* Nombre */
290
+ .nombre-input {
291
+ width: 100%;
292
+ background: var(--panel);
293
+ border: 1px solid var(--border);
294
+ border-radius: 8px;
295
+ padding: 10px 14px;
296
+ color: var(--text);
297
+ font-family: var(--mono);
298
+ font-size: 0.85rem;
299
+ margin-bottom: 14px;
300
+ outline: none;
301
+ transition: border-color 0.2s;
302
+ }
303
+
304
+ .nombre-input:focus { border-color: var(--cyan); }
305
+ .nombre-input::placeholder { color: #2a4a5a; }
306
+
307
+ /* Botones */
308
+ .botones {
309
+ display: grid;
310
+ grid-template-columns: 1fr 1fr;
311
+ gap: 10px;
312
+ margin-bottom: 14px;
313
+ }
314
+
315
+ .btn {
316
+ padding: 14px;
317
+ border: none;
318
+ border-radius: 8px;
319
+ font-family: var(--display);
320
+ font-size: 0.8rem;
321
+ font-weight: 600;
322
+ letter-spacing: 0.1em;
323
+ text-transform: uppercase;
324
+ cursor: pointer;
325
+ transition: all 0.2s;
326
+ }
327
+
328
+ .btn-iniciar {
329
+ background: var(--cyan);
330
+ color: var(--bg);
331
+ }
332
+
333
+ .btn-iniciar:hover { background: #00ccee; transform: translateY(-1px); }
334
+ .btn-iniciar:disabled { background: #0a3040; color: #2a5060; cursor: not-allowed; transform: none; }
335
+
336
+ .btn-parar {
337
+ background: transparent;
338
+ color: var(--red);
339
+ border: 1px solid var(--red);
340
+ }
341
+
342
+ .btn-parar:hover { background: rgba(255,34,68,0.1); }
343
+ .btn-parar:disabled { opacity: 0.3; cursor: not-allowed; }
344
+
345
+ /* Log */
346
+ .log-box {
347
+ background: var(--panel);
348
+ border: 1px solid var(--border);
349
+ border-radius: 8px;
350
+ padding: 12px;
351
+ margin-bottom: 14px;
352
+ max-height: 120px;
353
+ overflow-y: auto;
354
+ }
355
+
356
+ .log-label {
357
+ font-family: var(--mono);
358
+ font-size: 0.55rem;
359
+ color: #4a7a8a;
360
+ letter-spacing: 0.15em;
361
+ text-transform: uppercase;
362
+ margin-bottom: 8px;
363
+ }
364
+
365
+ .log-entry {
366
+ font-family: var(--mono);
367
+ font-size: 0.7rem;
368
+ color: #5a8a9a;
369
+ padding: 2px 0;
370
+ border-bottom: 1px solid rgba(14,58,80,0.4);
371
+ }
372
+
373
+ .log-entry:last-child { border-bottom: none; }
374
+ .log-entry.critico { color: var(--red); }
375
+ .log-entry.normal { color: var(--green); }
376
+ .log-entry.info { color: var(--cyan); }
377
+
378
+ /* Alarma overlay */
379
+ .alarma-overlay {
380
+ display: none;
381
+ position: fixed;
382
+ inset: 0;
383
+ background: rgba(255,34,68,0.15);
384
+ z-index: 100;
385
+ backdrop-filter: blur(2px);
386
+ animation: flashRed 0.5s infinite;
387
+ }
388
+
389
+ .alarma-overlay.activa { display: flex; align-items: center; justify-content: center; }
390
+
391
+ @keyframes flashRed {
392
+ 0%, 100% { background: rgba(255,34,68,0.1); }
393
+ 50% { background: rgba(255,34,68,0.3); }
394
+ }
395
+
396
+ .alarma-card {
397
+ background: #1a0508;
398
+ border: 2px solid var(--red);
399
+ border-radius: 12px;
400
+ padding: 30px;
401
+ text-align: center;
402
+ max-width: 300px;
403
+ box-shadow: 0 0 60px rgba(255,34,68,0.5);
404
+ }
405
+
406
+ .alarma-icono { font-size: 3rem; margin-bottom: 10px; }
407
+
408
+ .alarma-titulo {
409
+ font-size: 1.5rem;
410
+ font-weight: 800;
411
+ color: var(--red);
412
+ letter-spacing: 0.15em;
413
+ text-transform: uppercase;
414
+ margin-bottom: 10px;
415
+ }
416
+
417
+ .alarma-motivo {
418
+ font-family: var(--mono);
419
+ font-size: 0.8rem;
420
+ color: var(--text);
421
+ margin-bottom: 20px;
422
+ line-height: 1.5;
423
+ }
424
+
425
+ .btn-silenciar {
426
+ background: var(--red);
427
+ color: white;
428
+ border: none;
429
+ border-radius: 6px;
430
+ padding: 12px 24px;
431
+ font-family: var(--display);
432
+ font-weight: 600;
433
+ font-size: 0.85rem;
434
+ cursor: pointer;
435
+ letter-spacing: 0.1em;
436
+ text-transform: uppercase;
437
+ }
438
+
439
+ /* Scrollbar */
440
+ ::-webkit-scrollbar { width: 4px; }
441
+ ::-webkit-scrollbar-track { background: var(--bg); }
442
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
443
+ </style>
444
  </head>
445
+ <body>
446
+
447
+ <div class="grid-bg"></div>
448
+
449
+ <!-- Alarma overlay -->
450
+ <div class="alarma-overlay" id="alarmaOverlay">
451
+ <div class="alarma-card">
452
+ <div class="alarma-icono">🚨</div>
453
+ <div class="alarma-titulo">¡ALERTA CRÍTICA!</div>
454
+ <div class="alarma-motivo" id="alarmaMotivo">Detectado problema en el paciente</div>
455
+ <button class="btn-silenciar" onclick="silenciarAlarma()">SILENCIAR</button>
456
+ </div>
457
+ </div>
458
+
459
+ <div class="container">
460
+
461
+ <div class="header">
462
+ <h1>⚕ Vigilante Paciente</h1>
463
+ <p>SISTEMA DE MONITORIZACIÓN SIN CONTACTO · v1.0</p>
464
+ </div>
465
+
466
+ <!-- Estado IA -->
467
+ <div class="estado-box esperando" id="estadoBox">
468
+ <div class="estado-label">// ESTADO DEL SISTEMA</div>
469
+ <div class="estado-valor esperando" id="estadoValor">EN ESPERA</div>
470
+ <div class="motivo-texto" id="motivoTexto"></div>
471
+ </div>
472
+
473
+ <!-- Cámara -->
474
+ <div class="camara-container">
475
+ <video id="video" autoplay playsinline muted></video>
476
+ <div class="camara-overlay">
477
+ <div class="corner tl"></div>
478
+ <div class="corner tr"></div>
479
+ <div class="corner bl"></div>
480
+ <div class="corner br"></div>
481
+ <div class="camara-label">CÁMARA ACTIVA</div>
482
+ <div class="rec-dot" id="recDot" style="display:none"></div>
483
+ </div>
484
+ </div>
485
+
486
+ <!-- Constantes -->
487
+ <div class="constantes">
488
+ <div class="constante-card">
489
+ <div class="constante-label">// Pulso estimado</div>
490
+ <div class="constante-valor" id="pulsoValor">--<span class="constante-unidad">BPM</span></div>
491
+ </div>
492
+ <div class="constante-card">
493
+ <div class="constante-label">// Oxígeno estimado</div>
494
+ <div class="constante-valor" id="oxigenoValor">--<span class="constante-unidad">%</span></div>
495
+ </div>
496
+ </div>
497
+
498
+ <!-- Sonido -->
499
+ <div class="sonido-card">
500
+ <div class="sonido-label">// Nivel de sonido ambiente</div>
501
+ <div class="sonido-barra-container">
502
+ <div class="sonido-barra">
503
+ <div class="sonido-fill" id="sonidoFill"></div>
504
+ </div>
505
+ <div class="sonido-estado" id="sonidoEstado">SILENCIO</div>
506
+ </div>
507
+ </div>
508
+
509
+ <!-- Nombre -->
510
+ <input type="text" class="nombre-input" id="nombreInput" placeholder="Nombre del paciente..." value="Paciente">
511
+
512
+ <!-- Botones -->
513
+ <div class="botones">
514
+ <button class="btn btn-iniciar" id="btnIniciar" onclick="iniciarVigilancia()">▶ Iniciar</button>
515
+ <button class="btn btn-parar" id="btnParar" onclick="pararVigilancia()" disabled>■ Parar</button>
516
+ </div>
517
+
518
+ <!-- Log -->
519
+ <div class="log-box">
520
+ <div class="log-label">// Registro de eventos</div>
521
+ <div id="logContainer">
522
+ <div class="log-entry info">Sistema iniciado. Listo para vigilar.</div>
523
  </div>
524
+ </div>
525
 
526
+ </div>
527
+
528
+ <script>
529
+ // ─── CONFIGURACIÓN ───────────────────────────────────────────
530
+ const BACKEND_URL = "https://cristobal299-iaweb.hf.space/vigilar";
531
+ const INTERVALO_SEGUNDOS = 15;
532
+
533
+ // ─── ESTADO GLOBAL ───────────────────────────────────────────
534
+ let vigilanciaActiva = false;
535
+ let intervalId = null;
536
+ let audioContext = null;
537
+ let analyser = null;
538
+ let microfonoStream = null;
539
+ let camaraStream = null;
540
+ let nivelSonido = 0;
541
+ let alarmaAudio = null;
542
+
543
+ // ─── INICIAR VIGILANCIA ──────────────────────────────────────
544
+ async function iniciarVigilancia() {
545
+ try {
546
+ // Cámara
547
+ camaraStream = await navigator.mediaDevices.getUserMedia({
548
+ video: { facingMode: 'user', width: 640, height: 480 },
549
+ audio: false
550
+ });
551
+ document.getElementById('video').srcObject = camaraStream;
552
+
553
+ // Micrófono
554
+ microfonoStream = await navigator.mediaDevices.getUserMedia({ audio: true });
555
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
556
+ const source = audioContext.createMediaStreamSource(microfonoStream);
557
+ analyser = audioContext.createAnalyser();
558
+ analyser.fftSize = 256;
559
+ source.connect(analyser);
560
+ iniciarVisualizacionSonido();
561
+
562
+ vigilanciaActiva = true;
563
+ document.getElementById('btnIniciar').disabled = true;
564
+ document.getElementById('btnParar').disabled = false;
565
+ document.getElementById('recDot').style.display = 'block';
566
+
567
+ agregarLog('Vigilancia iniciada ✓', 'info');
568
+
569
+ // Primera llamada inmediata
570
+ await bucleVigilancia();
571
+
572
+ // Bucle cada X segundos
573
+ intervalId = setInterval(bucleVigilancia, INTERVALO_SEGUNDOS * 1000);
574
+
575
+ } catch (err) {
576
+ agregarLog('Error: ' + err.message, 'critico');
577
+ alert('Necesito acceso a cámara y micrófono para funcionar.');
578
+ }
579
+ }
580
+
581
+ // ─── PARAR VIGILANCIA ────────────────────────────────────────
582
+ function pararVigilancia() {
583
+ vigilanciaActiva = false;
584
+ clearInterval(intervalId);
585
+
586
+ if (camaraStream) camaraStream.getTracks().forEach(t => t.stop());
587
+ if (microfonoStream) microfonoStream.getTracks().forEach(t => t.stop());
588
+ if (audioContext) audioContext.close();
589
+
590
+ document.getElementById('video').srcObject = null;
591
+ document.getElementById('btnIniciar').disabled = false;
592
+ document.getElementById('btnParar').disabled = true;
593
+ document.getElementById('recDot').style.display = 'none';
594
+ document.getElementById('sonidoFill').style.width = '0%';
595
+ document.getElementById('sonidoEstado').textContent = 'SILENCIO';
596
+
597
+ actualizarEstado('EN ESPERA', 'esperando', '');
598
+ agregarLog('Vigilancia detenida', 'info');
599
+ }
600
+
601
+ // ─── BUCLE PRINCIPAL ─────────────────────────────────────────
602
+ async function bucleVigilancia() {
603
+ if (!vigilanciaActiva) return;
604
+
605
+ const nombre = document.getElementById('nombreInput').value || 'Paciente';
606
+
607
+ // Simular pulso con variación realista (paciente en reposo)
608
+ const pulso = Math.floor(65 + Math.random() * 20 + (nivelSonido > 60 ? 15 : 0));
609
+ const oxigeno = Math.floor(94 + Math.random() * 4);
610
+
611
+ // Clasificar sonido
612
+ let descripcionSonido = "silencio";
613
+ if (nivelSonido > 75) descripcionSonido = "sonido muy alto detectado, posible grito o golpe";
614
+ else if (nivelSonido > 55) descripcionSonido = "ruido elevado, posible quejido o movimiento brusco";
615
+ else if (nivelSonido > 30) descripcionSonido = "ruido leve de fondo";
616
+
617
+ // Actualizar UI
618
+ document.getElementById('pulsoValor').innerHTML = pulso + '<span class="constante-unidad">BPM</span>';
619
+ document.getElementById('oxigenoValor').innerHTML = oxigeno + '<span class="constante-unidad">%</span>';
620
+
621
+ agregarLog(`Enviando → Pulso:${pulso} O2:${oxigeno}% Sonido:"${descripcionSonido}"`, 'info');
622
+
623
+ try {
624
+ const resp = await fetch(BACKEND_URL, {
625
+ method: 'POST',
626
+ headers: { 'Content-Type': 'application/json' },
627
+ body: JSON.stringify({
628
+ nombre: nombre,
629
+ pulso: pulso,
630
+ oxigeno: oxigeno,
631
+ sonido_ambiente: descripcionSonido,
632
+ movimiento_respiracion: "regular"
633
+ })
634
+ });
635
+
636
+ const texto = await resp.text();
637
+ let data;
638
+
639
+ try {
640
+ // A veces viene como string JSON dentro de string
641
+ data = JSON.parse(texto);
642
+ if (typeof data === 'string') data = JSON.parse(data);
643
+ } catch(e) {
644
+ agregarLog('Error parseando respuesta', 'critico');
645
+ return;
646
+ }
647
+
648
+ if (data.estado === 'CRITICO') {
649
+ actualizarEstado('⚠ CRÍTICO', 'critico', data.motivo || '');
650
+ agregarLog('CRÍTICO: ' + (data.motivo || ''), 'critico');
651
+ dispararAlarma(data.motivo || 'Estado crítico detectado');
652
+ } else {
653
+ actualizarEstado('NORMAL', 'normal', '');
654
+ agregarLog('Estado: NORMAL ✓', 'normal');
655
+ }
656
+
657
+ } catch (err) {
658
+ agregarLog('Error de conexión: ' + err.message, 'critico');
659
+ }
660
+ }
661
+
662
+ // ─── VISUALIZACIÓN SONIDO ────────────────────────────────────
663
+ function iniciarVisualizacionSonido() {
664
+ const dataArray = new Uint8Array(analyser.frequencyBinCount);
665
+
666
+ function actualizar() {
667
+ if (!vigilanciaActiva) return;
668
+ analyser.getByteFrequencyData(dataArray);
669
+ const promedio = dataArray.reduce((a, b) => a + b) / dataArray.length;
670
+ nivelSonido = promedio;
671
+
672
+ const porcentaje = Math.min(100, (promedio / 128) * 100);
673
+ document.getElementById('sonidoFill').style.width = porcentaje + '%';
674
+
675
+ let estado = 'SILENCIO';
676
+ let color = 'var(--green)';
677
+ if (promedio > 75) { estado = 'ALERTA'; color = 'var(--red)'; }
678
+ else if (promedio > 55) { estado = 'RUIDO'; color = 'var(--yellow)'; }
679
+ else if (promedio > 30) { estado = 'LEVE'; color = 'var(--cyan)'; }
680
+
681
+ document.getElementById('sonidoEstado').textContent = estado;
682
+ document.getElementById('sonidoEstado').style.color = color;
683
+
684
+ requestAnimationFrame(actualizar);
685
+ }
686
+ actualizar();
687
+ }
688
+
689
+ // ─── ESTADO UI ───────────────────────────────────────────────
690
+ function actualizarEstado(texto, clase, motivo) {
691
+ const box = document.getElementById('estadoBox');
692
+ const valor = document.getElementById('estadoValor');
693
+ const motivoEl = document.getElementById('motivoTexto');
694
+
695
+ box.className = 'estado-box ' + clase;
696
+ valor.className = 'estado-valor ' + clase;
697
+ valor.textContent = texto;
698
+ motivoEl.textContent = motivo;
699
+ }
700
+
701
+ // ─── LOG ─────────────────────────────────────────────────────
702
+ function agregarLog(mensaje, tipo) {
703
+ const container = document.getElementById('logContainer');
704
+ const entry = document.createElement('div');
705
+ entry.className = 'log-entry ' + (tipo || '');
706
+ const hora = new Date().toLocaleTimeString('es', {hour:'2-digit', minute:'2-digit', second:'2-digit'});
707
+ entry.textContent = `[${hora}] ${mensaje}`;
708
+ container.appendChild(entry);
709
+ container.scrollTop = container.scrollHeight;
710
+
711
+ // Máximo 50 entradas
712
+ while (container.children.length > 50) {
713
+ container.removeChild(container.firstChild);
714
+ }
715
+ }
716
+
717
+ // ─── ALARMA ──────────────────────────────────────────────────
718
+ function dispararAlarma(motivo) {
719
+ document.getElementById('alarmaMotivo').textContent = motivo;
720
+ document.getElementById('alarmaOverlay').classList.add('activa');
721
+
722
+ // Sonido de alarma con Web Audio API
723
+ try {
724
+ const ctx = new AudioContext();
725
+ function beep(freq, start, dur) {
726
+ const osc = ctx.createOscillator();
727
+ const gain = ctx.createGain();
728
+ osc.connect(gain);
729
+ gain.connect(ctx.destination);
730
+ osc.frequency.value = freq;
731
+ osc.type = 'square';
732
+ gain.gain.setValueAtTime(0.3, ctx.currentTime + start);
733
+ gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + start + dur);
734
+ osc.start(ctx.currentTime + start);
735
+ osc.stop(ctx.currentTime + start + dur);
736
+ }
737
+ // Patrón de alarma
738
+ for (let i = 0; i < 6; i++) {
739
+ beep(880, i * 0.4, 0.3);
740
+ beep(660, i * 0.4 + 0.2, 0.15);
741
+ }
742
+ } catch(e) {}
743
+ }
744
+
745
+ function silenciarAlarma() {
746
+ document.getElementById('alarmaOverlay').classList.remove('activa');
747
+ }
748
+ </script>
749
  </body>
750
  </html>