Spaces:
Sleeping
Sleeping
| <html lang="es"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Autoevaluación de Ciberseguridad Operativa - Doctor Linux</title> | |
| <style> | |
| body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Inter,Arial,sans-serif;background:#f5f6f5;margin:0;padding:0} | |
| .dlx-wrap{max-width:960px;margin:auto;padding:16px} | |
| .dlx-card{background:#fff;border:1px solid #e6ebef;border-radius:16px;box-shadow:0 6px 24px rgba(0,0,0,.06);padding:22px;margin:14px 0} | |
| .dlx-title{font-size:28px;margin:0 0 4px;color:#214424} | |
| .dlx-sub{color:#5c6b5f;margin-bottom:16px} | |
| .dlx-progress{height:10px;background:#eff4f0;border-radius:999px;overflow:hidden;margin:10px 0 18px} | |
| .dlx-bar{height:100%;width:0;background:#7fbf7f;transition:width .3s} | |
| .dlx-q h4{margin:0 0 6px;font-size:18px;display:flex;align-items:flex-start;gap:8px} | |
| .dlx-options{display:flex;gap:10px;flex-wrap:wrap;margin:6px 0 12px} | |
| .dlx-pill{border:1px solid #d7e1d9;padding:8px 12px;border-radius:999px;cursor:pointer} | |
| .dlx-pill input{display:none} | |
| .dlx-pill.active{background:#214424;color:#fff;border-color:#214424} | |
| .dlx-nav{display:flex;gap:10px;justify-content:space-between;margin-top:8px} | |
| .dlx-btn{background:#214424;color:#fff;border:none;border-radius:10px;padding:12px 16px;cursor:pointer;transition:all 0.3s} | |
| .dlx-btn:hover{background:#1a361c} | |
| .dlx-btn[disabled]{opacity:.5;cursor:not-allowed} | |
| .dlx-meter{display:grid;grid-template-columns:1fr auto;gap:6px 10px;margin:8px 0} | |
| .dlx-badge{font-weight:600} | |
| .dlx-tag{display:inline-block;background:#eff4f0;border:1px solid #dfe9e2;border-radius:8px;padding:6px 10px;margin:6px 6px 0 0} | |
| .dlx-note{color:#6a786d} | |
| .dlx-score{font-size:40px;font-weight:800;color:#214424} | |
| .dlx-muted{color:#7b8b7f} | |
| /* Estilos para el botón de ayuda */ | |
| .help-btn { | |
| background: #214424; | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| width: 22px; | |
| height: 22px; | |
| font-size: 12px; | |
| font-weight: bold; | |
| cursor: help; | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| flex-shrink: 0; | |
| margin-top: 2px; | |
| } | |
| .help-btn:hover { | |
| background: #1a361c; | |
| transform: scale(1.1); | |
| } | |
| /* Modal de ayuda */ | |
| .help-modal { | |
| display: none; | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0,0,0,0.5); | |
| z-index: 1000; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .help-content { | |
| background: #fff; | |
| padding: 24px; | |
| border-radius: 16px; | |
| width: 90%; | |
| max-width: 500px; | |
| border: 2px solid #214424; | |
| max-height: 80vh; | |
| overflow-y: auto; | |
| box-shadow: 0 20px 60px rgba(0,0,0,0.2); | |
| } | |
| .help-content h4 { | |
| margin: 0 0 16px 0; | |
| color: #214424; | |
| font-size: 20px; | |
| border-bottom: 2px solid #eff4f0; | |
| padding-bottom: 8px; | |
| } | |
| .help-content .close-btn { | |
| background: #214424; | |
| color: white; | |
| border: none; | |
| padding: 10px 20px; | |
| border-radius: 10px; | |
| cursor: pointer; | |
| margin-top: 16px; | |
| font-weight: 600; | |
| } | |
| .help-content .close-btn:hover { | |
| background: #1a361c; | |
| } | |
| .help-example { | |
| background: #f8f9f8; | |
| border-left: 4px solid #214424; | |
| padding: 12px 16px; | |
| margin: 12px 0; | |
| border-radius: 0 8px 8px 0; | |
| } | |
| .help-example h5 { | |
| margin: 0 0 8px 0; | |
| color: #214424; | |
| font-size: 14px; | |
| } | |
| .help-example code { | |
| background: #e9efe9; | |
| padding: 4px 8px; | |
| border-radius: 4px; | |
| font-family: monospace; | |
| font-size: 12px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="dlx-wrap" id="dlxApp"> | |
| <div class="dlx-card"> | |
| <h2 class="dlx-title">Autoevaluación de Ciberseguridad Operativa</h2> | |
| <div class="dlx-sub">Responde con honestidad. Obtendrás tu <b>puntaje</b>, los <b>riesgos clave</b> y un plan sugerido de mejora.</div> | |
| <div class="dlx-progress"><div class="dlx-bar" id="dlxBar"></div></div> | |
| <div class="dlx-note dlx-muted" id="dlxStepNote"></div> | |
| </div> | |
| <div id="dlxSteps"></div> | |
| <div class="dlx-card" id="dlxNavBox"> | |
| <div class="dlx-nav"> | |
| <button class="dlx-btn" id="prevBtn">← Anterior</button> | |
| <button class="dlx-btn" id="nextBtn">Siguiente →</button> | |
| </div> | |
| </div> | |
| <div class="dlx-card" id="dlxResult" style="display:none"> | |
| <h3 class="dlx-title">Tu diagnóstico</h3> | |
| <div class="dlx-sub">Este resultado es orientativo. Si quieres un informe firmado por ingeniería, solicita el <b>diagnóstico gratuito</b>.</div> | |
| <div style="display:flex;gap:24px;align-items:center;flex-wrap:wrap"> | |
| <div> | |
| <div class="dlx-muted">Puntaje global</div> | |
| <div class="dlx-score" id="dlxScore">—%</div> | |
| <div class="dlx-tag" id="dlxLabel">—</div> | |
| </div> | |
| <div style="flex:1;min-width:260px"> | |
| <div class="dlx-progress"><div class="dlx-bar" id="dlxBarFinal" style="background:#3aa655"></div></div> | |
| <div class="dlx-note">≈ Mayor a 80%: postura sólida. 60–79%: revisar. Menor a 60%: riesgo elevado.</div> | |
| </div> | |
| </div> | |
| <div class="dlx-card" style="margin-top:16px"> | |
| <h4 style="margin:0 0 8px">Detalle por dominio</h4> | |
| <div id="dlxBreakdown" class="dlx-meter"></div> | |
| </div> | |
| <div class="dlx-card"> | |
| <h4 style="margin:0 0 8px">Recomendaciones inmediatas</h4> | |
| <div id="dlxRecs"></div> | |
| <div class="dlx-note" style="margin-top:8px">Tip: prioriza los dominios con menor puntaje. Podemos ayudarte a cerrar brechas con <b>hardening, firewalls, backups 3-2-1 y monitoreo 24/7</b>.</div> | |
| </div> | |
| <!-- BOTONES DE ACCIÓN --> | |
| <div class="dlx-nav" style="margin-top:25px"> | |
| <button class="dlx-btn" onclick="window.location.reload()">🔄 Nueva Evaluación</button> | |
| <button class="dlx-btn" id="analyzeBtn">🤖 Análisis con IA</button> | |
| <button class="dlx-btn" onclick="downloadReport()" style="background:#3aa655">📄 Descargar Informe</button> | |
| </div> | |
| <div id="iaBox" class="dlx-card" style="display:none;margin-top:16px"> | |
| <h4>Análisis generado por IA</h4> | |
| <pre id="iaOutput" style="white-space:pre-wrap;"></pre> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Modal de ayuda --> | |
| <div id="helpModal" class="help-modal"> | |
| <div class="help-content"> | |
| <h4 id="helpTitle">Ayuda</h4> | |
| <div id="helpText"></div> | |
| <button class="close-btn" id="closeHelp">Cerrar</button> | |
| </div> | |
| </div> | |
| <script> | |
| /* === CONFIGURACIÓN INICIAL === */ | |
| const DOMAINS = [ | |
| { id:'perimetro', name:'Perímetro / Firewall', qs:[ | |
| {q:'¿Usas un firewall dedicado (Mikrotik/OPNsense/etc.) con reglas mínimas por servicio?', help:'Un firewall dedicado es un dispositivo/software que filtra todo el tráfico entre tu red e Internet. "Reglas mínimas" significa permitir SOLO los puertos y servicios estrictamente necesarios para operar, denegando todo lo demás por defecto.'}, | |
| {q:'¿Tienes listas de acceso/geo-bloqueo/IPS/DoS activas?', help:'Listas de acceso: Bloquean IPs maliciosas conocidas. Geo-bloqueo: Restringe tráfico de países de alto riesgo. IPS: Detecta y bloquea patrones de ataque en tiempo real. Protección DoS: Limita conexiones para prevenir saturación.'}, | |
| {q:'¿Los servicios expuestos están detrás de NAT o Port-Forward controlado?', help:'NAT oculta las IPs internas de tu red. Port-Forward controlado significa redirigir SOLO puertos específicos necesarios (ej: puerto 443 para web), no rangos completos.'}, | |
| {q:'¿Hay políticas VPN de acceso remoto con 2FA?', help:'VPN crea un túnel seguro para acceso remoto. 2FA (autenticación de dos factores) añade una capa extra de seguridad además de la contraseña (código SMS, app authenticator, etc.).'} | |
| ]}, | |
| { id:'servers', name:'Servidores / Hardening', qs:[ | |
| {q:'¿Cuentas con hardening básico (SSH seguro, usuarios/roles, auditoría)?', help:'Hardening significa configurar el sistema de forma segura: SSH sin acceso root, solo autenticación por claves, usuarios con privilegios mínimos, y logs de auditoría activos.'}, | |
| {q:'¿Actualizaciones y parches aplicados regularmente?', help:'Aplicar actualizaciones de seguridad mensualmente para corregir vulnerabilidades conocidas en sistema operativo y aplicaciones.'}, | |
| {q:'¿Servicios innecesarios deshabilitados/controlados?', help:'Cada servicio activo es una posible puerta de entrada. Deshabilitar servicios como FTP, Telnet, servicios de impresión no usados, etc.'} | |
| ]}, | |
| { id:'backups', name:'Backups y Recuperación', qs:[ | |
| {q:'¿Aplicas la regla 3-2-1 (3 copias, 2 medios, 1 offsite)?', help:'3 copias totales de datos, en al menos 2 medios diferentes (discos, cinta, cloud), y 1 copia fuera del sitio físico principal.'}, | |
| {q:'¿Pruebas de restauración realizadas en los últimos 6 meses?', help:'Verificar periódicamente que los backups son recuperables restaurando datos de prueba en ambiente controlado.'}, | |
| {q:'¿Backups protegidos contra ransomware (inmutables/air-gap)?', help:'Backups inmutables no pueden ser modificados o eliminados. Air-gap significa almacenamiento físicamente desconectado de la red.'} | |
| ]}, | |
| { id:'monitor', name:'Monitoreo y Alertas', qs:[ | |
| {q:'¿Monitoreo centralizado (Zabbix/PRTG/Grafana) con alertas 24/7?', help:'Sistema que monitorea servidores, redes, servicios y envía alertas inmediatas cuando detecta problemas o anomalías.'}, | |
| {q:'¿Métricas revisadas mensualmente?', help:'Analizar regularmente rendimiento, capacidad, intentos de acceso fallidos, y otros indicadores de seguridad.'}, | |
| {q:'¿Alertas integradas con soporte?', help:'Las alertas críticas deben notificar automáticamente al personal técnico mediante email, SMS, Telegram, etc.'} | |
| ]}, | |
| { id:'network', name:'Red y Conectividad', qs:[ | |
| {q:'¿Segmentación VLANs para aislar servicios críticos?', help:'Dividir la red en segmentos lógicos separados para aislar servidores críticos, usuarios, e invitados.'}, | |
| {q:'¿Control de ancho de banda/Proxy/filtrado de contenidos?', help:'Limitar ancho de banda por servicio, filtrar contenido malicioso, y controlar acceso a internet mediante proxy.'}, | |
| {q:'¿Wi-Fi empresarial con WPA2-Enterprise?', help:'WPA2-Enterprise usa autenticación individual por usuario en lugar de contraseña compartida para toda la red WiFi.'} | |
| ]}, | |
| { id:'endpoints', name:'Usuarios / Estaciones', qs:[ | |
| {q:'¿Antimalware/EDR y políticas de actualización?', help:'EDR detecta comportamientos sospechosos. Políticas de actualización automática para software y antivirus.'}, | |
| {q:'¿Gestión de contraseñas y MFA?', help:'Password manager para contraseñas seguras y únicas. MFA requiere código adicional además de la contraseña.'}, | |
| {q:'¿Políticas de mínimo privilegio + bloqueo de USB?', help:'Usuarios solo tienen permisos necesarios para su trabajo. Bloqueo de puertos USB previene infecciones y fugas.'} | |
| ]}, | |
| { id:'webmail', name:'Web y Correo', qs:[ | |
| {q:'¿Sitios con HTTPS válido y cabeceras seguras?', help:'HTTPS encripta comunicación web. Cabeceras de seguridad previenen ataques como XSS, clickjacking, etc.'}, | |
| {q:'¿Correo con SPF, DKIM y DMARC publicados?', help:'Protocolos que previenen suplantación de identidad (spoofing) en correos electrónicos.'}, | |
| {q:'¿WAF/antispam/antiphishing activos?', help:'WAF protege aplicaciones web, antispam filtra correo no deseado, antiphishing detecta intentos de engaño.'} | |
| ]} | |
| ]; | |
| const SCALE = [ | |
| {label:'No',score:0}, | |
| {label:'Parcial',score:50}, | |
| {label:'Sí',score:100}, | |
| {label:'No sé',score:null} | |
| ]; | |
| const RECS = { | |
| perimetro:'Endurecer perímetro: reglas mínimo necesario, VPN 2FA, IDS/IPS y protección DoS.', | |
| servers:'Aplicar hardening, cuentas/roles y mantener parches al día.', | |
| backups:'Implementar 3-2-1 con pruebas de restauración y backups inmutables.', | |
| monitor:'Centralizar monitoreo (Zabbix/PRTG) y definir umbrales.', | |
| network:'Segregar VLANs, reforzar proxy y aislar Wi-Fi invitados.', | |
| endpoints:'Mínimo privilegio, EDR/antimalware y MFA.', | |
| webmail:'Forzar HTTPS, cabeceras OWASP, SPF+DKIM+DMARC y WAF.' | |
| }; | |
| let current = 0, answers = {}; | |
| const steps = document.getElementById('dlxSteps'), | |
| bar = document.getElementById('dlxBar'), | |
| note = document.getElementById('dlxStepNote'), | |
| prev = document.getElementById('prevBtn'), | |
| next = document.getElementById('nextBtn'), | |
| result = document.getElementById('dlxResult'), | |
| nav = document.getElementById('dlxNavBox'); | |
| /* === SISTEMA DE AYUDA === */ | |
| function showHelp(helpKey, questionText) { | |
| const modal = document.getElementById('helpModal'); | |
| const title = document.getElementById('helpTitle'); | |
| const content = document.getElementById('helpText'); | |
| // Encontrar la pregunta correspondiente para mostrar su ayuda | |
| let helpContent = ''; | |
| DOMAINS.forEach(domain => { | |
| domain.qs.forEach((item, i) => { | |
| const currentHelpKey = `${domain.id}-q${i}`; | |
| if (currentHelpKey === helpKey) { | |
| helpContent = item.help; | |
| } | |
| }); | |
| }); | |
| if (helpContent) { | |
| content.innerHTML = ` | |
| <h4>💡 Explicación Técnica</h4> | |
| <p><strong>Pregunta:</strong> ${questionText}</p> | |
| <div class="help-example"> | |
| <h5>🔍 Qué significa esto:</h5> | |
| <p>${helpContent}</p> | |
| </div> | |
| <div class="help-example"> | |
| <h5>🎯 Por qué es importante:</h5> | |
| <p>Esta medida protege contra amenazas específicas y es considerada una práctica esencial de seguridad.</p> | |
| </div> | |
| `; | |
| } else { | |
| content.innerHTML = ` | |
| <h4>💡 Ayuda Contextual</h4> | |
| <p><strong>Pregunta:</strong> ${questionText}</p> | |
| <p>Esta pregunta evalúa la implementación de controles de seguridad específicos para este dominio técnico.</p> | |
| `; | |
| } | |
| modal.style.display = 'flex'; | |
| } | |
| function closeHelp() { | |
| document.getElementById('helpModal').style.display = 'none'; | |
| } | |
| // Event listeners para el modal de ayuda | |
| document.getElementById('closeHelp').addEventListener('click', closeHelp); | |
| document.getElementById('helpModal').addEventListener('click', function(e) { | |
| if (e.target.id === 'helpModal') closeHelp(); | |
| }); | |
| /* === FUNCIONES PRINCIPALES === */ | |
| function render() { | |
| steps.innerHTML = ''; | |
| const d = DOMAINS[current]; | |
| note.innerHTML = `<b>${current+1}/${DOMAINS.length}</b> · ${d.name}`; | |
| const card = document.createElement('div'); | |
| card.className = 'dlx-card'; | |
| card.innerHTML = `<h3 class="dlx-title">${d.name}</h3>`; | |
| d.qs.forEach((item, i) => { | |
| const b = document.createElement('div'); | |
| b.className = 'dlx-q'; | |
| // Crear botón de ayuda con ID único | |
| const helpKey = `${d.id}-q${i}`; | |
| const helpBtn = `<button class="help-btn" onclick="showHelp('${helpKey}', '${item.q.replace(/'/g, "\\'")}')">?</button>`; | |
| b.innerHTML = `<h4>${item.q} ${helpBtn}</h4>`; | |
| const row = document.createElement('div'); | |
| row.className = 'dlx-options'; | |
| SCALE.forEach(opt => { | |
| const lbl = document.createElement('label'); | |
| lbl.className = 'dlx-pill'; | |
| lbl.textContent = opt.label; | |
| lbl.onclick = () => { | |
| row.querySelectorAll('.dlx-pill').forEach(p => p.classList.remove('active')); | |
| lbl.classList.add('active'); | |
| if (!answers[d.id]) answers[d.id] = []; | |
| answers[d.id][i] = opt.score; | |
| updateButtons(); | |
| }; | |
| if (answers[d.id] && answers[d.id][i] === opt.score) lbl.classList.add('active'); | |
| row.appendChild(lbl); | |
| }); | |
| b.appendChild(row); | |
| card.appendChild(b); | |
| }); | |
| steps.appendChild(card); | |
| bar.style.width = Math.round(current / DOMAINS.length * 100) + '%'; | |
| updateButtons(); | |
| } | |
| function allAnswered(i) { | |
| const d = DOMAINS[i]; | |
| return answers[d.id] && answers[d.id].length === d.qs.length && answers[d.id].every(v => v !== undefined); | |
| } | |
| function avg(a) { | |
| const nums = a.filter(v => typeof v === 'number' && isFinite(v)); | |
| if (nums.length === 0) return 0; | |
| return nums.reduce((x, y) => x + y, 0) / nums.length; | |
| } | |
| function updateButtons() { | |
| prev.disabled = current === 0; | |
| const last = current === DOMAINS.length - 1; | |
| next.textContent = last ? 'Ver resultado →' : 'Siguiente →'; | |
| next.disabled = !allAnswered(current); | |
| } | |
| prev.onclick = () => { if (current > 0) { current--; render(); } }; | |
| next.onclick = () => { | |
| if (current < DOMAINS.length - 1) { | |
| if (!allAnswered(current)) return; | |
| current++; render(); | |
| } else { | |
| if (!allAnswered(current)) return; | |
| showResult(); | |
| } | |
| }; | |
| function showResult() { | |
| bar.style.width = '100%'; | |
| nav.style.display = 'none'; | |
| steps.style.display = 'none'; | |
| note.style.display = 'none'; | |
| result.style.display = 'block'; | |
| let weighted = 0, total = 0; | |
| const breakdown = document.getElementById('dlxBreakdown'), | |
| recs = document.getElementById('dlxRecs'); | |
| breakdown.innerHTML = ''; | |
| recs.innerHTML = ''; | |
| DOMAINS.forEach(d => { | |
| const a = answers[d.id] ? avg(answers[d.id]) : 0; | |
| weighted += a; total += 100; | |
| const pct = Math.round(a); | |
| breakdown.innerHTML += `<div>${d.name}</div><div><b>${pct}%</b></div>`; | |
| if (pct < 75) { recs.innerHTML += `<div class="dlx-tag">• ${RECS[d.id]}</div>`; } | |
| }); | |
| const global = Math.round(weighted / DOMAINS.length); | |
| document.getElementById('dlxScore').textContent = global + '%'; | |
| document.getElementById('dlxBarFinal').style.width = global + '%'; | |
| const label = document.getElementById('dlxLabel'); | |
| if (global >= 80) { label.textContent = 'Nivel Sólido'; label.style.background = '#e7f7e7'; } | |
| else if (global >= 60) { label.textContent = 'Nivel Medio (revisar)'; label.style.background = '#fff6e1'; } | |
| else { label.textContent = 'Riesgo Alto'; label.style.background = '#ffe9e9'; } | |
| // Guardar datos para el análisis IA | |
| window.finalData = { | |
| overall: global, | |
| domains: DOMAINS.map(d => ({ | |
| name: d.name, | |
| score: Math.round(avg(answers[d.id])) | |
| })) | |
| }; | |
| } | |
| /* === DESCARGAR INFORME MEJORADO === */ | |
| function downloadReport() { | |
| const global = window.finalData.overall; | |
| const date = new Date().toLocaleDateString('es-ES'); | |
| // Crear contenido con formato profesional | |
| const content = ` | |
| ╔══════════════════════════════════════════════════╗ | |
| ║ INFORME DE CIBERSEGURIDAD ║ | |
| ║ DOCTOR LINUX ║ | |
| ╚══════════════════════════════════════════════════╝ | |
| 📅 Fecha de evaluación: ${date} | |
| 🎯 Puntaje Global: ${global}% | |
| ${global >= 80 ? '✅ POSTURA SÓLIDA - Controles robustos implementados' : | |
| global >= 60 ? '⚠️ MEJORAS NECESARIAS - Se requieren ajustes en controles' : | |
| '🚨 RIESGO ELEVADO - Atención inmediata requerida'} | |
| 📊 DETALLE POR DOMINIO TÉCNICO: | |
| ${window.finalData.domains.map(d => { | |
| const icon = d.score >= 80 ? '✅' : d.score >= 60 ? '⚠️ ' : '🚨'; | |
| return `${icon} ${d.name}: ${d.score}%`; | |
| }).join('\n')} | |
| 🛠️ RECOMENDACIONES PRIORITARIAS: | |
| 1. Revisar y endurecer configuración de firewall | |
| 2. Implementar estrategia de backups 3-2-1 con pruebas | |
| 3. Establecer monitoreo centralizado 24/7 | |
| 4. Aplicar hardening en servidores y estaciones | |
| 5. Implementar autenticación multifactor (MFA) | |
| 📈 PLAN DE ACCIÓN SUGERIDO: | |
| • SEMANA 1: Contención de riesgos críticos | |
| • SEMANA 2-4: Implementación controles básicos | |
| • MES 2-3: Mejora de madurez operacional | |
| • MES 4-6: Optimización y automatización | |
| ────────────────────────────────────────────────── | |
| Este es un informe automático generado por la | |
| herramienta de autodiagnóstico de Doctor Linux. | |
| Para un análisis técnico detallado y personalizado, | |
| contacte a nuestros especialistas: | |
| 🌐 www.doctorlinux.com | |
| 📧 gerencia@doctorlinux.com | |
| 📞 +57 315 340 7238 | |
| ────────────────────────────────────────────────── | |
| `; | |
| // Crear y descargar archivo | |
| const blob = new Blob([content], { type: 'text/plain; charset=utf-8' }); | |
| const url = URL.createObjectURL(blob); | |
| const a = document.createElement('a'); | |
| a.href = url; | |
| a.download = `informe-ciberseguridad-doctor-linux-${date.replace(/\//g, '-')}.txt`; | |
| document.body.appendChild(a); | |
| a.click(); | |
| document.body.removeChild(a); | |
| URL.revokeObjectURL(url); | |
| } | |
| /* === ANÁLISIS CON IA === */ | |
| document.getElementById('analyzeBtn').onclick = async function() { | |
| const box = document.getElementById('iaBox'); | |
| const out = document.getElementById('iaOutput'); | |
| const btn = this; | |
| box.style.display = 'block'; | |
| out.textContent = 'Generando análisis detallado...'; | |
| btn.disabled = true; | |
| try { | |
| const res = await fetch('/analyze', { | |
| method: 'POST', | |
| headers: {'Content-Type': 'application/json'}, | |
| body: JSON.stringify(window.finalData) | |
| }); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| out.textContent = data.analysis || 'Análisis completado.'; | |
| } else { | |
| throw new Error('Error del servidor'); | |
| } | |
| } catch (error) { | |
| out.textContent = 'El análisis detallado no está disponible en este momento.\n\nPuntaje: ' + window.finalData.overall + '%\n\nPara un diagnóstico completo, contacta a Doctor Linux.'; | |
| } finally { | |
| btn.disabled = false; | |
| } | |
| }; | |
| // Inicializar aplicación | |
| render(); | |
| </script> | |
| </body> | |
| </html> |