Spaces:
Sleeping
Sleeping
Upload 5 files
Browse files- README.md +17 -1
- app.py +33 -18
- index.html +202 -92
README.md
CHANGED
|
@@ -8,4 +8,20 @@ app_file: app.py
|
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
-
# Autoevaluación de Ciberseguridad
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# Autoevaluación de Ciberseguridad Operativa
|
| 12 |
+
|
| 13 |
+
Herramienta interactiva para evaluar tu postura de ciberseguridad con diagnóstico automatizado.
|
| 14 |
+
|
| 15 |
+
## 📋 Dominios Evaluados
|
| 16 |
+
|
| 17 |
+
- 🔥 Perímetro y Firewall
|
| 18 |
+
- 🖥️ Servidores y Hardening
|
| 19 |
+
- 💾 Backups y Recuperación
|
| 20 |
+
- 📊 Monitoreo y Alertas
|
| 21 |
+
- 🌐 Red y Conectividad
|
| 22 |
+
- 💻 Usuarios y Estaciones
|
| 23 |
+
- 🌐 Web y Correo
|
| 24 |
+
|
| 25 |
+
---
|
| 26 |
+
|
| 27 |
+
*Desarrollado por Doctor Linux - Expertos en Ciberseguridad Operativa*
|
app.py
CHANGED
|
@@ -1,10 +1,14 @@
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
from fastapi.responses import HTMLResponse
|
|
|
|
| 3 |
from pydantic import BaseModel
|
| 4 |
from typing import List
|
| 5 |
|
| 6 |
app = FastAPI()
|
| 7 |
|
|
|
|
|
|
|
|
|
|
| 8 |
class DomainScore(BaseModel):
|
| 9 |
name: str
|
| 10 |
score: int
|
|
@@ -15,37 +19,48 @@ class AnalysisRequest(BaseModel):
|
|
| 15 |
|
| 16 |
@app.post("/analyze")
|
| 17 |
async def analyze(data: AnalysisRequest):
|
|
|
|
| 18 |
analysis = f"""
|
| 19 |
-
🔒 **
|
| 20 |
|
| 21 |
-
**PUNTAJE
|
| 22 |
|
| 23 |
**RESUMEN:**
|
| 24 |
-
{"✅
|
| 25 |
-
"⚠️
|
| 26 |
-
"🚨
|
| 27 |
|
| 28 |
-
|
| 29 |
{chr(10).join([f"• {domain.name}: {domain.score}%" for domain in data.domains])}
|
| 30 |
|
| 31 |
**RECOMENDACIONES PRIORITARIAS:**
|
| 32 |
-
1.
|
| 33 |
-
2. Implementar estrategia de backups 3-2-1
|
| 34 |
-
3. Establecer
|
| 35 |
-
4.
|
| 36 |
-
5.
|
| 37 |
-
|
| 38 |
-
**PRÓXIMOS PASOS:**
|
| 39 |
-
•
|
| 40 |
-
•
|
| 41 |
-
•
|
|
|
|
|
|
|
|
|
|
| 42 |
"""
|
| 43 |
return {"analysis": analysis}
|
| 44 |
|
| 45 |
@app.get("/")
|
| 46 |
async def read_root():
|
| 47 |
-
|
| 48 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
|
| 50 |
if __name__ == "__main__":
|
| 51 |
import uvicorn
|
|
|
|
| 1 |
from fastapi import FastAPI
|
| 2 |
from fastapi.responses import HTMLResponse
|
| 3 |
+
from fastapi.staticfiles import StaticFiles
|
| 4 |
from pydantic import BaseModel
|
| 5 |
from typing import List
|
| 6 |
|
| 7 |
app = FastAPI()
|
| 8 |
|
| 9 |
+
# Servir archivos estáticos
|
| 10 |
+
app.mount("/static", StaticFiles(directory="."), name="static")
|
| 11 |
+
|
| 12 |
class DomainScore(BaseModel):
|
| 13 |
name: str
|
| 14 |
score: int
|
|
|
|
| 19 |
|
| 20 |
@app.post("/analyze")
|
| 21 |
async def analyze(data: AnalysisRequest):
|
| 22 |
+
# Análisis básico sin IA para evitar problemas
|
| 23 |
analysis = f"""
|
| 24 |
+
🔒 **DIAGNÓSTICO DE CIBERSEGURIDAD - DOCTOR LINUX**
|
| 25 |
|
| 26 |
+
**PUNTAJE GLOBAL:** {data.overall}%
|
| 27 |
|
| 28 |
**RESUMEN:**
|
| 29 |
+
{"✅ POSTURA SÓLIDA - Buen trabajo en controles de seguridad" if data.overall >= 80 else
|
| 30 |
+
"⚠️ POSTURA MEDIA - Se requieren mejoras en algunos controles" if data.overall >= 60 else
|
| 31 |
+
"🚨 RIESGO ELEVADO - Atención inmediata requerida"}
|
| 32 |
|
| 33 |
+
**DETALLE POR DOMINIOS:**
|
| 34 |
{chr(10).join([f"• {domain.name}: {domain.score}%" for domain in data.domains])}
|
| 35 |
|
| 36 |
**RECOMENDACIONES PRIORITARIAS:**
|
| 37 |
+
1. Endurecer perímetro con reglas mínimas necesarias
|
| 38 |
+
2. Implementar estrategia de backups 3-2-1 con pruebas
|
| 39 |
+
3. Establecer monitoreo centralizado 24/7
|
| 40 |
+
4. Aplicar hardening en servidores y estaciones
|
| 41 |
+
5. Implementar autenticación multifactor (MFA)
|
| 42 |
+
|
| 43 |
+
**PRÓXIMOS PASOS SUGERIDOS:**
|
| 44 |
+
• Solicitar diagnóstico técnico gratuito
|
| 45 |
+
• Desarrollar plan de remediación priorizado
|
| 46 |
+
• Establecer métricas de seguimiento continuo
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
*Diagnóstico generado por Doctor Linux - Expertos en Ciberseguridad Operativa*
|
| 50 |
"""
|
| 51 |
return {"analysis": analysis}
|
| 52 |
|
| 53 |
@app.get("/")
|
| 54 |
async def read_root():
|
| 55 |
+
try:
|
| 56 |
+
with open("index.html", "r", encoding="utf-8") as f:
|
| 57 |
+
return HTMLResponse(content=f.read())
|
| 58 |
+
except Exception as e:
|
| 59 |
+
return HTMLResponse(content=f"<h1>Error cargando la aplicación</h1><p>{e}</p>")
|
| 60 |
+
|
| 61 |
+
@app.get("/health")
|
| 62 |
+
async def health_check():
|
| 63 |
+
return {"status": "healthy", "service": "Cibertest Doctor Linux"}
|
| 64 |
|
| 65 |
if __name__ == "__main__":
|
| 66 |
import uvicorn
|
index.html
CHANGED
|
@@ -12,16 +12,20 @@
|
|
| 12 |
.dlx-sub{color:#5c6b5f;margin-bottom:16px}
|
| 13 |
.dlx-progress{height:10px;background:#eff4f0;border-radius:999px;overflow:hidden;margin:10px 0 18px}
|
| 14 |
.dlx-bar{height:100%;width:0;background:#7fbf7f;transition:width .3s}
|
| 15 |
-
.dlx-
|
| 16 |
-
.dlx-btn:hover{background:#1a361c}
|
| 17 |
-
.dlx-btn:disabled{background:#ccc;cursor:not-allowed}
|
| 18 |
.dlx-options{display:flex;gap:10px;flex-wrap:wrap;margin:6px 0 12px}
|
| 19 |
-
.dlx-pill{border:1px solid #d7e1d9;padding:8px 12px;border-radius:999px;cursor:pointer
|
| 20 |
-
.dlx-pill:
|
| 21 |
.dlx-pill.active{background:#214424;color:#fff;border-color:#214424}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
.dlx-note{color:#6a786d}
|
| 23 |
.dlx-score{font-size:40px;font-weight:800;color:#214424}
|
| 24 |
-
.
|
| 25 |
</style>
|
| 26 |
</head>
|
| 27 |
|
|
@@ -29,94 +33,178 @@
|
|
| 29 |
<div class="dlx-wrap" id="dlxApp">
|
| 30 |
<div class="dlx-card">
|
| 31 |
<h2 class="dlx-title">Autoevaluación de Ciberseguridad Operativa</h2>
|
| 32 |
-
<div class="dlx-sub">Responde con honestidad y
|
| 33 |
<div class="dlx-progress"><div class="dlx-bar" id="dlxBar"></div></div>
|
| 34 |
-
<div
|
| 35 |
</div>
|
| 36 |
|
| 37 |
<div id="dlxSteps"></div>
|
| 38 |
|
| 39 |
<div class="dlx-card" id="dlxNavBox">
|
| 40 |
-
<div
|
| 41 |
<button class="dlx-btn" id="prevBtn">← Anterior</button>
|
| 42 |
<button class="dlx-btn" id="nextBtn">Siguiente →</button>
|
| 43 |
</div>
|
| 44 |
</div>
|
| 45 |
|
| 46 |
<div class="dlx-card" id="dlxResult" style="display:none">
|
| 47 |
-
<h3 class="dlx-title">
|
| 48 |
-
<div class="dlx-
|
| 49 |
-
<div
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
|
| 51 |
-
<button id="analyzeBtn" class="dlx-btn" style="margin-top:20px;">🔍 Analizar con IA</button>
|
| 52 |
<div id="iaBox" class="dlx-card" style="display:none;margin-top:16px;">
|
| 53 |
-
<h4>Análisis
|
| 54 |
<pre id="iaOutput" style="white-space:pre-wrap;font-family:inherit;"></pre>
|
| 55 |
</div>
|
| 56 |
</div>
|
| 57 |
</div>
|
| 58 |
|
| 59 |
<script>
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
'¿
|
| 64 |
-
'¿
|
| 65 |
-
|
| 66 |
-
'¿
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
'¿
|
| 71 |
-
'¿
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
];
|
|
|
|
|
|
|
| 74 |
const SCALE=[
|
| 75 |
{label:'No',score:0},
|
| 76 |
{label:'Parcial',score:50},
|
| 77 |
{label:'Sí',score:100},
|
| 78 |
{label:'No sé',score:null}
|
| 79 |
];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
let current=0,answers={};
|
| 81 |
-
const
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
|
| 84 |
function render(){
|
| 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 |
-
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);}
|
| 116 |
-
function avg(a){const n=a.filter(v=>typeof v==='number');return n.length?n.reduce((x,y)=>x+y,0)/n.length:0;}
|
| 117 |
function updateButtons(){
|
| 118 |
prev.disabled=current===0;
|
| 119 |
-
|
|
|
|
| 120 |
next.disabled=!allAnswered(current);
|
| 121 |
}
|
| 122 |
|
|
@@ -126,52 +214,74 @@ next.onclick=()=>{
|
|
| 126 |
if(!allAnswered(current))return;
|
| 127 |
current++;render();
|
| 128 |
}else{
|
|
|
|
| 129 |
showResult();
|
| 130 |
}
|
| 131 |
};
|
| 132 |
|
| 133 |
function showResult(){
|
| 134 |
-
|
|
|
|
| 135 |
steps.style.display='none';
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
document.getElementById('dlxScore').textContent=global+'%';
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 145 |
}
|
| 146 |
|
| 147 |
-
//
|
| 148 |
-
document.getElementById('analyzeBtn').onclick=async()
|
| 149 |
-
const box=document.getElementById('iaBox')
|
| 150 |
-
const
|
|
|
|
| 151 |
|
| 152 |
-
box.style.display='block';
|
| 153 |
-
out.textContent='
|
| 154 |
-
btn.disabled=true;
|
| 155 |
-
btn.classList.add('loading');
|
| 156 |
|
| 157 |
-
try{
|
| 158 |
-
const res=await fetch('/analyze',{
|
| 159 |
-
method:'POST',
|
| 160 |
-
headers:{'Content-Type':'application/json'},
|
| 161 |
-
body:JSON.stringify(window.finalData)
|
| 162 |
});
|
| 163 |
|
| 164 |
-
if(
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
out.textContent='
|
| 172 |
-
}finally{
|
| 173 |
-
btn.disabled=false;
|
| 174 |
-
btn.classList.remove('loading');
|
| 175 |
}
|
| 176 |
};
|
| 177 |
|
|
|
|
| 12 |
.dlx-sub{color:#5c6b5f;margin-bottom:16px}
|
| 13 |
.dlx-progress{height:10px;background:#eff4f0;border-radius:999px;overflow:hidden;margin:10px 0 18px}
|
| 14 |
.dlx-bar{height:100%;width:0;background:#7fbf7f;transition:width .3s}
|
| 15 |
+
.dlx-q h4{margin:0 0 6px;font-size:18px}
|
|
|
|
|
|
|
| 16 |
.dlx-options{display:flex;gap:10px;flex-wrap:wrap;margin:6px 0 12px}
|
| 17 |
+
.dlx-pill{border:1px solid #d7e1d9;padding:8px 12px;border-radius:999px;cursor:pointer}
|
| 18 |
+
.dlx-pill input{display:none}
|
| 19 |
.dlx-pill.active{background:#214424;color:#fff;border-color:#214424}
|
| 20 |
+
.dlx-nav{display:flex;gap:10px;justify-content:space-between;margin-top:8px}
|
| 21 |
+
.dlx-btn{background:#214424;color:#fff;border:none;border-radius:10px;padding:12px 16px;cursor:pointer}
|
| 22 |
+
.dlx-btn[disabled]{opacity:.5;cursor:not-allowed}
|
| 23 |
+
.dlx-meter{display:grid;grid-template-columns:1fr auto;gap:6px 10px;margin:8px 0}
|
| 24 |
+
.dlx-badge{font-weight:600}
|
| 25 |
+
.dlx-tag{display:inline-block;background:#eff4f0;border:1px solid #dfe9e2;border-radius:8px;padding:6px 10px;margin:6px 6px 0 0}
|
| 26 |
.dlx-note{color:#6a786d}
|
| 27 |
.dlx-score{font-size:40px;font-weight:800;color:#214424}
|
| 28 |
+
.dlx-muted{color:#7b8b7f}
|
| 29 |
</style>
|
| 30 |
</head>
|
| 31 |
|
|
|
|
| 33 |
<div class="dlx-wrap" id="dlxApp">
|
| 34 |
<div class="dlx-card">
|
| 35 |
<h2 class="dlx-title">Autoevaluación de Ciberseguridad Operativa</h2>
|
| 36 |
+
<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>
|
| 37 |
<div class="dlx-progress"><div class="dlx-bar" id="dlxBar"></div></div>
|
| 38 |
+
<div class="dlx-note dlx-muted" id="dlxStepNote"></div>
|
| 39 |
</div>
|
| 40 |
|
| 41 |
<div id="dlxSteps"></div>
|
| 42 |
|
| 43 |
<div class="dlx-card" id="dlxNavBox">
|
| 44 |
+
<div class="dlx-nav">
|
| 45 |
<button class="dlx-btn" id="prevBtn">← Anterior</button>
|
| 46 |
<button class="dlx-btn" id="nextBtn">Siguiente →</button>
|
| 47 |
</div>
|
| 48 |
</div>
|
| 49 |
|
| 50 |
<div class="dlx-card" id="dlxResult" style="display:none">
|
| 51 |
+
<h3 class="dlx-title">Tu diagnóstico</h3>
|
| 52 |
+
<div class="dlx-sub">Este resultado es orientativo. Si quieres un informe firmado por ingeniería, solicita el <b>diagnóstico gratuito</b>.</div>
|
| 53 |
+
<div style="display:flex;gap:24px;align-items:center;flex-wrap:wrap">
|
| 54 |
+
<div>
|
| 55 |
+
<div class="dlx-muted">Puntaje global</div>
|
| 56 |
+
<div class="dlx-score" id="dlxScore">—%</div>
|
| 57 |
+
<div class="dlx-tag" id="dlxLabel">—</div>
|
| 58 |
+
</div>
|
| 59 |
+
<div style="flex:1;min-width:260px">
|
| 60 |
+
<div class="dlx-progress"><div class="dlx-bar" id="dlxBarFinal" style="background:#3aa655"></div></div>
|
| 61 |
+
<div class="dlx-note">≈ Mayor a 80%: postura sólida. 60–79%: revisar. Menor a 60%: riesgo elevado.</div>
|
| 62 |
+
</div>
|
| 63 |
+
</div>
|
| 64 |
+
|
| 65 |
+
<div class="dlx-card" style="margin-top:16px">
|
| 66 |
+
<h4 style="margin:0 0 8px">Detalle por dominio</h4>
|
| 67 |
+
<div id="dlxBreakdown" class="dlx-meter"></div>
|
| 68 |
+
</div>
|
| 69 |
+
|
| 70 |
+
<div class="dlx-card">
|
| 71 |
+
<h4 style="margin:0 0 8px">Recomendaciones inmediatas</h4>
|
| 72 |
+
<div id="dlxRecs"></div>
|
| 73 |
+
<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>
|
| 74 |
+
</div>
|
| 75 |
+
|
| 76 |
+
<div class="dlx-nav">
|
| 77 |
+
<button class="dlx-btn" onclick="window.location.reload()">Refrescar test</button>
|
| 78 |
+
<button class="dlx-btn" id="analyzeBtn">🔍 Análisis Detallado</button>
|
| 79 |
+
</div>
|
| 80 |
|
|
|
|
| 81 |
<div id="iaBox" class="dlx-card" style="display:none;margin-top:16px;">
|
| 82 |
+
<h4>Análisis Generado por IA</h4>
|
| 83 |
<pre id="iaOutput" style="white-space:pre-wrap;font-family:inherit;"></pre>
|
| 84 |
</div>
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
<script>
|
| 89 |
+
/* === lógica actualizada === */
|
| 90 |
+
const DOMAINS = [
|
| 91 |
+
{ id:'perimetro', name:'Perímetro / Firewall', qs:[
|
| 92 |
+
'¿Usas un firewall dedicado (Mikrotik/OPNsense/etc.) con reglas mínimas por servicio?',
|
| 93 |
+
'¿Tienes listas de acceso/geo-bloqueo/IPS/DoS activas?',
|
| 94 |
+
'¿Los servicios expuestos están detrás de NAT o Port-Forward controlado?',
|
| 95 |
+
'¿Hay políticas VPN de acceso remoto con 2FA?'
|
| 96 |
+
]},
|
| 97 |
+
{ id:'servers', name:'Servidores / Hardening', qs:[
|
| 98 |
+
'¿Cuentas con hardening básico (SSH seguro, usuarios/roles, auditoría)?',
|
| 99 |
+
'¿Actualizaciones y parches aplicados regularmente?',
|
| 100 |
+
'¿Servicios innecesarios deshabilitados/controlados?'
|
| 101 |
+
]},
|
| 102 |
+
{ id:'backups', name:'Backups y Recuperación', qs:[
|
| 103 |
+
'¿Aplicas la regla 3-2-1 (3 copias, 2 medios, 1 offsite)?',
|
| 104 |
+
'¿Pruebas de restauración realizadas en los últimos 6 meses?',
|
| 105 |
+
'¿Backups protegidos contra ransomware (inmutables/air-gap)?'
|
| 106 |
+
]},
|
| 107 |
+
{ id:'monitor', name:'Monitoreo y Alertas', qs:[
|
| 108 |
+
'¿Monitoreo centralizado (Zabbix/PRTG/Grafana) con alertas 24/7?',
|
| 109 |
+
'¿Métricas revisadas mensualmente?',
|
| 110 |
+
'¿Alertas integradas con soporte?'
|
| 111 |
+
]},
|
| 112 |
+
{ id:'network', name:'Red y Conectividad', qs:[
|
| 113 |
+
'¿Segmentación VLANs para aislar servicios críticos?',
|
| 114 |
+
'¿Control de ancho de banda/Proxy/filtrado de contenidos?',
|
| 115 |
+
'¿Wi-Fi empresarial con WPA2-Enterprise?'
|
| 116 |
+
]},
|
| 117 |
+
{ id:'endpoints', name:'Usuarios / Estaciones', qs:[
|
| 118 |
+
'¿Antimalware/EDR y políticas de actualización?',
|
| 119 |
+
'¿Gestión de contraseñas y MFA?',
|
| 120 |
+
'¿Políticas de mínimo privilegio + bloqueo de USB?'
|
| 121 |
+
]},
|
| 122 |
+
{ id:'webmail', name:'Web y Correo', qs:[
|
| 123 |
+
'¿Sitios con HTTPS válido y cabeceras seguras?',
|
| 124 |
+
'¿Correo con SPF, DKIM y DMARC publicados?',
|
| 125 |
+
'¿WAF/antispam/antiphishing activos?'
|
| 126 |
+
]}
|
| 127 |
];
|
| 128 |
+
|
| 129 |
+
// 🟢 Ahora "No sé" no suma ni resta
|
| 130 |
const SCALE=[
|
| 131 |
{label:'No',score:0},
|
| 132 |
{label:'Parcial',score:50},
|
| 133 |
{label:'Sí',score:100},
|
| 134 |
{label:'No sé',score:null}
|
| 135 |
];
|
| 136 |
+
|
| 137 |
+
const RECS={
|
| 138 |
+
perimetro:'Endurecer perímetro: reglas mínimo necesario, VPN 2FA, IDS/IPS y protección DoS.',
|
| 139 |
+
servers:'Aplicar hardening, cuentas/roles y mantener parches al día.',
|
| 140 |
+
backups:'Implementar 3-2-1 con pruebas de restauración y backups inmutables.',
|
| 141 |
+
monitor:'Centralizar monitoreo (Zabbix/PRTG) y definir umbrales.',
|
| 142 |
+
network:'Segregar VLANs, reforzar proxy y aislar Wi-Fi invitados.',
|
| 143 |
+
endpoints:'Mínimo privilegio, EDR/antimalware y MFA.',
|
| 144 |
+
webmail:'Forzar HTTPS, cabeceras OWASP, SPF+DKIM+DMARC y WAF.'
|
| 145 |
+
};
|
| 146 |
+
|
| 147 |
let current=0,answers={};
|
| 148 |
+
const bar=document.getElementById('dlxBar'),
|
| 149 |
+
steps=document.getElementById('dlxSteps'),
|
| 150 |
+
note=document.getElementById('dlxStepNote'),
|
| 151 |
+
prev=document.getElementById('prevBtn'),
|
| 152 |
+
next=document.getElementById('nextBtn'),
|
| 153 |
+
result=document.getElementById('dlxResult'),
|
| 154 |
+
nav=document.getElementById('dlxNavBox');
|
| 155 |
|
| 156 |
function render(){
|
| 157 |
+
steps.innerHTML='';
|
| 158 |
+
const d=DOMAINS[current];
|
| 159 |
+
note.innerHTML=`<b>${current+1}/${DOMAINS.length}</b> · ${d.name}`;
|
| 160 |
+
const card=document.createElement('div');
|
| 161 |
+
card.className='dlx-card';
|
| 162 |
+
card.innerHTML=`<h3 class="dlx-title">${d.name}</h3>`;
|
| 163 |
+
d.qs.forEach((q,i)=>{
|
| 164 |
+
const b=document.createElement('div');
|
| 165 |
+
b.className='dlx-q';
|
| 166 |
+
b.innerHTML=`<h4>${q}</h4>`;
|
| 167 |
+
const row=document.createElement('div');
|
| 168 |
+
row.className='dlx-options';
|
| 169 |
+
SCALE.forEach(opt=>{
|
| 170 |
+
const lbl=document.createElement('label');
|
| 171 |
+
lbl.className='dlx-pill';
|
| 172 |
+
lbl.textContent=opt.label;
|
| 173 |
+
lbl.onclick=()=>{
|
| 174 |
+
row.querySelectorAll('.dlx-pill').forEach(p=>p.classList.remove('active'));
|
| 175 |
+
lbl.classList.add('active');
|
| 176 |
+
if(!answers[d.id])answers[d.id]=[];
|
| 177 |
+
answers[d.id][i]=opt.score;
|
| 178 |
+
updateButtons();
|
| 179 |
+
};
|
| 180 |
+
if(answers[d.id]&&answers[d.id][i]===opt.score)lbl.classList.add('active');
|
| 181 |
+
row.appendChild(lbl);
|
| 182 |
});
|
| 183 |
+
b.appendChild(row);
|
| 184 |
+
card.appendChild(b);
|
| 185 |
+
});
|
| 186 |
+
steps.appendChild(card);
|
| 187 |
+
bar.style.width=Math.round(current/DOMAINS.length*100)+'%';
|
| 188 |
+
updateButtons();
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
// ✅ Acepta "No sé" como respuesta
|
| 192 |
+
function allAnswered(i){
|
| 193 |
+
const d=DOMAINS[i];
|
| 194 |
+
return answers[d.id] && answers[d.id].length===d.qs.length && answers[d.id].every(v=>v!==undefined);
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
// ✅ Ignora "No sé" al promediar
|
| 198 |
+
function avg(a){
|
| 199 |
+
const nums=a.filter(v=>typeof v==='number' && isFinite(v));
|
| 200 |
+
if(nums.length===0) return 0;
|
| 201 |
+
return nums.reduce((x,y)=>x+y,0)/nums.length;
|
| 202 |
}
|
| 203 |
|
|
|
|
|
|
|
| 204 |
function updateButtons(){
|
| 205 |
prev.disabled=current===0;
|
| 206 |
+
const last=current===DOMAINS.length-1;
|
| 207 |
+
next.textContent=last?'Ver resultado →':'Siguiente →';
|
| 208 |
next.disabled=!allAnswered(current);
|
| 209 |
}
|
| 210 |
|
|
|
|
| 214 |
if(!allAnswered(current))return;
|
| 215 |
current++;render();
|
| 216 |
}else{
|
| 217 |
+
if(!allAnswered(current))return;
|
| 218 |
showResult();
|
| 219 |
}
|
| 220 |
};
|
| 221 |
|
| 222 |
function showResult(){
|
| 223 |
+
bar.style.width='100%';
|
| 224 |
+
nav.style.display='none';
|
| 225 |
steps.style.display='none';
|
| 226 |
+
note.style.display='none';
|
| 227 |
+
result.style.display='block';
|
| 228 |
+
let weighted=0,total=0;
|
| 229 |
+
const breakdown=document.getElementById('dlxBreakdown'),
|
| 230 |
+
recs=document.getElementById('dlxRecs');
|
| 231 |
+
breakdown.innerHTML='';
|
| 232 |
+
recs.innerHTML='';
|
| 233 |
+
DOMAINS.forEach(d=>{
|
| 234 |
+
const a=answers[d.id]?avg(answers[d.id]):0;
|
| 235 |
+
weighted+=a;total+=100;
|
| 236 |
+
const pct=Math.round(a);
|
| 237 |
+
breakdown.innerHTML+=`<div>${d.name}</div><div><b>${pct}%</b></div>`;
|
| 238 |
+
if(pct<75){recs.innerHTML+=`<div class="dlx-tag">• ${RECS[d.id]}</div>`;}
|
| 239 |
+
});
|
| 240 |
+
const global=Math.round(weighted/DOMAINS.length);
|
| 241 |
document.getElementById('dlxScore').textContent=global+'%';
|
| 242 |
+
document.getElementById('dlxBarFinal').style.width=global+'%';
|
| 243 |
+
const label=document.getElementById('dlxLabel');
|
| 244 |
+
if(global>=80){label.textContent='Nivel Sólido';label.style.background='#e7f7e7';}
|
| 245 |
+
else if(global>=60){label.textContent='Nivel Medio (revisar)';label.style.background='#fff6e1';}
|
| 246 |
+
else{label.textContent='Riesgo Alto';label.style.background='#ffe9e9';}
|
| 247 |
+
|
| 248 |
+
// Guardar datos para el análisis IA
|
| 249 |
+
window.finalData = {
|
| 250 |
+
overall: global,
|
| 251 |
+
domains: DOMAINS.map(d => ({
|
| 252 |
+
name: d.name,
|
| 253 |
+
score: Math.round(avg(answers[d.id]))
|
| 254 |
+
}))
|
| 255 |
+
};
|
| 256 |
}
|
| 257 |
|
| 258 |
+
// 🔍 Análisis con IA
|
| 259 |
+
document.getElementById('analyzeBtn').onclick = async function() {
|
| 260 |
+
const box = document.getElementById('iaBox');
|
| 261 |
+
const out = document.getElementById('iaOutput');
|
| 262 |
+
const btn = this;
|
| 263 |
|
| 264 |
+
box.style.display = 'block';
|
| 265 |
+
out.textContent = 'Generando análisis detallado...';
|
| 266 |
+
btn.disabled = true;
|
|
|
|
| 267 |
|
| 268 |
+
try {
|
| 269 |
+
const res = await fetch('/analyze', {
|
| 270 |
+
method: 'POST',
|
| 271 |
+
headers: {'Content-Type': 'application/json'},
|
| 272 |
+
body: JSON.stringify(window.finalData)
|
| 273 |
});
|
| 274 |
|
| 275 |
+
if (res.ok) {
|
| 276 |
+
const data = await res.json();
|
| 277 |
+
out.textContent = data.analysis || 'Análisis completado.';
|
| 278 |
+
} else {
|
| 279 |
+
throw new Error('Error del servidor');
|
| 280 |
+
}
|
| 281 |
+
} catch (error) {
|
| 282 |
+
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.';
|
| 283 |
+
} finally {
|
| 284 |
+
btn.disabled = false;
|
|
|
|
| 285 |
}
|
| 286 |
};
|
| 287 |
|