feat: rediseño UI completo + infra email + stats

This commit is contained in:
hacklab 2026-04-20 00:46:00 +02:00
parent 93d75ddafe
commit 24401c0ee5
37 changed files with 2162 additions and 412 deletions

View file

@ -217,6 +217,7 @@
<a class="nav-btn" href="concienciacion.html">Concienciación</a>
<a class="nav-btn" href="index.html">Resetea</a>
<a class="nav-btn" href="egosurfing.html">Egosurfing</a>
<a class="nav-btn" href="stats.html">Estadísticas</a>
</nav>
</div>
</header>
@ -637,6 +638,28 @@ En aplicación del artículo 21.3 RGPD, en el caso de tratamiento con fines de m
}
};
/* ============================================================
UTILIDADES DE SEGURIDAD
esc() — escapa HTML para inserción en innerHTML
safeHref() — valida que una URL sea http/https (bloquea javascript:)
============================================================ */
function esc(str) {
return String(str ?? '')
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#x27;');
}
function safeHref(url) {
try {
const u = new URL(String(url || ''));
if (u.protocol !== 'http:' && u.protocol !== 'https:') return '#';
return u.href;
} catch { return '#'; }
}
/* ============================================================
ESTADO
============================================================ */
@ -654,8 +677,8 @@ document.addEventListener('DOMContentLoaded', () => {
function renderPlatformGrid() {
const grid = document.getElementById('platform-grid');
grid.innerHTML = Object.entries(PLATFORMS).map(([key, p]) => `
<button class="platform-btn" data-key="${key}" onclick="selectPlatform('${key}')">
${p.name}
<button class="platform-btn" data-key="${esc(key)}" onclick="selectPlatform('${esc(key)}')">
${esc(p.name)}
</button>
`).join('');
}
@ -663,8 +686,8 @@ function renderPlatformGrid() {
function renderTypeGrid() {
const grid = document.getElementById('type-grid');
grid.innerHTML = Object.entries(REQUEST_TYPES).map(([key, t]) => `
<button class="type-btn" data-key="${key}" onclick="selectType('${key}')">
${t.label}
<button class="type-btn" data-key="${esc(key)}" onclick="selectType('${esc(key)}')">
${esc(t.label)}
</button>
`).join('');
}
@ -681,14 +704,14 @@ function showPlatformInfo(key) {
const p = PLATFORMS[key];
const info = document.getElementById('platform-info');
info.innerHTML = `
<div class="pi-row"><span class="pi-label">Empresa:</span><span>${p.company}</span></div>
<div class="pi-row"><span class="pi-label">Dirección:</span><span>${p.address}</span></div>
<div class="pi-row"><span class="pi-label">Empresa:</span><span>${esc(p.company)}</span></div>
<div class="pi-row"><span class="pi-label">Dirección:</span><span>${esc(p.address)}</span></div>
<div class="pi-row"><span class="pi-label">DPO / Privacidad:</span><span>${
p.dpoEmail ? `<a href="mailto:${p.dpoEmail}">${p.dpoEmail}</a>` : '—'
p.dpoEmail ? `<a href="mailto:${esc(p.dpoEmail)}">${esc(p.dpoEmail)}</a>` : '—'
}</span></div>
<div class="pi-row"><span class="pi-label">Eliminar cuenta:</span><span><a href="${p.deleteUrl}" target="_blank" rel="noopener">Abrir enlace oficial</a></span></div>
${p.formUrl ? `<div class="pi-row"><span class="pi-label">Formulario GDPR:</span><span><a href="${p.formUrl}" target="_blank" rel="noopener">Formulario oficial</a></span></div>` : ''}
${p.notes ? `<div class="pi-row" style="margin-top:0.5rem;"><span class="pi-label" style="color:var(--accent);">Nota:</span><span style="color:var(--muted);font-size:0.82rem;">${p.notes}</span></div>` : ''}
<div class="pi-row"><span class="pi-label">Eliminar cuenta:</span><span><a href="${safeHref(p.deleteUrl)}" target="_blank" rel="noopener">Abrir enlace oficial</a></span></div>
${p.formUrl ? `<div class="pi-row"><span class="pi-label">Formulario GDPR:</span><span><a href="${safeHref(p.formUrl)}" target="_blank" rel="noopener">Formulario oficial</a></span></div>` : ''}
${p.notes ? `<div class="pi-row" style="margin-top:0.5rem;"><span class="pi-label" style="color:var(--accent);">Nota:</span><span style="color:var(--muted);font-size:0.82rem;">${esc(p.notes)}</span></div>` : ''}
`;
info.classList.add('visible');
}
@ -788,12 +811,12 @@ ${address ? address + '\n' : ''}${today}`;
<ol>
<li><strong>Copia la carta</strong> con el botón "Copiar carta".</li>
<li>${p.dpoEmail
? `<strong>Envíala por email</strong> a <a href="mailto:${p.dpoEmail}">${p.dpoEmail}</a>.`
: `<strong>Usa el formulario oficial</strong>: <a href="${p.formUrl || p.deleteUrl}" target="_blank" rel="noopener">abrir formulario</a>.`
? `<strong>Envíala por email</strong> a <a href="mailto:${esc(p.dpoEmail)}">${esc(p.dpoEmail)}</a>.`
: `<strong>Usa el formulario oficial</strong>: <a href="${safeHref(p.formUrl || p.deleteUrl)}" target="_blank" rel="noopener">abrir formulario</a>.`
}</li>
<li><strong>Guarda el acuse de recibo</strong> (captura o email de confirmación). El plazo de 30 días empieza desde la recepción.</li>
<li>Si no responden antes del <strong>${deadline}</strong>, presenta reclamación en la <a href="https://sedeagpd.gob.es/sede-electronica-web/" target="_blank" rel="noopener">sede AEPD</a>.</li>
${p.deleteUrl ? `<li>Si también quieres eliminar tu cuenta, accede aquí: <a href="${p.deleteUrl}" target="_blank" rel="noopener">${p.name} — eliminación de cuenta</a>.</li>` : ''}
<li>Si no responden antes del <strong>${esc(deadline)}</strong>, presenta reclamación en la <a href="https://sedeagpd.gob.es/sede-electronica-web/" target="_blank" rel="noopener">sede AEPD</a>.</li>
${p.deleteUrl ? `<li>Si también quieres eliminar tu cuenta, accede aquí: <a href="${safeHref(p.deleteUrl)}" target="_blank" rel="noopener">${esc(p.name)} — eliminación de cuenta</a>.</li>` : ''}
</ol>
`;