══════════════════════════════════════════════════════════════════════
RESETEA.NET — CONTEXTO DEL PROYECTO
Actualizado: 2026-04-20
══════════════════════════════════════════════════════════════════════

QUÉ ES
──────
Herramienta web de privacidad digital. Ayuda a los usuarios a reducir
su huella digital: enviar cartas GDPR, eliminar cuentas en redes
sociales, desindexar datos de buscadores y contactar con data brokers.

Principio fundamental: PRIVACY-FIRST.
  · No se almacena ningún dato personal (PII).
  · Sin cookies, sin tracking, sin analytics.
  · La generación de cartas en plantillas.html es 100% local (JS en navegador).
  · El único dato que pasa por el servidor es el email para reenvío GDPR,
    y solo se guarda un hash SHA-256 truncado (12 chars) como referencia.


ARQUITECTURA GENERAL
────────────────────
  Internet
    └── Nginx (puerto 80 → redirect HTTPS / puerto 443 SSL)
          ├── /          → archivos estáticos en public/
          └── /api/*     → proxy → Node.js en 127.0.0.1:8787
                              └── Nodemailer → sendmail (MTA local)
                              └── Postfix → relay Brevo SMTP (smtp-relay.brevo.com:587)
                              └── opendkim (firma DKIM, selector: mail, 2048-bit RSA)
                              └── Google APIs (opcional: Gmail OAuth, CSE)
                              └── SearXNG (instancias públicas, fallback egosearch)


INFRAESTRUCTURA DE EMAIL
────────────────────────
Problema: IP dinámica del router → reputación de envío comprometida.
Solución: relay via Brevo (free tier, 300 emails/día).
  · Postfix configurado como relay hacia smtp-relay.brevo.com:587
  · Credenciales en /etc/postfix/sasl_passwd (script: infra/set-relay-credentials.sh)
  · DKIM signing con opendkim — selector "mail", clave en /etc/opendkim/keys/resetea.net/
  · DNS gestionado via Gandi LiveDNS API — script: /home/capitansito/HOST/managedns.sh

Registros DNS configurados:
  SPF:   "v=spf1 include:spf.brevo.com include:_mailcust.gandi.net ~all"
  DKIM:  mail._domainkey.resetea.net  TXT  (clave pública 2048-bit RSA)
  DMARC: _dmarc.resetea.net  TXT  "v=DMARC1; p=none; rua=mailto:dmarc@resetea.net"
  → Cuando el envío sea estable varios días, subir p=none a p=quarantine

Scripts de infraestructura:
  infra/setup-mail.sh          — instala Postfix, opendkim, genera clave DKIM
  infra/set-relay-credentials.sh — escribe /etc/postfix/sasl_passwd con login Brevo
  HOST/managedns.sh            — actualiza IP dinámica en Gandi (NO toca registros mail)
                                  Comando: setup-mail-dns para configurar SPF/DKIM/DMARC

Proceso Node.js:
  Servicio: /etc/systemd/system/resetea.service
  Node binary: /home/capitansito/.nvm/versions/node/v18.20.8/bin/node
  Arranque: sudo systemctl restart resetea


PÁGINAS PÚBLICAS (public/)
───────────────────────────
index.html  — Página principal. REDISEÑADA EN ESTA SESIÓN.
  Secciones:
  1. Hero: título "RESETEA" + lema "La información es poder. Quitémosles poder."
     Carrusel de citas (5 frases, rota cada 6s):
       - "Matamos gente basándonos en metadatos." (Gen. Hayden, NSA)
       - Edward Snowden sobre privacidad
       - Parafraseando Orwell, 1984
       - "No somos clientes, somos el producto." (Serra & Schoolman, 1973)
       - "La vigilancia es el modelo de negocio de internet." (Schneier)
  2. Formulario GDPR (DISEÑO DOS COLUMNAS — nuevo):
       Columna izquierda (Paso 1): email input + lista de privacidad
       Columna derecha (Paso 2): selección de redes
         · Grid de chips AUTO-GDPR (instagram, facebook, twitter_x, linkedin,
           tiktok, snapchat, discord, reddit, microsoft, apple, google, amazon)
           → seleccionables, al enviar se manda carta GDPR via API o se abre form
         · Grid de chips MANUALES (whatsapp, telegram, spotify, youtube,
           netflix, twitch, pinterest)
           → ACCIÓN DIRECTA: clic abre el enlace oficial inmediatamente
           → NO son seleccionables, NO cuentan para el botón "Enviar"
           → Borde discontinuo, flash verde al hacer clic
         · Botón "Seleccionar todas" (solo auto-chips)
         · Contador "X redes seleccionadas"
       Botón "Enviar cartas GDPR (X)" — solo activo con email válido + al menos 1 auto-chip
  3. Panel de acciones completo (REDISEÑADO — secciones visibles, sin tabs):
       Barra de progreso global (todos los checkboxes)
       6 secciones siempre visibles apiladas verticalmente, cada una con:
         · Badge pill con nombre en fuente Recion (Cuentas base, Redes sociales,
           Mensajería, Streaming, Buscadores, Data brokers)
         · Botón "Marcar todas" (header) y "✓ Marcar sección completada" (footer)
         · Grid de tarjetas con: logo oficial + nombre + botones de acción
         · Al marcar checkbox: tarjeta se vuelve verde irlandés (sage)
  4. Sección "Cómo funciona" (3 pasos)
  5. Sección "Plantillas legales"
  6. Aviso legal

  Logos: Simple Icons CDN (cdn.simpleicons.org) — pendiente mover a /public/icons/
  PENDIENTE: Descargar los SVGs localmente (linkedin, microsoft, amazon,
             microsoftbing fallaron — buscar slug correcto en Simple Icons)

stats.html  — Dashboard de estadísticas anónimas. NUEVO.
  · 4 KPI cards: emails enviados, redireccionados, búsquedas, total
  · Gráfico de barras por proveedor
  · Explicación detallada de privacidad (sección <details> expandible)
  · Fetch a GET /api/stats con credentials:'omit'
  · Todo el HTML de servidor pasa por esc() antes de innerHTML (anti-XSS)

plantillas.html — Generador de cartas GDPR (browser-side).
  · esc() + safeHref() aplicados a todo HTML dinámico
  · Nav: enlace añadido a stats.html

concienciacion.html, tipos.html, egosurfing.html — sin cambios en esta sesión.


SERVICIOS DEL BACKEND (api/)
─────────────────────────────
POST /api/erase
  Envía carta GDPR Art. 17 al DPO.
  Entrada: { provider, email, [nickname, phone, address, extra] }
  Salida:  { status: 'ok'|'use_form'|error, reference: hash12chars }
  Proveedores API directa (10): instagram, facebook, twitter_x, linkedin,
    tiktok, snapchat, microsoft, apple, reddit, discord
  Proveedores formulario oficial (2): google, amazon
  SEGURIDAD: sin fallback a proveedor desconocido (eliminado open-relay)

GET /api/stats
  Devuelve estadísticas anónimas en JSON.
  Rate limit: 30 req/min
  Datos: total_sent, total_redirected, total_errors, total_searches,
         by_provider (solo proveedores whitelisteados)
  Almacenamiento: data/stats.json (escritura atómica con .tmp + rename)
  SIN PII — solo contadores agregados.

GET /api/egosearch?q=<consulta>
  Busca con SearXNG o Google CSE. Límite: 500.000 bytes respuesta.
  Registra stats.record({ type: 'search' }) por cada búsqueda.

GET /api/gmail/auth → GET /api/gmail/callback
  OAuth2 Google. Estado firmado con HMAC-SHA256 + nonce (anti-forgery).
  Token de un solo uso, sin refresh, sin persistencia.


SEGURIDAD IMPLEMENTADA
──────────────────────
FRONTEND:
  · esc() en todos los datos del servidor antes de innerHTML
  · safeHref() para validar URLs (solo http/https)
  · credentials:'omit' en todos los fetch

BACKEND:
  · Rate limiting Nginx: 20 req/s, burst 40 para /api/
  · Rate limiting Express: general 100/15min, email 10/1h, búsqueda 8/1min, stats 30/1min
  · IP real via X-Real-IP desde Nginx ($remote_addr — no spoofeable)
  · Helmet: HSTS, X-Frame-Options DENY, CSP estricta, Referrer-Policy, Permissions-Policy
  · Anti-CRLF: sanitizeHeader() en campos opcionales del email
  · Anti prototype pollution: Object.create(null) en stats.by_provider
  · Timeout Slowloris: headersTimeout 10s, requestTimeout 15s en Node
  · Timeout Nginx: proxy_read_timeout 15s, connect_timeout 5s
  · OAuth state: HMAC-SHA256 con nonce random, base64url, max 4096 bytes
  · Mailer: sin fallback a proveedor desconocido
  · Stats: safeInt() valida numéricos, VALID_DATE regex valida fecha


DISEÑO Y UI
───────────
Sistema de diseño (index.css):
  Variables CSS: --caoba (marrón oscuro), --sage (#1a7a4a, verde irlandés),
                 --acid (#c8ff00, amarillo ácido), --surface, --bg, --border
  Fuentes: Recion/Italiana (serif, para títulos y badges), system-ui (para texto)

Formulario principal:
  · Layout dos columnas (grid 340px + 1fr) — sticky email column
  · Botones de red: system-ui, 0.97rem, grid auto-fill minmax(130px, 1fr)
  · Auto-chips seleccionados: verde irlandés (#1a7a4a)
  · Manual chips: borde discontinuo, clic directo
  · Mobile: stacks a columna única en ≤720px

Panel de acciones:
  · Secciones visibles sin tabs
  · Badges: fuente Recion, background caoba, border-radius 20px
  · Cards: border 1.5px, border-radius 14px, checked = sage-lt background
  · Botones de acción: 0.82rem, padding 0.35rem 0.85rem, border-radius 8px
  · Logos: Simple Icons CDN (pendiente mover a /public/icons/ local)


PENDIENTES PARA PRÓXIMA SESIÓN
───────────────────────────────
1. Logos locales: descargar SVGs de Simple Icons con slugs correctos
     Fallaron: linkedin, microsoft, amazon, microsoftbing
     Intentar desde: https://simpleicons.org/ o el paquete npm simple-icons
     Guardar en: public/icons/{slug}.svg
     Actualizar src en HTML de /icons/{slug}.svg

2. DNS/mail: revisar logs de envío con journalctl -u postfix
     Cuando envío sea estable varios días → subir DMARC a p=quarantine:
     Comando: bash /home/capitansito/HOST/managedns.sh setup-mail-dns
     (editar el script para cambiar p=none a p=quarantine)

3. Systemd: verificar que resetea.service arranca bien con:
     sudo systemctl status resetea
     sudo systemctl enable resetea  (si no está enabled)

4. Probar flujo completo de email desde la web en producción.

5. Gmail OAuth: requiere credenciales Google Cloud Console en .env
     GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI

6. Revisar si el ícono de Snapchat (amarillo) es visible sobre fondo claro —
   puede necesitar ajuste de contraste.
