diff --git a/05-decision-seguridad.md b/05-decision-seguridad.md
new file mode 100644
index 0000000..0306689
--- /dev/null
+++ b/05-decision-seguridad.md
@@ -0,0 +1,847 @@
+# Decision: Capa de seguridad (punto 2)
+
+## Contexto
+
+Este servidor fue comprometido con un credential stealer via PAM que opero 4 dias sin ser detectado. Tenemos firewall bien configurado (policy DROP, SSH por IP, bloqueo de mineria) y aun asi entraron. La capa de seguridad debe cubrir lo que el firewall no puede: **detectar amenazas que ya estan dentro**.
+
+4 subcapas:
+- 2.1 Deteccion de intrusiones y file integrity
+- 2.2 Escaneo de vulnerabilidades
+- 2.3 Gestion de secretos
+- 2.4 Acceso y autenticacion
+
+---
+
+## 2.1 Deteccion de intrusiones y file integrity
+
+### La pregunta: Wazuh, AIDE, o auditd?
+
+No son excluyentes. Hacen cosas diferentes:
+
+| Herramienta | Que hace | Analogia |
+|-------------|----------|----------|
+| **auditd** | Registra eventos del kernel: quien abrio/modifico/creo que fichero, que proceso hizo que syscall | La camara de seguridad |
+| **AIDE** | Genera baseline de checksums y alerta si algo cambia | La foto del "antes" para comparar |
+| **Wazuh** | HIDS completo: analiza logs + file integrity + rootkit detection + vulnerability detection + compliance + respuesta activa | El guardia de seguridad con camaras, alarmas y protocolo |
+
+### Opcion A: Wazuh (RECOMENDADA)
+
+#### Que es
+Plataforma de seguridad open source (fork de OSSEC). Incluye:
+- **HIDS**: deteccion de intrusiones basada en host
+- **FIM**: file integrity monitoring (incluye lo que hace AIDE)
+- **Rootkit detection**: busca artefactos conocidos de rootkits
+- **Log analysis**: analiza auth.log, syslog, etc. con reglas de correlacion
+- **Vulnerability detection**: escanea paquetes instalados contra bases de CVE
+- **Active response**: puede bloquear IPs, matar procesos, ejecutar acciones automaticas
+- **Compliance**: reportes CIS, PCI-DSS, GDPR
+- **Dashboard**: Kibana/OpenSearch integrado, o Grafana via plugin
+
+#### Que habria detectado en este incidente
+
+| Evento | Regla Wazuh | Tiempo de deteccion |
+|--------|-------------|-------------------|
+| Creacion de `/usr/bin/login.sh` | FIM: fichero nuevo en directorio monitorizado | **Segundos** |
+| Modificacion de `/etc/pam.d/common-auth` | FIM: cambio en fichero critico | **Segundos** |
+| Creacion de `/etc/ld.so.preload` | FIM + rootkit check | **Segundos** |
+| Creacion de `/usr/local/sbin/env/` | FIM: directorio nuevo en ruta de sistema | **Segundos** |
+| Conexion saliente a 91.208.162.132:10480 | Log analysis de firewall + active response | **Minutos** |
+| Exfiltracion de credenciales via curl | Log analysis / network anomaly | **Minutos** |
+
+**Conclusion: el incidente habria durado minutos, no 4 dias.**
+
+#### Arquitectura
+
+```
+ ┌─────────────────────────────┐
+ │ Wazuh Manager │
+ │ (servidor central) │
+ │ │
+ │ - Recibe eventos de agentes │
+ │ - Evalua reglas │
+ │ - Genera alertas │
+ │ - Vulnerability scanner │
+ │ │
+ │ Wazuh Dashboard │
+ │ (o Grafana via plugin) │
+ └──────────┬──────────────────┘
+ │
+ ┌────────────────┼────────────────────┐
+ │ │ │
+ ┌────────▼────────┐ ┌────▼──────────┐ ┌──────▼───────┐
+ │ Wazuh Agent │ │ Wazuh Agent │ │ Wazuh Agent │
+ │ (PBS server 1) │ │ (PBS server 2│ │ (PVE node) │
+ │ │ │ )│ │ │
+ │ - FIM │ │ - FIM │ │ - FIM │
+ │ - Log analysis │ │ - Log anal. │ │ - Log anal. │
+ │ - Rootkit det. │ │ - Rootkit │ │ - Rootkit │
+ │ - Vuln scan │ │ - Vuln scan │ │ - Vuln scan │
+ └────────────────┘ └──────────────┘ └──────────────┘
+```
+
+#### Configuracion FIM para PBS/Proxmox
+
+```xml
+
+
+
+ 300
+
+
+
+ /etc/pam.d
+
+
+ /etc/ssh
+
+
+ /etc/ld.so.preload
+
+
+ /root/.ssh/authorized_keys
+
+
+ /etc/sudoers,/etc/sudoers.d
+
+
+ /etc/cron.d,/etc/crontab
+
+
+ /etc/systemd/system
+
+
+
+
+ /usr/bin,/usr/sbin,/usr/local/bin,/usr/local/sbin
+
+
+
+
+ /etc/proxmox-backup
+
+
+ /etc/pve
+
+
+
+ /etc/pve/authkey.pub
+ /etc/pve/authkey.pub.old
+ /etc/pve/priv/authkey.key
+ /etc/proxmox-backup/shadow.json
+ /etc/proxmox-backup/.*.cfg
+ /etc/zfs/zpool.cache
+
+
+
+
+ /var/ossec/etc/shared/rootkit_files.txt
+ /var/ossec/etc/shared/rootkit_trojans.txt
+ /var/ossec/etc/shared/system_audit_rcl.txt
+ 3600
+
+```
+
+#### Reglas custom para este tipo de ataque
+
+```xml
+
+
+
+
+
+ 550
+ pam.d
+ Fichero PAM modificado - posible backdoor
+
+
+
+
+ 554
+ ld.so.preload
+ ld.so.preload creado/modificado - posible rootkit
+
+
+
+
+ 554
+ /usr/bin/|/usr/sbin/
+ Nuevo fichero en directorio de sistema
+
+
+
+
+ 530
+ curl.*--max-time|nohup.*curl
+ Ejecucion sospechosa de curl con nohup
+
+
+
+```
+
+#### Integracion con el stack existente
+
+**Opcion 1: Wazuh Dashboard (OpenSearch)**
+- Dashboard propio de Wazuh con todos los modulos
+- Requiere OpenSearch (fork Elasticsearch) + 4GB RAM extra
+- Mejor experiencia para analisis de seguridad dedicado
+
+**Opcion 2: Wazuh + Grafana (via plugin o Loki)**
+- Wazuh envia alertas a Loki -> Grafana las muestra junto con metricas y logs
+- No necesita OpenSearch (ahorra 4GB RAM)
+- Un solo panel para todo, pero pierde dashboards especializados de Wazuh
+
+**Recomendacion**: empezar con **Opcion 1** (Wazuh Dashboard completo). La seguridad merece su propio panel especializado. Las alertas criticas se reenvian a Telegram/email igual que el resto.
+
+#### Recursos
+
+| Componente | CPU | RAM | Disco |
+|-----------|-----|-----|-------|
+| Wazuh Manager + Dashboard (OpenSearch) | 4 vCPU | 12 GB | 100 GB |
+| Wazuh Agent (por servidor) | <0.1 | 50-100 MB | 100 MB |
+
+**VM dedicada recomendada**: no compartir con la VM de monitoring (VictoriaMetrics/Loki/Grafana). Si Wazuh detecta un problema, debe estar en infra separada.
+
+#### Despliegue
+
+```bash
+# Manager (VM dedicada)
+curl -sO https://packages.wazuh.com/4.9/wazuh-install.sh
+bash wazuh-install.sh -a # instalacion all-in-one
+
+# Agent (en cada servidor, via Ansible)
+apt-get install wazuh-agent
+# Configurar manager IP y registrar
+```
+
+### Opcion B: AIDE + auditd (ligero, sin servidor central)
+
+#### Cuando tiene sentido
+- Si no quereis/podeis mantener un Wazuh Manager
+- Como complemento a Wazuh (belt and suspenders)
+- Para servidores aislados sin conectividad al manager
+
+#### AIDE (file integrity)
+
+```bash
+# Instalar y generar baseline
+apt install aide
+aide --init
+mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
+
+# Check diario via cron
+0 5 * * * /usr/bin/aide --check | mail -s "AIDE $(hostname)" seguridad@empresa.com
+
+# Tras cambios legitimos, regenerar baseline
+aide --update
+```
+
+Configuracion para PBS:
+```
+# /etc/aide/aide.conf.d/99_pbs_custom.conf
+/etc/pam.d Full
+/etc/ssh Full
+/etc/ld.so.preload Full
+/usr/bin Full
+/usr/sbin Full
+/usr/local/bin Full
+/usr/local/sbin Full
+/root/.ssh Full
+/etc/cron.d Full
+/etc/systemd/system Full
+```
+
+#### auditd (registro de eventos)
+
+```bash
+# /etc/audit/rules.d/pbs-security.rules
+
+# Monitorizar cambios en PAM
+-w /etc/pam.d/ -p wa -k pam_changes
+
+# Monitorizar ld.so.preload
+-w /etc/ld.so.preload -p wa -k ld_preload
+
+# Monitorizar SSH config
+-w /etc/ssh/ -p wa -k ssh_changes
+
+# Monitorizar authorized_keys
+-w /root/.ssh/authorized_keys -p wa -k ssh_keys
+
+# Monitorizar crontabs
+-w /etc/crontab -p wa -k cron_changes
+-w /etc/cron.d/ -p wa -k cron_changes
+-w /var/spool/cron/ -p wa -k cron_changes
+
+# Monitorizar creacion de ficheros en /usr/bin
+-w /usr/bin/ -p wa -k system_binaries
+-w /usr/sbin/ -p wa -k system_binaries
+-w /usr/local/bin/ -p wa -k local_binaries
+-w /usr/local/sbin/ -p wa -k local_binaries
+
+# Monitorizar sudoers
+-w /etc/sudoers -p wa -k sudoers
+-w /etc/sudoers.d/ -p wa -k sudoers
+
+# Monitorizar systemd
+-w /etc/systemd/system/ -p wa -k systemd_changes
+
+# Monitorizar ejecucion de herramientas sospechosas
+-a always,exit -F arch=b64 -S execve -F path=/usr/bin/curl -k suspicious_exec
+-a always,exit -F arch=b64 -S execve -F path=/usr/bin/wget -k suspicious_exec
+-a always,exit -F arch=b64 -S execve -F path=/usr/bin/base64 -k suspicious_exec
+```
+
+#### Limitaciones sin Wazuh
+- auditd registra pero **no alerta** (hay que leer los logs o enviarlos a Loki)
+- AIDE detecta cambios pero **solo en el check programado** (no real-time)
+- No hay correlacion de eventos entre servidores
+- No hay vulnerability scanning
+- No hay active response
+
+### Opcion C: Lynis (auditoria puntual, no monitoring continuo)
+
+#### Que es
+Script de auditoria de seguridad que escanea el sistema y da puntuacion con recomendaciones. No es un agente permanente.
+
+#### Uso
+```bash
+apt install lynis
+lynis audit system
+```
+
+#### Donde encaja
+- Ejecutar tras el despliegue inicial de cada servidor (baseline)
+- Ejecutar periodicamente (mensual) para verificar drift
+- Generar informe de hardening pendiente
+- **No reemplaza** ni a Wazuh ni a auditd, es complementario
+
+### Comparativa 2.1
+
+| Aspecto | Wazuh | AIDE + auditd | Lynis |
+|---------|-------|--------------|-------|
+| Deteccion real-time | Si (FIM + log analysis) | auditd si, AIDE no | No (puntual) |
+| File integrity | Si (integrado) | AIDE (cron) | Check puntual |
+| Rootkit detection | Si | No | Basico |
+| Vulnerability scan | Si | No | Basico |
+| Log analysis | Si (reglas de correlacion) | Hay que leer logs | No |
+| Active response | Si (bloquear IP, matar proceso) | No | No |
+| Alertas | Si (email, Telegram, webhook) | Manual (cron + mail) | Informe |
+| Servidor central | Si (Manager) | No | No |
+| RAM por servidor | 50-100 MB | 10-20 MB | Solo durante scan |
+| Complejidad | Media-alta | Baja | Nula |
+
+### DECISION 2.1 (confirmada)
+
+**Wazuh como pilar principal** + **auditd como complemento** en todos los servidores + **Lynis** como check puntual post-despliegue.
+
+- Wazuh: deteccion continua, alertas, correlacion, vulnerability scanning
+- auditd: registro granular de quien hizo que (forensics, compliance)
+- Lynis: auditoria de hardening tras cada despliegue y revision trimestral
+
+**Visualizacion**: Wazuh Dashboard propio (OpenSearch). La seguridad merece su panel especializado con modulos dedicados (FIM, rootkits, vulnerabilidades, compliance). Al no tener experiencia previa en HIDS, los dashboards especializados aceleran el aprendizaje. Alertas criticas se reenvian a Telegram/email igualmente.
+
+**Lynis como checklist**: se ejecuta en 4 momentos:
+1. Instalacion nueva (baseline)
+2. Post-hardening (verificar que Ansible aplico todo)
+3. Revision periodica trimestral (detectar drift)
+4. Post-incidente (evaluar estado tras limpieza)
+
+**Overhead por servidor**: 50-100MB RAM del agente Wazuh. Perfectamente asumible en servidores de 32-256GB (pasa completamente desapercibido).
+
+**Recursos VM Wazuh**: 4 vCPU, 12 GB RAM (8 Manager + 4 Dashboard/OpenSearch), 100 GB disco. VM dedicada separada de monitoring.
+
+---
+
+## 2.2 Escaneo de vulnerabilidades
+
+### Que necesitamos
+
+Dos tipos de escaneo:
+
+| Tipo | Que busca | Desde donde |
+|------|-----------|-------------|
+| **Interno (host)** | Paquetes con CVEs, configuraciones debiles | Dentro de cada servidor |
+| **Externo (red)** | Puertos abiertos, servicios expuestos, CVEs accesibles | Desde fuera del servidor |
+
+### Escaneo interno: ya cubierto por Wazuh
+
+Wazuh incluye vulnerability detection que escanea los paquetes instalados contra bases de CVE (NVD, Debian Security). Si hemos desplegado Wazuh, esto ya esta cubierto.
+
+### Escaneo externo: OpenVAS/Greenbone
+
+| Herramienta | Que hace | Recursos |
+|-------------|----------|----------|
+| **OpenVAS/Greenbone** | Escaner de vulnerabilidades de red. Escanea puertos, servicios, prueba CVEs conocidos. | VM dedicada: 4 vCPU, 8 GB RAM, 20 GB disco |
+
+**Frecuencia**: scan semanal o tras cambios significativos. No mantener scaneando continuamente (consume muchos recursos y puede afectar servicios).
+
+**Alternativa ligera**: **Nuclei** (escaner basado en templates, mucho mas ligero que OpenVAS). Ideal para servicios web (paneles Proxmox, PBS GUI, PowerDNS Admin).
+
+### Escaneo de contenedores: Trivy
+
+Para la VM del DNS y cualquier otra VM con Docker:
+
+```bash
+# Escanear imagenes en uso
+trivy image powerdns/pdns-auth
+trivy image mariadb:latest
+trivy image traefik:latest
+
+# Escanear docker-compose
+trivy config /opt/30-powerdns/docker-compose.yml
+
+# Escanear filesystem de la VM
+trivy fs --scanners vuln,misconfig /
+```
+
+**Frecuencia**: tras cada `docker pull` o actualizacion de imagenes.
+
+### Modelo de responsabilidad: VMs Linux vs Windows
+
+| Tipo | Quien gestiona | Que hacemos nosotros |
+|------|----------------|---------------------|
+| **Hosts Proxmox/PBS** | Nosotros | Wazuh agent + todo el stack de seguridad |
+| **VMs Linux** | Nosotros (mayoria) | Wazuh agent + auditd + hardening |
+| **VMs Windows** | El cliente | Solo monitoring externo |
+
+Para VMs Windows de clientes (IaaS):
+- **Monitorizar desde fuera**: Uptime Kuma (disponibilidad), trafico anomalo desde el host (mineria, C2, DDoS)
+- **NO instalar agentes dentro**, NO gestionar parches, NO escanear vulnerabilidades
+- Si la VM "se desmadra", tenemos visibilidad de red/recursos para orientar al cliente
+
+### DECISION 2.2 (confirmada)
+
+| Herramienta | Ambito | Frecuencia | Prioridad |
+|-------------|--------|------------|-----------|
+| **Wazuh vuln detection** | Paquetes en hosts + VMs Linux gestionadas | Continuo (ya viene con Wazuh) | Fase 1 (gratis, ya incluido) |
+| **Trivy** | Imagenes Docker en VMs | Tras updates de imagenes | Fase 2 |
+| **Nuclei** | Servicios web expuestos (PBS:8007, PVE:8006, paneles) | Semanal (programado en Semaphore) | Fase 2 (mes 2-3) |
+| **OpenVAS** | ~~Escaneo de red completo~~ | ~~Mensual~~ | **Aparcado** salvo requisitos de compliance (ISO 27001, PCI-DSS) |
+| **VMs Windows** | Solo monitoring externo (red, disponibilidad) | Continuo desde el host | Siempre |
+
+OpenVAS descartado por ahora: con Wazuh + Nuclei cubrimos el 90%. OpenVAS requiere VM dedicada (4 vCPU, 8GB RAM) y solo aporta valor marginal sin compliance obligatorio.
+
+---
+
+## 2.3 Gestion de secretos e identidad
+
+### El problema real
+
+En este entorno encontramos:
+- Contraseñas en docker-compose.yml en texto plano (`PDNS_API_KEY=secret`, `MYSQL_ROOT_PASSWORD`)
+- Contraseña de PBS en scripts (`modificar.sh` recibe password como argumento)
+- API tokens en ficheros de configuracion
+- Password de root capturada y enviada al atacante
+- ~50 contraseñas distintas para diferentes servicios, compartidas por chat
+
+### Dos estrategias complementarias
+
+No basta con custodiar secretos (guardarlos bien). Tambien hay que **reducir la cantidad de secretos** (SSO):
+
+| Estrategia | Que hace | Herramienta |
+|------------|----------|-------------|
+| **Custodiar secretos** | Guardar 50 contraseñas de forma segura | Vaultwarden |
+| **Reducir secretos** | Tener 1 login con MFA que abre todo (SSO) | **Authentik** |
+
+### Authentik: Identity Provider centralizado
+
+**Que es**: Identity Provider open source (SSO, SAML, OAuth2, OIDC, LDAP provider). UI moderna e intuitiva.
+
+**Por que Authentik y no Keycloak/Authelia**:
+
+| Aspecto | Authentik | Keycloak | Authelia |
+|---------|-----------|----------|----------|
+| UI | Moderna, intuitiva | Funcional pero anticuada | Minima (solo login) |
+| LDAP provider | Si (emite LDAP) | Si | No |
+| Proxy de autenticacion | Si (protege apps sin SSO nativo) | No nativo | Si (es su funcion) |
+| MFA integrado | TOTP, WebAuthn, Duo | TOTP, WebAuthn | TOTP, WebAuthn |
+| Flows personalizables | Si (drag & drop) | Si (mas complejo) | No |
+| Recursos | 2 vCPU, 2 GB RAM | 2 vCPU, 4 GB RAM | 1 vCPU, 512 MB RAM |
+| Comunidad | Creciente, muy activa | Grande, madura | Pequeña |
+
+**Que servicios se ponen detras de Authentik (SSO)**:
+
+| Servicio | Soporte SSO | Como |
+|----------|-------------|------|
+| **Proxmox VE** | OpenID Connect (nativo desde PVE 7) | Configuracion directa |
+| **Proxmox Backup Server** | OpenID Connect (nativo) | Configuracion directa |
+| **Grafana** | OAuth2/OIDC nativo | Configuracion directa |
+| **Netbox** | OAuth2/OIDC nativo | Plugin SSO |
+| **Forgejo** | OAuth2/OIDC nativo | Configuracion directa |
+| **Semaphore** | OIDC nativo | Configuracion directa |
+| **Wazuh Dashboard** | SAML/OpenID | Configuracion en OpenSearch |
+| **Vaultwarden** | SSO nativo (v1.29+) | Configuracion directa |
+| **PowerDNS Admin** | OAuth2 nativo | Configuracion directa |
+| **n8n** | OAuth2 nativo | Configuracion directa |
+| **Uptime Kuma** | No nativo | Authentik proxy delante |
+| **phpMyAdmin** | No nativo | Authentik proxy delante |
+
+**Impacto**: un tecnico, una cuenta, un MFA, acceso a todo. Baja de la empresa = desactivar 1 cuenta, no 50.
+
+### Evolucion de la gestion de secretos
+
+```
+FASE 0 (actual):
+ 50 contraseñas distintas → en la cabeza, en un .txt, en Telegram
+
+FASE 1 (dia 1 - Vaultwarden + .env):
+ 50 contraseñas distintas → custodiadas en Vaultwarden
+ Secretos en docker-compose → ficheros .env con chmod 600
+
+FASE 2 (dia 1 - Authentik):
+ 1 cuenta Authentik con MFA → abre PVE, PBS, Grafana, Netbox,
+ Forgejo, Semaphore, Wazuh...
+ ~10 contraseñas residuales → custodiadas en Vaultwarden
+ (APIs, root de emergencia, clientes)
+
+FASE 3 (mes 6+ - HashiCorp Vault):
+ Authentik → humanos
+ Vault → maquinas (Ansible, CI/CD, API tokens con rotacion)
+ Vaultwarden → secretos residuales del equipo
+```
+
+### Orden de despliegue critico
+
+**Authentik debe desplegarse ANTES que los demas servicios** en el servidor nuevo. Si no, cada servicio nace con contraseña local y hay que reconfigurarlo despues.
+
+Orden correcto:
+```
+Dia 1: Forgejo (git) → para versionar todo desde el minuto 0
+Dia 1: Authentik → para que todo lo siguiente ya nazca con SSO
+Dia 1: Vaultwarden → con SSO de Authentik
+Dia 2: Wazuh Manager → con SSO
+Dia 2: VictoriaMetrics + Grafana → con SSO
+Dia 3: Loki + Alloy
+Dia 3: Netbox → con SSO
+Dia 4: Semaphore → con SSO
+... Todo nace autenticado contra Authentik
+```
+
+### Vaultwarden (gestor de contraseñas para el equipo)
+
+**Que es**: fork self-hosted de Bitwarden. Gestor de contraseñas para equipos.
+
+**Que resuelve**:
+- Contraseñas residuales (APIs, root de emergencia, credenciales de clientes)
+- Generacion de contraseñas fuertes
+- Audit log de quien accedio a que secreto
+- Eliminacion de contraseñas en chats/post-its
+
+**Despliegue**: un contenedor Docker, 256MB RAM, 1GB disco.
+
+```yaml
+services:
+ vaultwarden:
+ image: vaultwarden/server:latest
+ container_name: vaultwarden
+ restart: unless-stopped
+ volumes:
+ - ./data:/data
+ environment:
+ - ADMIN_TOKEN=${ADMIN_TOKEN}
+ - SIGNUPS_ALLOWED=false
+```
+
+### HashiCorp Vault (secretos para automatizacion) - MES 6+
+
+**Cuando implementar**: cuando Ansible este en produccion y la automatizacion lo justifique. No antes.
+
+**Que resuelve**:
+- Secretos en docker-compose.yml → referencia a Vault
+- Passwords en playbooks → Ansible lee de Vault
+- API tokens → rotacion automatica
+- Certificados → PKI interna
+
+### DECISION 2.3 (confirmada)
+
+| Fase | Herramienta | Que cubre | Esfuerzo |
+|------|-------------|-----------|----------|
+| **Dia 1** | **Vaultwarden** | Custodiar contraseñas del equipo | 30 min |
+| **Dia 1** | **Ficheros .env** (chmod 600) | Sacar secretos de docker-compose | 1h |
+| **Dia 1** | **Authentik** | SSO + MFA centralizado para todos los paneles | 1-2 dias |
+| **Mes 6+** | **HashiCorp Vault** | Secretos para automatizacion (Ansible, CI/CD) | 1 semana |
+
+Recursos Authentik: 2 vCPU, 2 GB RAM, 20 GB disco (contenedor Docker o VM ligera).
+
+---
+
+## 2.4 Acceso y autenticacion
+
+### El problema real
+
+- SSH con password habilitado (cloud-init lo reactivo)
+- Solo clave SSH de `david@ansible` como acceso
+- Sin MFA
+- Sin registro centralizado de quien accede a que servidor
+- Sin grabacion de sesiones SSH (no hay audit de que hizo cada tecnico)
+- PBS:8007 abierto a internet con autenticacion basica
+
+### Paneles web: ya cubierto por Authentik (2.3)
+
+Con Authentik desplegado, todos los paneles web (PVE, PBS, Grafana, Netbox, Forgejo, Wazuh, etc.) tienen SSO + MFA centralizado. No hace falta configurar TOTP por separado en cada panel.
+
+### SSH: Bastion host con grabacion de sesiones
+
+#### Por que bastion y no Teleport
+
+Se evaluo Teleport pero se descarto por:
+- **Licencia AGPL con riesgo** de features migrando a enterprise (patron HashiCorp)
+- **Cliente propietario** (`tsh`): no es SSH estandar, requiere instalar software en cada maquina de tecnico
+- **Complejidad adicional** para Ansible (requiere configurar proxy SSH especial)
+
+El bastion SSH usa **SSH estandar** (OpenSSH `ProxyJump`, disponible desde 2016):
+
+| Cliente | Soporte ProxyJump (-J) |
+|---------|----------------------|
+| Linux (OpenSSH) | Si, nativo |
+| macOS Terminal / iTerm | Si, nativo |
+| Windows 10/11 (OpenSSH) | Si, preinstalado |
+| MobaXterm | Si |
+| WinSCP | Si (transferencia de ficheros) |
+| Ansible | Si (`ansible_ssh_common_args`) |
+| PuTTY | No directo (config propia, pero casi nadie lo usa ya) |
+
+#### Como funciona
+
+```
+┌──────────┐ ┌─────────────────────────────┐ ┌──────────────┐
+│ Tecnico │────▶│ BASTION (VM pequeña) │────▶│ Servidor PBS │
+│ │ │ │ └──────────────┘
+│ ssh -J │ │ - ForceCommand graba sesion │────▶│ Servidor PVE │
+│ bastion │ │ (script/asciinema) │ └──────────────┘
+│ servidor │ │ - Grabacion → Loki │────▶│ Servidor DNS │
+│ │ │ - Audit log completo │ └──────────────┘
+└──────────┘ │ - Solo IP permitida en │
+ │ firewalls de servidores │
+ └─────────────────────────────┘
+```
+
+Los servidores destino **solo aceptan SSH desde el bastion**:
+
+```bash
+# Firewall de cada servidor de produccion (via Ansible)
+iptables -A INPUT -p tcp --dport 22 -s IP_BASTION -j ACCEPT
+iptables -A INPUT -p tcp --dport 22 -j DROP
+```
+
+No hay forma de saltarse el bastion.
+
+#### Configuracion del tecnico (una sola vez)
+
+```
+# ~/.ssh/config del tecnico
+Host bastion
+ HostName bastion.empresa.local
+ User david
+ IdentityFile ~/.ssh/id_ed25519
+
+Host *.infra
+ ProxyJump bastion
+ User root
+ IdentityFile ~/.ssh/id_ed25519
+```
+
+Uso diario (nada cambia):
+
+```bash
+# SSH normal, transparente, el tecnico ni nota el bastion:
+ssh servidor-pbs.infra
+ssh servidor-pve.infra
+scp fichero.tar.gz servidor-pbs.infra:/tmp/
+
+# Ansible tambien funciona sin cambios:
+ansible all -m ping
+ansible-playbook -i inventory/netbox.yml playbooks/hardening.yml
+```
+
+#### Grabacion de sesiones
+
+El bastion usa `ForceCommand` en sshd_config para grabar cada sesion con `script` o `asciinema`:
+- Cada sesion se guarda con: fecha, usuario, servidor destino, duracion
+- Se puede reproducir con `scriptreplay` o `asciinema play`
+- Los logs van a Loki para busqueda centralizada
+- No es un video bonito como Teleport, pero es texto completo con replay y timing
+
+#### Recursos
+
+VM bastion: 1 vCPU, 1 GB RAM, 20 GB disco. Minima.
+
+### Hardening SSH (Ansible playbook, dia 1)
+
+Cada servidor nuevo nace con este hardening aplicado automaticamente:
+
+```yaml
+# Hardening SSH base
+- PasswordAuthentication: "no"
+- KbdInteractiveAuthentication: "no"
+- PermitRootLogin: "prohibit-password" # solo clave
+- MaxAuthTries: 3
+- LoginGraceTime: 30
+- AllowUsers: "root david" # solo usuarios conocidos
+# Neutralizar cloud-init SSH override
+- /etc/cloud/cloud.cfg.d/99-ssh-hardening.cfg: "ssh_pwauth: false"
+# Firewall: SSH solo desde bastion
+- iptables: -A INPUT -p tcp --dport 22 -s IP_BASTION -j ACCEPT
+# Fail2ban
+- Instalar y configurar con ban de 1h tras 5 intentos
+```
+
+### Fail2ban (dia 1)
+
+```ini
+# /etc/fail2ban/jail.d/custom.conf
+[sshd]
+enabled = true
+port = ssh
+filter = sshd
+maxretry = 5
+findtime = 600
+bantime = 3600
+action = %(action_mwl)s
+
+[proxmox]
+enabled = true
+port = 8006
+filter = proxmox
+maxretry = 5
+bantime = 3600
+```
+
+### CrowdSec (semana 2)
+
+**Que es**: IPS colaborativo open source. Analiza logs, detecta ataques, y comparte IPs maliciosas con la comunidad.
+
+**Diferencia con Fail2ban**:
+- Fail2ban: reactivo, local, solo tu ves tus ataques
+- CrowdSec: proactivo, colaborativo, bloqueas IPs que atacan a otros antes de que te ataquen a ti
+
+**Complementa** a Fail2ban, no lo reemplaza.
+
+```bash
+curl -s https://install.crowdsec.net | bash
+cscli collections install crowdsecurity/sshd
+cscli collections install crowdsecurity/linux
+```
+
+### Flujo de acceso completo para un tecnico
+
+```
+Tecnico quiere acceder a:
+
+ Panel PVE/PBS/Grafana/Netbox/Wazuh/Forgejo:
+ → Authentik SSO → MFA → acceso
+ → 1 cuenta, 1 password, 1 TOTP
+
+ SSH a un servidor:
+ → ssh servidor.infra (transparente, pasa por bastion)
+ → Clave SSH (sin password)
+ → Bastion graba toda la sesion
+ → Fail2ban + CrowdSec protegen
+ → auditd registra todo dentro del servidor
+ → Wazuh alerta si algo raro
+
+ Secreto de un cliente:
+ → Vaultwarden (SSO via Authentik) → buscar → copiar
+ → Audit log de quien accedio
+```
+
+### DECISION 2.4 (confirmada)
+
+| Fase | Accion | Herramienta |
+|------|--------|-------------|
+| **Dia 1** | SSO + MFA para todos los paneles web | Authentik |
+| **Dia 1** | Bastion SSH con grabacion de sesiones | VM bastion + asciinema |
+| **Dia 1** | Hardening SSH en cada servidor nuevo | Ansible playbook |
+| **Dia 1** | Fail2ban en cada servidor nuevo | Ansible playbook |
+| **Cada servidor nuevo** | Firewall: SSH solo desde bastion | Ansible playbook |
+| **Semana 2** | CrowdSec en servidores expuestos | Ansible playbook |
+
+---
+
+## Resumen Capa 2 completa (DECISIONES FINALES)
+
+```
+┌─────────────────────────────────────────────────────┐
+│ CAPA 2: SEGURIDAD │
+├─────────────────────────────────────────────────────┤
+│ │
+│ 2.1 DETECCION │
+│ ┌───────────────────────────────────┐ │
+│ │ Wazuh Manager + Dashboard │ │
+│ │ (VM dedicada, 4vCPU, 12GB RAM) │ │
+│ │ - FIM (file integrity real-time) │ │
+│ │ - Log analysis + correlacion │ │
+│ │ - Rootkit detection │ │
+│ │ - Vulnerability detection │ │
+│ │ - Active response │ │
+│ │ + auditd en cada servidor │ │
+│ │ + Lynis post-despliegue/trimest. │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 2.2 VULNERABILIDADES │
+│ ┌───────────────────────────────────┐ │
+│ │ Wazuh vuln detection (continuo) │ │
+│ │ Trivy (contenedores Docker) │ │
+│ │ Nuclei (servicios web, semanal) │ │
+│ │ OpenVAS: DESCARTADO (salvo │ │
+│ │ compliance futuro) │ │
+│ │ VMs Windows: solo monitoring │ │
+│ │ externo (red/disp.) │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 2.3 SECRETOS E IDENTIDAD │
+│ ┌───────────────────────────────────┐ │
+│ │ Authentik (SSO + MFA, dia 1) │ │
+│ │ - PVE, PBS, Grafana, Netbox, │ │
+│ │ Forgejo, Semaphore, Wazuh, │ │
+│ │ Vaultwarden, PowerDNS Admin │ │
+│ │ Vaultwarden (secretos residuales)│ │
+│ │ .env con permisos 600 (interim) │ │
+│ │ HashiCorp Vault (mes 6+) │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 2.4 ACCESO │
+│ ┌───────────────────────────────────┐ │
+│ │ Bastion SSH con grabacion │ │
+│ │ (SSH estandar, ProxyJump -J) │ │
+│ │ Hardening SSH (Ansible, dia 1) │ │
+│ │ Fail2ban (dia 1) │ │
+│ │ CrowdSec (semana 2) │ │
+│ │ Teleport: DESCARTADO │ │
+│ │ (AGPL, cliente propietario) │ │
+│ └───────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────┘
+
+Recursos Capa 2:
+- VM Wazuh: 4 vCPU, 12 GB RAM, 100 GB disco
+- VM Bastion: 1 vCPU, 1 GB RAM, 20 GB disco
+- Authentik: 2 vCPU, 2 GB RAM, 20 GB disco (contenedor)
+- Vaultwarden: contenedor Docker, 256 MB RAM
+- Wazuh agent por servidor: 50-100 MB RAM
+- CrowdSec por servidor: ~50 MB RAM
+- Fail2ban por servidor: ~30 MB RAM
+- auditd: ya instalado, 0 extra
+```
+
+---
+
+## Prioridades de despliegue Capa 2
+
+Contexto: se monta un servidor nuevo desde cero. La infra existente esta cerrada y se migrara progresivamente. Cada servicio nuevo nace ya con SSO, monitoring y seguridad desde el minuto 0.
+
+| Prioridad | Que | Esfuerzo | Impacto |
+|-----------|-----|----------|---------|
+| **Dia 1** | Authentik (SSO + MFA) | 1-2h | Todo lo que se despliegue despues ya nace con SSO |
+| **Dia 1** | Vaultwarden (con SSO) | 30min | Secretos custodiados desde el dia 1 |
+| **Dia 1** | Bastion SSH + grabacion | 2h | Toda sesion SSH queda registrada |
+| **Dia 1** | Hardening SSH + Fail2ban (Ansible) | 2h | Cierra la puerta principal |
+| **Semana 1** | Wazuh Manager + Dashboard (con SSO) | 1 dia | Deteccion de intrusiones activa |
+| **Semana 1** | Wazuh agents en servidores criticos | 2h | FIM + rootkit detection |
+| **Semana 2** | auditd en todos los servidores (Ansible) | 2h | Registro forense completo |
+| **Semana 2** | CrowdSec en servidores con puertos publicos | 2h | Inteligencia de amenazas colaborativa |
+| **Mes 1** | Wazuh agents en todos los servidores | 1 dia | Cobertura completa |
+| **Mes 1** | Lynis baseline en todos los servidores | 2h | Hardening score por servidor |
+| **Mes 2** | Trivy en VMs con Docker | 2h | Vulnerabilidades en contenedores |
+| **Mes 2-3** | Nuclei scans semanales | 2h | Vulnerabilidades en servicios web |
+| **Mes 6+** | HashiCorp Vault | 1 semana | Secretos automatizados |
diff --git a/06-decision-automatizacion.md b/06-decision-automatizacion.md
new file mode 100644
index 0000000..ae7feb7
--- /dev/null
+++ b/06-decision-automatizacion.md
@@ -0,0 +1,542 @@
+# Decision: Capa de automatizacion (punto 3)
+
+## Contexto
+
+Actualmente la operativa del PBS se gestiona con scripts bash en `/root/` (`modificar.sh`, `lista.sh`, `gc_jobs.sh`, `verify_jobs.sh`, `status_disk.sh`, etc.). Existe una clave SSH de `david@ansible` pero Ansible se ha usado muy poco (algun apt upgrade en batch). No hay experiencia real con playbooks ni roles. A 100-500 servidores, la forma actual no escala: scripts no versionados, sin idempotencia, sin audit trail, si el servidor se pierde se pierde toda la operativa.
+
+Se desplegara un servidor nuevo desde cero donde se migraran VMs progresivamente. Ya existe un Forgejo con proyectos de desarrollo (solo git basico).
+
+3 subcapas (Terraform/OpenTofu descartado):
+- 3.1 Gestion de configuracion (Ansible + Semaphore)
+- 3.2 Control de versiones y CI/CD (Forgejo existente + Actions)
+- 3.3 Orquestacion de jobs (Semaphore + n8n futuro)
+
+---
+
+## 3.1 Gestion de configuracion: Ansible
+
+### Por que Ansible y no otras
+
+| Herramienta | Modelo | Agente | Estado |
+|-------------|--------|--------|--------|
+| **Ansible** | Push (sin agente) | Solo SSH | Estandar de facto para infra Linux |
+| **Puppet** | Pull (con agente) | Si (puppet-agent, ~200MB RAM) | En declive, comunidad menguante |
+| **Chef** | Pull (con agente) | Si (chef-client) | Comprado por Progress, futuro incierto |
+| **Salt** | Push/Pull | Opcional (salt-minion) | Bueno pero menor ecosistema |
+
+**Ansible gana por**:
+- Ya se usa en el entorno (clave SSH de `david@ansible`)
+- Sin agente: solo necesita SSH, que ya esta configurado en todos los servidores
+- Inventario dinamico desde Netbox (ya decidido en 1.1)
+- Enorme ecosistema de colecciones para Proxmox (`community.general.proxmox*`)
+- Curva de aprendizaje accesible para sysadmins (YAML, no Ruby ni DSL propios)
+- Compatible con todo lo decidido: Wazuh, VictoriaMetrics, Loki, CrowdSec, Fail2ban
+
+### De modificar.sh a Ansible: la transformacion
+
+El script `modificar.sh` actual hace en secuencia:
+1. Crear dataset ZFS con quota y reservation (multiplicador 6x)
+2. Crear datastore en PBS
+3. Crear usuario PBS
+4. Setear password
+5. Configurar ACL (DatastorePowerUser)
+6. Crear sync job (dia aleatorio 11-20, 09:XX)
+7. Crear verify job (outdated 30d, ignore-verified)
+8. Opcionalmente eliminar sync job si "migrado"
+
+Esto se transforma en un **role de Ansible** (`pbs_customer`) con tasks idempotentes:
+
+```yaml
+# roles/pbs_customer/tasks/main.yml
+---
+- name: Crear dataset ZFS
+ community.general.zfs:
+ name: "pool9/{{ customer_name }}"
+ state: present
+ extra_zfs_properties:
+ quota: "{{ customer_size_gb * 6 }}G"
+ reservation: "{{ customer_size_gb * 5 }}G"
+ recordsize: "1M"
+ compression: "lz4"
+ atime: "off"
+
+- name: Crear datastore PBS
+ ansible.builtin.uri:
+ url: "https://localhost:8007/api2/json/config/datastore"
+ method: POST
+ headers:
+ Authorization: "PBSAPIToken={{ pbs_api_token }}"
+ body_format: json
+ body:
+ name: "{{ customer_name }}"
+ path: "/pool9/{{ customer_name }}"
+ gc-schedule: "daily"
+ validate_certs: false
+ status_code: [200, 500] # 500 si ya existe
+ register: ds_result
+
+- name: Crear usuario PBS
+ ansible.builtin.command:
+ cmd: >
+ proxmox-backup-manager user create
+ {{ customer_name }}@pbs
+ --comment "Cliente {{ customer_name }}"
+ register: user_result
+ failed_when: user_result.rc != 0 and 'already exists' not in user_result.stderr
+
+- name: Setear password del usuario
+ ansible.builtin.command:
+ cmd: >
+ proxmox-backup-debug api set /access/password
+ --userid {{ customer_name }}@pbs
+ --password {{ customer_password }}
+ no_log: true # No mostrar password en logs
+
+- name: Configurar ACL
+ ansible.builtin.command:
+ cmd: >
+ proxmox-backup-manager acl update
+ /datastore/{{ customer_name }}
+ DatastorePowerUser
+ --auth-id {{ customer_name }}@pbs
+
+- name: Crear sync job
+ ansible.builtin.command:
+ cmd: >
+ proxmox-backup-manager sync-job create
+ {{ customer_name }}
+ --store {{ customer_name }}
+ --remote {{ sync_remote }}
+ --remote-store {{ customer_name }}
+ --schedule "*-{{ sync_day }} 09:{{ sync_minute }}"
+ --remove-vanished true
+ when: sync_server is defined and not sync_server.startswith('migrado')
+ register: sync_result
+ failed_when: sync_result.rc != 0 and 'already exists' not in sync_result.stderr
+
+- name: Crear verify job
+ ansible.builtin.command:
+ cmd: >
+ proxmox-backup-manager verify-job create
+ v-{{ customer_name }}
+ --store {{ customer_name }}
+ --outdated-after 30
+ --ignore-verified true
+ register: verify_result
+ failed_when: verify_result.rc != 0 and 'already exists' not in verify_result.stderr
+```
+
+Variables desde Netbox (inventario dinamico):
+
+```yaml
+# host_vars o custom fields de Netbox
+customer_name: "acme"
+customer_size_gb: 100
+customer_password: "{{ lookup('hashi_vault', 'secret/pbs/acme') }}" # Fase 2
+sync_server: "pbs3343.vpn9.com.es"
+sync_day: 15
+sync_minute: 37
+```
+
+### Playbooks base (Dia 1)
+
+| Playbook | Que hace | Prioridad |
+|----------|----------|-----------|
+| `hardening-ssh.yml` | SSH hardening completo + cloud-init fix + Fail2ban | **Dia 1** |
+| `deploy-wazuh-agent.yml` | Instalar y registrar agente Wazuh | **Dia 1** |
+| `deploy-node-exporter.yml` | node_exporter + Alloy para logs | Semana 1 |
+| `deploy-auditd.yml` | Reglas auditd para PBS/Proxmox | Semana 1 |
+| `deploy-crowdsec.yml` | CrowdSec con collections SSH + Linux | Semana 2 |
+| `pbs-customer.yml` | Provisionar/modificar cliente PBS | Semana 2 |
+| `pbs-maintenance.yml` | GC, verify, optimizar ZFS en batch | Mes 1 |
+| `lynis-audit.yml` | Ejecutar Lynis y recoger informes | Mes 1 |
+
+### Ansible UI: Semaphore vs AWX
+
+| Aspecto | Semaphore | AWX |
+|---------|-----------|-----|
+| Recursos | 1 vCPU, 512MB RAM | 4 vCPU, 8GB RAM (Kubernetes) |
+| Complejidad | Docker compose simple | Necesita K8s o docker-compose complejo |
+| RBAC | Basico (admin/user) | Completo (orgs, teams, permisos por recurso) |
+| Inventario dinamico | Via CLI plugin | Nativo con sync automatico |
+| Scheduling | Si | Si (mas potente) |
+| API | Basica | REST completa |
+| Audit log | Si | Si (mas detallado) |
+| Ideal para | Equipos de 2-5 personas | Equipos de 5-20+ personas |
+
+**Recomendacion**: **Semaphore** para empezar. Si el equipo crece mas alla de 5 personas o necesitais RBAC granular, migrar a AWX.
+
+```yaml
+# docker-compose.yml de Semaphore
+services:
+ semaphore:
+ image: semaphoreui/semaphore:latest
+ ports:
+ - "3000:3000"
+ environment:
+ SEMAPHORE_DB_DIALECT: bolt
+ SEMAPHORE_ADMIN_PASSWORD: "${SEMAPHORE_ADMIN_PASSWORD}"
+ SEMAPHORE_ADMIN_NAME: admin
+ SEMAPHORE_ADMIN_EMAIL: admin@empresa.com
+ SEMAPHORE_ADMIN: admin
+ volumes:
+ - ./data:/var/lib/semaphore
+ - ./config:/etc/semaphore
+```
+
+### Inventario dinamico desde Netbox
+
+```yaml
+# inventory/netbox.yml
+plugin: netbox.netbox.nb_inventory
+api_endpoint: https://netbox.empresa.local
+token: "{{ lookup('env', 'NETBOX_TOKEN') }}"
+validate_certs: false
+
+# Agrupar por rol
+group_by:
+ - device_roles
+ - platforms
+ - regions
+ - sites
+ - tenants
+
+# Custom fields como variables de host
+compose:
+ pbs_commercial_size_gb: custom_fields.pbs_commercial_size_gb
+ pbs_quota_gb: custom_fields.pbs_quota_gb
+ pbs_sync_server: custom_fields.pbs_sync_server
+ ansible_host: primary_ip4.address | split('/') | first
+```
+
+Flujo completo:
+```
+ICSManager → API → Netbox (crea device + custom fields)
+ ↓
+ Ansible lee inventario
+ ↓
+ Ejecuta playbook pbs-customer.yml
+ ↓
+ PBS provisionado + monitorizado
+```
+
+---
+
+## 3.2 Control de versiones y CI/CD
+
+### El problema
+
+Todos los scripts operativos (`modificar.sh`, `lista.sh`, `gc_jobs.sh`, `verify_jobs.sh`, `status_disk.sh`, `enable-maintenance.sh`, etc.) estan en `/root/` sin control de versiones. Si:
+- El servidor se pierde → se pierde toda la operativa
+- Un tecnico modifica un script y rompe algo → no hay rollback
+- Dos tecnicos modifican el mismo script → conflictos sin resolver
+- No hay registro de quien cambio que ni cuando
+
+### DECISION: Forgejo existente (ya desplegado)
+
+Ya tenemos un Forgejo en produccion con proyectos de desarrollo. No hay que desplegar nada nuevo, solo ampliar su uso a infraestructura.
+
+Funcionalidades actuales en uso: solo git basico (push, pull, branches).
+Funcionalidades a activar: Forgejo Actions (CI/CD), branch protection, repos de infraestructura.
+
+### Estructura de repositorios
+
+```
+empresa/
+├── ansible-playbooks/ # Todos los playbooks y roles
+│ ├── roles/
+│ │ ├── pbs_customer/
+│ │ ├── hardening_ssh/
+│ │ ├── deploy_wazuh/
+│ │ ├── deploy_node_exporter/
+│ │ ├── deploy_alloy/
+│ │ ├── deploy_crowdsec/
+│ │ └── deploy_auditd/
+│ ├── playbooks/
+│ │ ├── site.yml
+│ │ ├── pbs-customer.yml
+│ │ ├── hardening.yml
+│ │ └── maintenance.yml
+│ ├── inventory/
+│ │ └── netbox.yml
+│ └── .forgejo/workflows/
+│ └── lint.yml # ansible-lint en cada push
+│
+├── pbs-scripts/ # Scripts operativos (migrados de /root)
+│ ├── gc_jobs.sh
+│ ├── verify_jobs.sh
+│ ├── status_disk.sh
+│ └── pbs-mantenimiento-secuencial.sh
+│
+├── monitoring-config/ # Configuracion de monitoring
+│ ├── grafana/dashboards/
+│ ├── victoriametrics/
+│ ├── vmagent/
+│ ├── alertmanager/
+│ └── loki/
+│
+├── docker-stacks/ # Docker compose de servicios
+│ ├── dns-powerdns/
+│ ├── vaultwarden/
+│ ├── semaphore/
+│ └── uptime-kuma/
+│
+└── docs/ # Documentacion como codigo
+ ├── runbooks/
+ ├── architecture/
+ └── onboarding/
+```
+
+### CI/CD con Forgejo Actions
+
+```yaml
+# .forgejo/workflows/ansible-lint.yml
+name: Lint Ansible
+on: [push, pull_request]
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Run ansible-lint
+ uses: ansible/ansible-lint-action@v6
+ with:
+ path: "playbooks/"
+```
+
+```yaml
+# .forgejo/workflows/deploy-hardening.yml
+name: Deploy Hardening
+on:
+ push:
+ branches: [main]
+ paths: ['roles/hardening_ssh/**', 'playbooks/hardening.yml']
+jobs:
+ deploy:
+ runs-on: self-hosted # Runner en la VM de automatizacion
+ steps:
+ - uses: actions/checkout@v4
+ - name: Run hardening playbook
+ run: |
+ ansible-playbook -i inventory/netbox.yml playbooks/hardening.yml --diff
+```
+
+---
+
+## 3.3 Infraestructura como codigo: DESCARTADO
+
+### Terraform / OpenTofu / Pulumi: no aplica
+
+Descartados porque:
+- Los servidores son fisicos dedicados con VMs que viven meses o años
+- No hay infraestructura efimera (crear/destruir continuamente)
+- No hay multi-cloud (todo es Proxmox on-premise)
+- Ansible cubre el 100% de lo que se necesita: configurar lo que ya existe
+
+Si algun dia se necesita crear 50 VMs identicas de golpe (nueva sede, nuevo cluster), se puede reevaluar. Para ahora, añadiria complejidad sin beneficio.
+
+---
+
+## 3.4 Orquestacion de jobs
+
+### El problema actual
+
+Los cron jobs actuales:
+```bash
+*/15 * * * * /root/gc_jobs.sh >> /var/log/gc_jobs.log 2>&1
+*/30 * * * * /root/verify_jobs.sh >> /var/log/verify_jobs.log 2>&1
+```
+
+Funcionan en un solo servidor. A 100+ PBS:
+- No hay visibilidad de que jobs corren donde
+- Si un job falla, nadie se entera (log en el servidor local)
+- No hay forma de pausar/cancelar un job sin SSH al servidor
+- No hay RBAC (quien puede ejecutar que)
+- No hay dependencias entre jobs
+
+**Caso real de congestion PBS**: los GC y verify jobs programados con el scheduling nativo de PBS provocaban picos de IO que congestionaban el servidor. Solucion actual: scripts "caseros" (`gc_jobs.sh`, `verify_jobs.sh`) que comprueban si hay otro proceso corriendo antes de lanzar uno nuevo, limitando la concurrencia manualmente.
+
+### DECISION 3.4 (confirmada)
+
+#### Semaphore como scheduler (reemplaza cron)
+
+Si ya usamos Semaphore como UI de Ansible (decidido en 3.1), podemos usarlo tambien para scheduling de jobs:
+
+- **GC jobs**: playbook que consulta API de PBS, comprueba si hay GC corriendo, si no lanza el mas antiguo. Cada 15min en Semaphore.
+- **Verify jobs**: misma logica. Comprueba si hay verify activo, si no lanza el siguiente. Cada 30min.
+- **Mantenimiento**: playbook de mantenimiento secuencial, ejecutable manualmente desde la UI
+- **Hardening**: playbook de hardening, ejecutable con boton en la UI
+
+La logica de control de concurrencia actual (no lanzar si ya hay uno corriendo) se traduce directamente:
+1. **Dentro del playbook**: consulta API de PBS → si hay tarea activa, exit limpio
+2. **En Semaphore**: opcion "no ejecutar si la ejecucion anterior no ha terminado"
+3. Ambas combinadas = mismo comportamiento que los scripts actuales
+
+Ventajas sobre cron:
+- Visibilidad: ves en la UI que se ejecuto, cuando, y si fallo
+- Notificaciones: si un GC o verify falla, webhook a Telegram
+- Control: pausar/cancelar jobs desde la web sin SSH
+- Audit log de quien ejecuto que y cuando
+- Los playbooks estan en git (Forgejo)
+
+#### n8n para workflows reactivos (futuro, mes 3-6)
+
+Herramienta de tipo "si pasa X, haz Y" con editor visual de workflows. Apuntada como futura, no prioritaria.
+
+| Trigger | Accion |
+|---------|--------|
+| Wazuh detecta fichero nuevo en /usr/bin | Enviar alerta Telegram + ejecutar audit playbook |
+| PBS datastore > 90% quota | Notificar al comercial del cliente |
+| Nuevo cliente en Netbox (ICSManager) | Ejecutar playbook pbs-customer.yml |
+| Certificado SSL expira en 14 dias | Ejecutar renovacion + notificar |
+| Sync job fallido 2 veces seguidas | Escalar a nivel 2 de soporte |
+
+n8n tiene nodos de IA (OpenAI, Ollama local) que permiten analisis automatizado de eventos. Ejemplo: Wazuh detecta actividad sospechosa → n8n pasa el evento a un LLM → analiza si es falso positivo → si es real, alerta con explicacion en lenguaje claro.
+
+**Cuando introducir**: cuando Ansible + Semaphore esten rodando y el equipo tenga soltura. No antes del mes 3.
+
+---
+
+## Resumen Capa 3 completa (DECISIONES FINALES)
+
+```
+┌─────────────────────────────────────────────────────┐
+│ CAPA 3: AUTOMATIZACION │
+├─────────────────────────────────────────────────────┤
+│ │
+│ 3.1 CONFIGURACION │
+│ ┌───────────────────────────────────┐ │
+│ │ Ansible (gestion de config) │ │
+│ │ - Sin agente (solo SSH) │ │
+│ │ - Roles: bootstrap, PBS customer,│ │
+│ │ hardening, deploy agentes │ │
+│ │ - Inventario: estatico → Netbox │ │
+│ │ - UI: Semaphore (SSO Authentik) │ │
+│ │ - API: integracion ICSManager │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 3.2 VERSION CONTROL + CI/CD │
+│ ┌───────────────────────────────────┐ │
+│ │ Forgejo EXISTENTE (no desplegar) │ │
+│ │ - Repos nuevos: infra/ansible, │ │
+│ │ infra/pbs-scripts, infra/ │ │
+│ │ monitoring-config, docker-stacks│ │
+│ │ - Actions: lint ansible (mes 1-2)│ │
+│ │ - Branch protection en main │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 3.3 IaC: DESCARTADO │
+│ ┌───────────────────────────────────┐ │
+│ │ Terraform/OpenTofu/Pulumi: no │ │
+│ │ Servidores permanentes, no cloud │ │
+│ │ Ansible cubre el 100% │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 3.4 ORQUESTACION (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Semaphore (scheduling de jobs) │ │
+│ │ - Reemplaza cron jobs │ │
+│ │ - Control concurrencia PBS │ │
+│ │ - Notif. fallos → Telegram │ │
+│ │ n8n (workflows reactivos, mes 3+)│ │
+│ │ - Futuro, con posible IA │ │
+│ └───────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────┘
+
+Recursos adicionales:
+- Semaphore: contenedor Docker en VM servicios, 512 MB RAM
+- Forgejo: YA DESPLEGADO (no requiere recursos nuevos)
+- n8n (futuro): contenedor Docker, 512 MB RAM
+```
+
+---
+
+## Prioridades de despliegue Capa 3
+
+Contexto: equipo con experiencia minima en Ansible (solo apt upgrade en batch). Forgejo ya existe con proyectos de desarrollo. Servidor nuevo desde cero.
+
+| Prioridad | Que | Esfuerzo | Impacto |
+|-----------|-----|----------|---------|
+| **Dia 1** | Crear repos infra en Forgejo existente | 1h | Scripts versionados desde el minuto 0 |
+| **Dia 1** | Migrar scripts de /root/ a repo git | 1h | Backup de toda la operativa |
+| **Semana 1** | Primer playbook: apt upgrade en todos (lo que ya sabian) | 2h | Confianza con Ansible |
+| **Semana 1** | Playbook bootstrap-server.yml (hardening + agentes) | 4h | Cada servidor nuevo nace seguro |
+| **Semana 2** | Instalar Semaphore (SSO Authentik) | 2h | UI web para ejecutar playbooks |
+| **Semana 2** | Playbook deploy-wazuh-agent.yml | 2h | Wazuh desplegado en masa |
+| **Mes 1** | Inventario dinamico Netbox → Ansible | 1 dia | Fuente unica de verdad |
+| **Mes 1** | Role pbs_customer (reemplazar modificar.sh) | 1 dia | Provisionamiento idempotente |
+| **Mes 1** | Migrar cron jobs a Semaphore scheduling | 2h | Audit log, notificaciones de fallo |
+| **Mes 1-2** | Forgejo Actions: ansible-lint en cada push | 2h | Validacion automatica |
+| **Mes 1-2** | Branch protection en repos de infra | 30min | Review obligatorio |
+| **Mes 3+** | n8n para workflows reactivos | 1 dia | Automatizacion de respuestas a eventos |
+
+---
+
+## Diagrama del stack completo hasta ahora (Capas 1-3)
+
+```
+ ┌──────────────────┐
+ │ GRAFANA │
+ │ (UI unificada) │
+ └────────┬─────────┘
+ │
+ ┌───────────────────┼───────────────────┐
+ │ │ │
+ ┌─────────▼─────────┐ ┌──────▼──────┐ ┌─────────▼─────────┐
+ │ VictoriaMetrics │ │ Loki │ │ Wazuh Dashboard │
+ │ (metricas) │ │ (logs) │ │ (seguridad) │
+ └─────────┬─────────┘ └──────┬──────┘ └─────────┬─────────┘
+ │ │ │
+ ┌─────────▼─────────┐ │ ┌─────────▼─────────┐
+ │ vmagent (scraper) │ │ │ Wazuh Manager │
+ └─────────┬─────────┘ │ └─────────┬─────────┘
+ │ │ │
+ ╔══════════╪═══════════════════╪════════════════════╪══════════╗
+ ║ Cada servidor de produccion: ║
+ ║ ┌──────────────┐ ┌────────┐ ┌───────────┐ ┌─────────────┐ ║
+ ║ │node_exporter │ │ Alloy │ │Wazuh Agent│ │ CrowdSec │ ║
+ ║ │(metricas) │ │(logs) │ │(FIM+HIDS) │ │ + Fail2ban │ ║
+ ║ │ 5MB RAM │ │ 30-50MB│ │ 50-100MB │ │ ~80MB │ ║
+ ║ └──────────────┘ └────────┘ └───────────┘ └─────────────┘ ║
+ ║ + auditd (ya instalado, 0 extra) ║
+ ║ Total overhead por servidor: ~200-250MB RAM ║
+ ╚═════════════════════════════════════════════════════════════╝
+
+ ┌─────────────────┐ ┌──────────────────┐
+ │ Netbox (CMDB) │────▶│ Ansible │
+ │ ← ICSManager │ │ + Semaphore (UI) │
+ └─────────────────┘ │ + Forgejo (git) │
+ └──────────────────┘
+
+ ┌─────────────────┐ ┌──────────────────┐
+ │ Uptime Kuma │ │ Vaultwarden │
+ │ (checks binarios│ │ (passwords equipo│
+ └─────────────────┘ └──────────────────┘
+
+VMs centrales (modelo: 1 VM = 1 servicio, caja negra):
+- VM Monitoring: 4 vCPU, 8-12 GB RAM, 200-500 GB disco
+- VM Wazuh: 4 vCPU, 12 GB RAM, 100 GB disco
+- VM Zammad: 2-3 vCPU, 4-6 GB RAM, 50 GB disco
+- VM Authentik: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
+- VM Netbox: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
+- VM ICSManager: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
+- VM WildDuck: 1-2 vCPU, 2-3 GB RAM, 30-50 GB disco
+- VM Outline: 1 vCPU, 1-2 GB RAM, 20 GB disco
+- VM Servicios: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
+ (Semaphore + Uptime Kuma + Technitium + Traefik, stateless)
+- VM Bastion: 1 vCPU, 1 GB RAM, 20 GB disco
+- VM WG Hub A: 1 vCPU, 512 MB RAM, 10 GB disco (+ NAT CGNAT)
+- VM WG Hub B: 1 vCPU, 512 MB RAM, 10 GB disco
+- VM PDM: 1 vCPU, 1-2 GB RAM, 20 GB disco (Proxmox Datacenter Manager)
+- VM PMG: 1-2 vCPU, 2-3 GB RAM, 30 GB disco (Proxmox Mail Gateway)
+- VM Forgejo: YA DESPLEGADA
+- PowerDNS: ya existente
+- PBS1 (Alemania): ya existente
+- PBS2 (Polonia): servidor fisico nuevo
+- PBS3 (opcional): por cliente premium
+- Replica oficina: servidor Xeon 96GB existente (coste 0)
+
+Total infra de gestion: ~24-32 vCPU, ~44-58 GB RAM, ~580-920 GB disco
+Recomendacion servidor: 128 GB RAM, 8-12c/16-24t, 2x2TB NVMe
+```
diff --git a/07-decision-red.md b/07-decision-red.md
new file mode 100644
index 0000000..3e8c09c
--- /dev/null
+++ b/07-decision-red.md
@@ -0,0 +1,1147 @@
+# Decision: Capa de red y conectividad (punto 4)
+
+## Contexto
+
+El PBS actual tiene el puerto 8007 abierto a internet para recibir backups. A 100+ servidores, cada servicio de backup deberia ir por VPN privada. Ademas, ya existe PowerDNS y Traefik en uso. La capa de red organiza como se comunican los servidores entre si y con el exterior de forma segura.
+
+Infraestructura fisica: servidores con 2 interfaces de red (IP publica + vRack OVH 25Gbps). Todo el direccionamiento interno sera IPv6 (ULA).
+
+3 subcapas:
+- 4.1 VPN y acceso seguro entre servidores
+- 4.2 DNS y service discovery
+- 4.3 Proxy inverso y balanceo
+
+---
+
+## 4.1 VPN y acceso seguro entre servidores
+
+### El problema
+
+- PBS:8007 expuesto a internet para recibir backups de PVE remotos
+- Trafico de backup viaja por internet sin cifrado adicional (solo TLS de PBS)
+- vRack de OVH conecta servidores pero sin cifrado ni autenticacion (L2 privada, no segura)
+- Sin VPN, cada servicio nuevo expuesto requiere abrir puertos en firewall publico
+- La superficie de ataque crece con cada puerto abierto
+- Modelo actual de gateways: 1 VM gateway por cliente (NAT + WG), no escala
+
+### Opciones evaluadas
+
+| Herramienta | Modelo | Recursos | Complejidad | Estado |
+|-------------|--------|----------|-------------|--------|
+| **WireGuard** | Punto a punto, hub-spoke | Kernel module, ~0 overhead | Baja (config manual, Ansible) | **ELEGIDA** |
+| **Tailscale** | Mesh VPN, SaaS | Agente ligero | Muy baja | Descartada (SaaS, dependencia externa) |
+| **Headscale** | Tailscale self-hosted | ~200MB RAM | Baja-media | Posible evolucion futura si escala |
+| **Nebula** | Overlay con certificados | Agente ligero | Media | Descartada (menor comunidad) |
+| **Netbird** | Mesh con SSO | ~300MB RAM | Media | Descartada (muy joven) |
+| **BGP/OSPF entre hubs** | Routing dinamico | FRRouting | Alta | Descartado (overengineering a esta escala) |
+
+### DECISION 4.1 (confirmada)
+
+#### WireGuard + wgdashboard + IPv6 ULA
+
+Todo el direccionamiento WireGuard en IPv6 ULA (fd00::/8). No ruteable a internet, privado por diseño.
+
+#### Tres roles de WireGuard
+
+**Rol 1: PBS como servidor WG (backups)**
+
+Cada PBS fisico es un servidor WireGuard cuyos unicos peers son los PVE que le envian backups.
+
+```
+PBS-1 (wg server) PBS-2 (wg server)
+├── peer: PVE-A ├── peer: PVE-D
+├── peer: PVE-B └── peer: PVE-E
+└── peer: PVE-C
+```
+
+- PVE apunta su PBS storage a [fdab:1234:bkp::1]:8007 (IP WG del PBS)
+- PBS:8007 deja de escuchar en IP publica
+- Los peers PVE NO se ven entre ellos (ip forwarding desactivado, AllowedIPs /128 por peer)
+- Trafico por vRack (25Gbps) cuando estan en el mismo DC
+
+**Rol 2: WG Hub (acceso clientes + tecnicos + VMs)**
+
+Hub centralizado donde se conectan:
+- Usuarios remotos (tecnicos, clientes)
+- Mikrotiks de oficinas de clientes
+- VMs de clientes que necesitan red privada
+
+```
+WG Hub (par HA con keepalived)
+├── peer: tecnico David
+├── peer: tecnico Juan
+├── peer: cliente ACME usuario remoto
+├── peer: cliente ACME mikrotik oficina
+├── peer: VM-web ACME (en PVE-1)
+├── peer: VM-db ACME (en PVE-2)
+├── peer: cliente BETA usuario remoto
+├── peer: VM-app BETA (en PVE-1)
+└── ...
+```
+
+- ip forwarding ACTIVADO (los peers deben verse entre ellos)
+- Aislamiento por cliente con nftables: peers de ACME solo ven peers de ACME
+- VMs en PVEs distintos se ven via WG Hub (cross-PVE transparente)
+- Panel futuro para que el cliente defina granularidad peer-a-peer
+- Reemplaza el modelo de 1 gateway VM por cliente: gateways compartidos con reglas granulares
+
+**Rol 3: Mesh de gestion entre fisicos (sobre vRack)**
+
+WireGuard sobre vRack para cifrar y autenticar el trafico de gestion entre servidores fisicos.
+
+```
+Fisico 1 (vRack) ◄── WG tunnel cifrado ──► Fisico 2 (vRack)
+fdab:1234:mgmt::1 fdab:1234:mgmt::2
+```
+
+- Servicios internos (Grafana, Netbox, Wazuh, Semaphore) escuchan en IP WG mesh
+- Ansible, exporters, logs viajan por WG mesh
+- Hub-spoke: todos los fisicos conectan a un hub de gestion (o al par HA)
+
+#### Direccionamiento IPv6 ULA
+
+```
+fdab:1234:bkp::/48 → Red de backups (PBS ↔ PVE)
+ fdab:1234:bkp:1::/64 PBS-1 y sus PVEs
+ fdab:1234:bkp:2::/64 PBS-2 y sus PVEs
+
+fdab:1234:vpn::/48 → Red de clientes/tecnicos (WG Hub)
+ fdab:1234:vpn:acme::/64 Cliente ACME (VMs + usuarios)
+ fdab:1234:vpn:beta::/64 Cliente BETA
+
+fdab:1234:mgmt::/48 → Red de gestion (mesh entre fisicos)
+ fdab:1234:mgmt::1 Hub/Fisico 1
+ fdab:1234:mgmt::2 Fisico 2
+```
+
+#### Arquitectura del WG Hub
+
+Debian minimal dedicado exclusivamente a WireGuard:
+
+```
+┌──────────────────────────────────┐
+│ Debian minimal │
+│ │
+│ Kernel: WireGuard module │
+│ Host: nftables (aislamiento │
+│ por cliente) │
+│ Host: /etc/wireguard/*.conf │
+│ │
+│ Docker: wgdashboard │
+│ └─ UI web (gestion de peers) │
+│ └─ API REST (→ ICSManager) │
+│ └─ Escribe /etc/wireguard/ │
+│ └─ Recarga WG (wg syncconf) │
+│ │
+│ Nada mas. Sin PBS, sin VMs, │
+│ sin otros servicios. │
+└──────────────────────────────────┘
+```
+
+#### Alta disponibilidad: par HA con keepalived
+
+```
+┌─────────────┐ ┌─────────────┐
+│ WG Hub A │ │ WG Hub B │
+│ (MASTER) │ │ (BACKUP) │
+│ fdab::1 │ │ fdab::2 │
+└──────┬──────┘ └──────┬──────┘
+ │ │
+ └───── VIP ─────────┘
+ fdab:1234:vpn::1
+ (IP flotante)
+```
+
+- Misma config WG en ambos (mismas claves, mismos peers)
+- Keepalived gestiona VIP sobre vRack
+- Failover en <2 segundos
+- Los peers no se enteran: su endpoint es la VIP, no cambia
+- Para endpoint publico (usuarios remotos): VIP publica o DNS con TTL bajo
+
+#### Aislamiento entre clientes (nftables en el hub)
+
+```bash
+# nftables en WG Hub
+# Cliente ACME: sus peers se ven entre ellos
+nft add rule inet filter forward \
+ iifname "wg-hub" ip6 saddr fdab:1234:vpn:acme::/64 \
+ ip6 daddr fdab:1234:vpn:acme::/64 accept
+
+# Cliente BETA: sus peers se ven entre ellos
+nft add rule inet filter forward \
+ iifname "wg-hub" ip6 saddr fdab:1234:vpn:beta::/64 \
+ ip6 daddr fdab:1234:vpn:beta::/64 accept
+
+# DEFAULT: nadie ve a nadie de otro cliente
+nft add rule inet filter forward iifname "wg-hub" drop
+```
+
+Cada cliente tiene su /64. Las reglas son por subnet, no por peer individual (mas simple). Panel futuro para granularidad peer-a-peer dentro del /64.
+
+#### Cambio de modelo: gateways compartidos
+
+| Modelo actual | Modelo nuevo |
+|---|---|
+| 1 VM gateway por cliente | 2 VM WG Hub (par HA) compartidos |
+| 50 clientes = 50 VMs gateway | 50 clientes = 2 VMs hub |
+| NAT por cliente | Sin NAT (peers directos IPv6) |
+| Cada GW consume RAM/CPU | Recursos compartidos |
+| Aislamiento por VM | Aislamiento por nftables |
+| Sin HA (si el GW cae, el cliente cae) | HA con keepalived (<2 seg failover) |
+
+Mitigacion de riesgo: tests automatizados de aislamiento (playbook que verifica que cada cliente solo alcanza sus recursos).
+
+#### Exposicion resultante del servidor fisico
+
+```
+eth0 (IP publica):
+ - Firewall: solo WG UDP (1 puerto) + SSH bastion
+ - PBS:8007 CERRADO desde internet
+ - PVE:8006 CERRADO desde internet
+ - Todo lo demas: DROP
+
+eth1 (vRack 25Gbps):
+ - WG mesh cifrado (gestion + backups)
+ - Servicios internos solo escuchan en IP WG
+
+wg-hub (overlay IPv6):
+ - Clientes y tecnicos acceden a paneles por aqui
+ - :8007, :8006, Grafana, etc. solo via WG
+```
+
+#### Fase de arranque
+
+Fase inicial con solo tecnicos internos para pulir el modelo:
+1. Montar par HA de WG Hub (2 VMs Debian minimal)
+2. Peers: solo tecnicos del equipo
+3. Validar: acceso a paneles, grabacion bastion, aislamiento nftables
+4. Cuando funcione estable: abrir a clientes y VMs progresivamente
+
+#### Recursos
+
+| Componente | CPU | RAM | Disco |
+|-----------|-----|-----|-------|
+| WG Hub A (master) | 1 vCPU | 512 MB | 10 GB |
+| WG Hub B (backup) | 1 vCPU | 512 MB | 10 GB |
+| wgdashboard (Docker en Hub A) | incluido | ~200 MB | incluido |
+| WG en cada PBS (backup role) | kernel module | ~0 | ~0 |
+| WG en cada fisico (mesh role) | kernel module | ~0 | ~0 |
+
+#### Escalabilidad futura
+
+- Si un par HA no es suficiente (>200 peers): asignar clientes a hubs distintos (Hub par 1: clientes A-M, Hub par 2: clientes N-Z), con tunel WG entre hubs para cross-routing
+- Si se necesita mesh automatico: evaluar Headscale (compatible con clientes WireGuard existentes)
+- Si se necesita routing dinamico entre DCs: FRRouting con OSPF sobre tuneles WG entre hubs
+- Nada de esto es necesario para arrancar
+
+---
+
+## 4.2 DNS y service discovery
+
+### Las 4 problematicas
+
+1. **Autoritativo de clientes**: zonas de dominios de clientes (primario o secundario)
+2. **Resolver para clientes**: navegacion de VMs y usuarios WG, con bloqueo configurable
+3. **Naming automatico**: toda IP asignada tiene AAAA + PTR operativos desde el momento 0
+4. **Diagnostico self-service**: pagina donde el cliente verifica su conectividad antes de abrir ticket
+
+### Opciones evaluadas para resolver
+
+| Herramienta | Multi-tenant | Panel cliente | Bloqueo config. | API | Estado |
+|-------------|-------------|---------------|-----------------|-----|--------|
+| **Technitium DNS** | Si (por cliente/zona) | Panel web multi-usuario nativo | Si (listas por cliente) | REST completa | **ELEGIDA como resolver** |
+| **AdGuard Home** | Parcial (por IP) | Solo admin | Si (listas por IP) | REST | Descartada (no multi-tenant nativo) |
+| **Pi-hole** | No | No | Global | Limitada | Descartada |
+| **Blocky** | Si (grupos) | No tiene UI | Si (por grupo) | Si | Descartada (sin panel) |
+| **Unbound** | No | No | No | No | Descartada (solo resuelve) |
+
+### DECISION 4.2 (confirmada)
+
+#### Componente 1: PowerDNS Authoritative (ya existente)
+
+Se mantiene y amplia. No se reemplaza.
+
+| Funcion | Estado |
+|---------|--------|
+| Zonas autoritativas de clientes (primario/secundario) | Ya funciona, mantener |
+| Zona vpn6.com.es (nombres WG peers) | Crear, automatizar |
+| Zonas PTR IPv6 (reverse DNS) | Crear, automatizar |
+| API REST para creacion automatica de registros | Activar/usar |
+
+Alimentado por:
+- **wgdashboard**: al crear peer WG → API PowerDNS → AAAA + PTR en vpn6.com.es
+- **Netbox (netbox-dns plugin)**: VMs sincronizadas desde PVE → registros DNS automaticos
+- **Manual**: zonas de clientes como hasta ahora, evolucionando dominio a dominio
+
+#### Componente 2: Technitium DNS (resolver con bloqueo)
+
+**Solo como resolver**. No toca las zonas autoritativas (eso es PowerDNS).
+
+Technitium resuelve consultas DNS de:
+- VMs de clientes
+- Usuarios WG (tecnicos, clientes remotos)
+- Mikrotiks de oficinas conectados por WG
+
+Funcionalidades clave:
+- **Bloqueo multi-tenant**: cada cliente (identificado por rango IP WG) tiene su perfil de bloqueo
+- **Panel web nativo multi-usuario**: cada cliente puede entrar y ver sus queries, ajustar bloqueo
+- **Categorias de bloqueo**: malware, phishing, ads, adult, custom por cliente
+- **Dashboard de trafico DNS**: top dominios, queries bloqueadas, estadisticas
+- **API REST completa**: automatizable desde ICSManager/n8n
+- **DNSSEC validation**: verifica firmas DNS
+- **DoH/DoT upstream**: consultas cifradas a upstream (Cloudflare, Google)
+
+```
+VM cliente ──────►┌──────────────────────────┐
+ │ Technitium DNS │
+WG usuario ──────►│ (resolver + bloqueo) │───► Upstream DoH
+ │ │ (Cloudflare/Google)
+Mikrotik ────────►│ Panel por cliente: │
+ │ - ACME: malware+ads ✅ │
+ │ - BETA: solo malware ✅ │
+ │ - GAMMA: todo ✅ │
+ │ │
+ │ Zonas internas: │
+ │ → reenvio a PowerDNS │
+ │ para *.vpn6.com.es │
+ │ y zonas de clientes │
+ └──────────────────────────┘
+```
+
+Technitium reenvia a PowerDNS las consultas de zonas internas:
+- `*.vpn6.com.es` → PowerDNS (nombres WG)
+- `*.cliente.com` → PowerDNS (si somos autoritativo)
+- Todo lo demas → upstream DoH (navegacion)
+
+Recursos: 1-2 vCPU, 512MB-1GB RAM, contenedor Docker o VM ligera.
+
+#### Componente 3: Naming automatico (toda IP tiene nombre)
+
+Esquema de nombres sobre `vpn6.com.es` (dominio propio):
+
+```
+Tecnicos internos:
+ laptop.david.vpn6.com.es → fdab:1234:vpn::42
+ movil.david.vpn6.com.es → fdab:1234:vpn::43
+
+Clientes (subdominio por cliente):
+ oficina.acme.vpn6.com.es → fdab:1234:vpn:acme::1 (mikrotik)
+ portatil.juan.acme.vpn6.com.es → fdab:1234:vpn:acme::10 (usuario)
+ vm-web.acme.vpn6.com.es → fdab:1234:vpn:acme::100 (VM)
+
+Infraestructura:
+ pbs-1.mgmt.vpn6.com.es → fdab:1234:mgmt::10
+ pve-2.mgmt.vpn6.com.es → fdab:1234:mgmt::20
+ grafana.mgmt.vpn6.com.es → fdab:1234:mgmt::30
+
+Con dominio del cliente (si lo pide):
+ vpn.acme-empresa.com → CNAME a acme.vpn6.com.es
+```
+
+Flujos de creacion automatica:
+
+```
+wgdashboard crea peer
+ → nombre definido en el formulario
+ → API PowerDNS: crea AAAA + PTR
+ → El peer ya tiene rDNS + forward desde el momento 0
+
+PVE crea VM
+ → netbox-proxmox sincroniza VM a Netbox
+ → netbox-dns genera registro en PowerDNS
+ → La VM ya tiene nombre DNS automatico
+
+Ansible despliega servidor nuevo
+ → Playbook incluye task: registrar en PowerDNS via API
+ → Servidor tiene nombre desde el primer boot
+```
+
+#### Componente 4: Pagina de diagnostico self-service
+
+Panel web donde el cliente entra (SSO Authentik) y ve su estado:
+
+```
+┌─────────────────────────────────────────┐
+│ Diagnostico de conectividad │
+│ Cliente: ACME │
+│ │
+│ ✅ Tu IP: fdab:1234:vpn:acme::10 │
+│ ✅ WireGuard: conectado (handshake 3s) │
+│ ✅ DNS: resolviendo correctamente │
+│ ❌ Certificado PBS: caduca en 3 dias │
+│ ✅ PBS:8007: accesible (120ms) │
+│ ✅ Latencia WG: 12ms │
+│ ⚠️ Tunel IPsec oficina: no detectado │
+│ │
+│ [Copiar informe] [Abrir ticket] │
+└─────────────────────────────────────────┘
+```
+
+Implementacion: app web ligera (Flask/FastAPI, un contenedor Docker) que:
+1. Detecta IP origen → identifica cliente (via Netbox API o tabla local)
+2. Ejecuta checks en tiempo real contra los servicios de ese cliente
+3. Genera informe copiable para pegar en ticket
+4. Login via SSO Authentik
+
+**Prioridad**: mes 3+, desarrollo propio. No es critico para arrancar.
+
+#### Integracion PVE → DNS via Netbox
+
+```
+PVE API ──netbox-proxmox──► Netbox
+ │
+ │ VM con IP asignada
+ ▼
+ netbox-dns plugin
+ │
+ │ Sincroniza zona en PowerDNS
+ ▼
+ PowerDNS API
+ │
+ ▼
+ AAAA: vm-web.acme.vpn6.com.es
+ PTR: fdab:...::100 → vm-web.acme.vpn6.com.es
+```
+
+No hay hookscripts fragiles en PVE. Netbox es el intermediario fiable. Si la VM existe en Netbox con IP, tiene DNS.
+
+#### Arquitectura DNS completa
+
+```
+┌───────────────────────────────────────────────────────┐
+│ │
+│ AUTORITATIVO RESOLVER │
+│ ┌──────────────────┐ ┌──────────────────┐ │
+│ │ PowerDNS Auth │ │ Technitium DNS │ │
+│ │ (ya existente) │◄─reenvio──│ (nuevo) │ │
+│ │ │ zonas │ │ │
+│ │ Zonas clientes │ internas │ Resolver + │ │
+│ │ vpn6.com.es │ │ bloqueo multi- │ │
+│ │ PTR IPv6 │ │ tenant │ │
+│ │ │ │ │ │
+│ │ ← Netbox API │ │ Panel por │ │
+│ │ ← wgdashboard │ │ cliente nativo │ │
+│ │ ← Ansible │ │ │ │
+│ └──────────────────┘ │ Upstream: DoH │ │
+│ └──────────────────┘ │
+│ │
+│ NAMING AUTOMATICO DIAGNOSTICO │
+│ ┌──────────────────┐ ┌──────────────────┐ │
+│ │ Netbox (SSOT) │ │ Panel web ligero│ │
+│ │ + netbox-dns │ │ (Flask/FastAPI) │ │
+│ │ + netbox-proxmox│ │ SSO Authentik │ │
+│ │ → PowerDNS API │ │ Checks por │ │
+│ │ │ │ cliente │ │
+│ │ wgdashboard │ │ Mes 3+ │ │
+│ │ → PowerDNS API │ └──────────────────┘ │
+│ └──────────────────┘ │
+│ │
+└───────────────────────────────────────────────────────┘
+```
+
+#### Recursos DNS
+
+| Componente | CPU | RAM | Disco | Estado |
+|-----------|-----|-----|-------|--------|
+| PowerDNS Authoritative | existente | existente | existente | Ampliar zonas |
+| Technitium DNS (resolver) | 1 vCPU | 512 MB - 1 GB | 10 GB | Nuevo |
+| netbox-dns plugin | 0 (dentro de Netbox) | 0 | 0 | Configurar |
+| Panel diagnostico | incluido en VM servicios | ~100 MB | incluido | Mes 3+ |
+
+#### Prioridades despliegue DNS
+
+| Prioridad | Que | Esfuerzo |
+|-----------|-----|----------|
+| **Semana 1** | Technitium DNS como resolver basico (sin bloqueo aun) | 2h |
+| **Semana 1** | Crear zona vpn6.com.es en PowerDNS | 1h |
+| **Semana 2** | Automatizar AAAA+PTR desde wgdashboard → PowerDNS API | 4h |
+| **Semana 2** | Configurar Technitium: reenvio zonas internas a PowerDNS | 1h |
+| **Mes 1** | Configurar perfiles de bloqueo por cliente en Technitium | 2h |
+| **Mes 1** | netbox-proxmox + netbox-dns: auto-registro VMs | 1 dia |
+| **Mes 2** | Abrir panel Technitium a clientes (con SSO Authentik) | 2h |
+| **Mes 3+** | Panel de diagnostico self-service | 3-5 dias desarrollo |
+
+---
+
+## 4.3 Proxy inverso y publicacion de servicios
+
+### El problema
+
+- Tendencia a eliminar IPv4: los clientes deben poder publicar servicios sin necesitar IP publica propia
+- Cada cliente que quiere exponer un servicio web necesita SSL, reverse proxy, y DNS
+- Hoy se hace con Traefik (ya en uso) pero sin un modelo sistematico
+- Los clientes deberian poder declarar "quiero publicar este servicio" y que se configure automaticamente
+
+### Opciones evaluadas
+
+| Herramienta | Autodiscovery | SSL auto | API | Estado |
+|-------------|--------------|---------|-----|--------|
+| **Traefik** | Docker labels, ficheros, API | Let's Encrypt nativo | REST | **ELEGIDA** (ya en uso) |
+| **Nginx Proxy Manager** | Manual (UI web) | Let's Encrypt | No | Descartada (manual) |
+| **HAProxy** | Config estatica | Manual o con ACME | Stats | Descartada (sin auto-SSL) |
+| **Caddy** | Caddyfile, API | Automatico por defecto | REST | Alternativa viable |
+
+### DECISION 4.3 (confirmada)
+
+#### Traefik como proxy inverso centralizado
+
+Ya se usa. Se amplia como punto de publicacion de servicios para clientes.
+
+#### Modelo: cliente declara servicio → Traefik lo expone
+
+```
+Cliente ACME quiere publicar:
+ - Portal web: portal.acme.com
+ - API: api.acme.com
+
+Flujo:
+1. Cliente declara en panel (o ICSManager): "quiero publicar portal.acme.com"
+2. DNS: AAAA de portal.acme.com → IPv6 del Traefik
+3. Traefik: route portal.acme.com → VM del cliente (por WG IPv6)
+4. SSL: Let's Encrypt automatico
+5. Cliente solo tiene IPv6 WG, Traefik pone la cara publica
+```
+
+#### Arquitectura
+
+```
+Internet (IPv4+IPv6)
+ │
+ ▼
+┌──────────────────────────────────────┐
+│ Traefik (proxy inverso) │
+│ │
+│ Escucha: │
+│ - IPv4 publica :443 (legacy) │
+│ - IPv6 publica :443 (preferido) │
+│ │
+│ Routes: │
+│ portal.acme.com → [fdab:..acme::100]:8080 │
+│ api.acme.com → [fdab:..acme::101]:3000 │
+│ grafana.mgmt.vpn6.com.es → [fdab:..mgmt::30]:3000 │
+│ │
+│ SSL: Let's Encrypt (automatico) │
+│ Middleware: rate limiting, headers │
+│ │
+│ Config: ficheros dinamicos │
+│ (generados por Ansible/API) │
+└──────────────────────────────────────┘
+ │
+ │ WG IPv6
+ ▼
+┌──────────────┐ ┌──────────────┐
+│ VM cliente │ │ VM cliente │
+│ portal ACME │ │ API ACME │
+│ (solo IPv6 │ │ (solo IPv6 │
+│ WG, sin IP │ │ WG, sin IP │
+│ publica) │ │ publica) │
+└──────────────┘ └──────────────┘
+```
+
+#### Eliminacion progresiva de IPv4
+
+La tendencia es que las VMs de clientes NO tengan IPv4 publica:
+
+| Antes | Despues |
+|-------|---------|
+| Cada VM con IP publica | VM solo tiene IPv6 WG |
+| SSL configurado en cada VM | SSL centralizado en Traefik |
+| Firewall por VM | Firewall centralizado |
+| El cliente gestiona su SSL | Traefik gestiona Let's Encrypt |
+
+Para clientes que aun necesiten IPv4 entrante (legacy):
+- Traefik escucha en IPv4 publica y reenvia a IPv6 WG del cliente
+- El cliente no necesita IPv4 propia
+- Transicion transparente
+
+#### Configuracion dinamica
+
+Traefik puede leer rutas de:
+- **Ficheros YAML** (generados por Ansible al dar de alta un servicio)
+- **Docker labels** (para servicios en Docker)
+- **API** (para automatizacion)
+
+```yaml
+# /etc/traefik/dynamic/clientes/acme.yml
+# Generado automaticamente al dar de alta el servicio
+http:
+ routers:
+ portal-acme:
+ rule: "Host(`portal.acme.com`)"
+ service: portal-acme
+ tls:
+ certResolver: letsencrypt
+ services:
+ portal-acme:
+ loadBalancer:
+ servers:
+ - url: "http://[fdab:1234:vpn:acme::100]:8080"
+```
+
+Flujo automatizado:
+```
+ICSManager/Panel → API → Ansible playbook
+ → Crea fichero YAML en Traefik
+ → Crea AAAA en PowerDNS
+ → Traefik recarga automaticamente (file watcher)
+ → Servicio publicado con SSL en segundos
+```
+
+#### Servicios internos via Traefik
+
+Los paneles internos (Grafana, Netbox, Wazuh, etc.) tambien pasan por Traefik pero solo accesibles via WG:
+
+```yaml
+# Servicios internos - solo escuchan en IPv6 WG
+http:
+ routers:
+ grafana:
+ rule: "Host(`grafana.mgmt.vpn6.com.es`)"
+ service: grafana
+ tls:
+ certResolver: letsencrypt
+ netbox:
+ rule: "Host(`netbox.mgmt.vpn6.com.es`)"
+ service: netbox
+ tls:
+ certResolver: letsencrypt
+```
+
+Accesibles solo desde la red WG de gestion. Desde internet: no alcanzable.
+
+#### Recursos
+
+| Componente | CPU | RAM | Disco |
+|-----------|-----|-----|-------|
+| Traefik | 1 vCPU | 256-512 MB | 5 GB |
+
+Puede correr en la VM de servicios (como contenedor Docker) o en los WG Hubs. No necesita VM dedicada.
+
+#### Prioridades despliegue proxy
+
+| Prioridad | Que | Esfuerzo |
+|-----------|-----|----------|
+| **Semana 1** | Traefik para servicios internos (Grafana, Netbox, etc.) | 2h |
+| **Semana 2** | SSL automatico para servicios internos | 1h |
+| **Mes 1** | Primer servicio de cliente publicado via Traefik | 2h |
+| **Mes 2** | Template Ansible para alta automatica de servicios | 4h |
+| **Mes 3+** | Panel para que clientes declaren servicios | Desarrollo |
+
+---
+
+## 4.4 Salida a internet: NAT CGNAT + política restrictiva
+
+### El problema
+
+Las VMs de clientes son IPv6-only (WG). Necesitan salida a internet para:
+- apt update / Docker pull
+- APIs externas
+- Navegación (si aplica)
+
+Sin un mecanismo de salida, las VMs están aisladas. Pero dar salida sin control abre riesgos: un cliente puede enviar spam, hacer scraping, o abusar y provocar que la IP compartida se blacklistee.
+
+### DECISION 4.4 (confirmada)
+
+#### Dual-stack con IPv4 CGNAT para salida
+
+Cada VM de cliente tiene dual-stack:
+
+```
+VM de cliente:
+ - IPv6 WG: fdab:1234:vpn:acme::10 → acceso entrante (RDP, SSH, servicios)
+ - IPv4 CGNAT: 100.64.1.10/24 → solo salida NAT (navegar, apt, APIs)
+
+Regla de oro:
+ IPv4 = solo SALE, nunca ENTRA (outbound NAT)
+ IPv6 = ENTRA controlado (el cliente/técnico accede vía WG)
+ Web = publicado por Traefik (HTTP/HTTPS al mundo)
+```
+
+#### Rango CGNAT (100.64.0.0/10)
+
+Se usa el rango CGNAT (RFC 6598) en vez de RFC1918 para evitar colisiones con redes de oficina de clientes:
+
+```
+100.64.0.0/10 total (~4 millones de IPs)
+
+Asignación: /24 por cliente
+ 100.64.1.0/24 → Cliente ACME
+ 100.64.2.0/24 → Cliente BETA
+ 100.64.3.0/24 → Cliente GAMMA
+ ...
+ 100.64.254.0/24 → Cliente 254
+ 100.65.x.0/24 → Overflow si hace falta
+```
+
+Ventaja sobre 10.x o 192.168.x: un cliente con oficina en 192.168.1.0/24 conectada por WG no tiene conflicto con la IP CGNAT de su VM.
+
+#### Política de salida restrictiva (nftables en WG Hub)
+
+```nft
+table ip filter {
+ chain forward {
+ type filter hook forward priority 0; policy drop;
+
+ # Tráfico establecido/relacionado
+ ct state established,related accept
+
+ # --- PERMITIDO PARA TODOS ---
+ ip saddr 100.64.0.0/10 tcp dport { 80, 443 } accept # Web
+ ip saddr 100.64.0.0/10 udp dport { 53, 123 } accept # DNS, NTP
+ ip saddr 100.64.0.0/10 tcp dport 53 accept # DNS TCP
+
+ # --- SMTP: BLOQUEADO POR DEFECTO ---
+ ip saddr 100.64.0.0/10 tcp dport 25 drop # NUNCA
+
+ # Puerto 465/587: solo whitelist
+ ip saddr 100.64.0.0/10 tcp dport { 465, 587 } ip daddr @smtp_whitelist accept
+
+ # --- RESTO: DENEGADO ---
+ ip saddr 100.64.0.0/10 log prefix "CGNAT-BLOCKED: " drop
+ }
+}
+
+table ip nat {
+ chain postrouting {
+ type nat hook postrouting priority 100;
+ # NAT compartido (base)
+ ip saddr 100.64.0.0/10 oifname "eth0" masquerade
+ }
+}
+
+# Whitelist SMTP
+set smtp_whitelist {
+ type ipv4_addr
+ flags interval
+ elements = {
+ # WildDuck propio (IP interna)
+ 100.64.0.0/10,
+ # Añadir IPs de servicios de correo autorizados según necesidad
+ }
+}
+```
+
+#### Puertos permitidos por defecto
+
+| Puerto | Política | Razón |
+|--------|----------|-------|
+| 80, 443 | ✅ Permitido | Navegación, APIs, apt, Docker pulls |
+| 53, 123 | ✅ Permitido | DNS, NTP |
+| 25 | ✗ Bloqueado siempre | Prevenir spam. Sin excepciones. |
+| 465, 587 | ✅ Solo whitelist | SMTP autenticado a servidores conocidos |
+| Todo lo demás | ✗ Bloqueado | El cliente pide, se evalúa, se abre específicamente |
+
+Logs de intentos bloqueados → Alloy → Loki. Visibles en Grafana.
+
+#### Rate limiting por cliente
+
+```nft
+# Evitar abuso incluso en puertos permitidos
+ip saddr 100.64.1.0/24 tcp dport { 80, 443 } limit rate 200/second burst 50 accept
+ip saddr 100.64.1.0/24 tcp dport { 80, 443 } drop
+```
+
+#### Definición de producto: base vs premium
+
+```
+SALIDA INTERNET (NAT) - Definición de producto
+─────────────────────────────────────────────────
+INCLUIDO (base):
+ - Salida compartida: HTTP/HTTPS, DNS, NTP
+ - SMTP solo a servidores autorizados (465/587 whitelist)
+ - Puerto 25 bloqueado sin excepciones
+ - IP pública compartida
+ - IP puede cambiar sin preaviso
+ - Sin garantía de reputación de IP
+ - Política restrictiva no negociable
+
+PREMIUM (contratación adicional):
+ - IP dedicada fija con PTR personalizable
+ - Si se blacklistea, solo afecta al cliente que la contrató
+ - Apertura de puertos adicionales (previa evaluación)
+ - Reglas firewall personalizadas
+ - Rate limits ajustados
+─────────────────────────────────────────────────
+```
+
+Implementación del premium:
+
+```nft
+# Cliente ACME tiene IP dedicada (premium)
+ip saddr 100.64.1.0/24 oifname "eth0" snat to 1.2.3.101
+
+# Resto de clientes: IP compartida
+ip saddr 100.64.0.0/10 oifname "eth0" snat to 1.2.3.100
+```
+
+#### Gestión
+
+- Reglas nftables gestionadas con Ansible (un fichero por cliente, versionado en Forgejo)
+- Aperturas de puertos: el cliente solicita → se evalúa → se aplica con playbook → queda en git
+- Asignación de rangos CGNAT: en Netbox (IPAM), prefijo 100.64.X.0/24 asignado al tenant
+- Alertas: logs de tráfico bloqueado correlacionados con cliente en Grafana
+
+---
+
+## 4.5 Modelo de VMs: una VM = un servicio (caja negra)
+
+### El problema
+
+Si varios servicios con BBDD comparten una VM:
+- Un técnico trabajando en Zammad puede afectar a Netbox
+- Diagnosticar "qué consume IO" es complejo cuando hay 5 servicios mezclados
+- Migrar un servicio a otro servidor requiere separarlo primero
+- El backup de la VM incluye todo, no puedes restaurar solo un servicio
+
+### DECISION 4.5 (confirmada)
+
+**Cada servicio con estado (BBDD) tiene su propia VM.** Es una caja negra: todo lo que necesita está dentro (app + BBDD + Redis). Se backupea entera, se migra entera, se diagnostica de forma aislada.
+
+#### Mapa de VMs por servicio
+
+| VM | Contenido (todo Docker dentro) | BBDD | Stateful |
+|----|-------------------------------|------|----------|
+| **VM Zammad** | Zammad + PostgreSQL + Redis + Elasticsearch | PostgreSQL | Sí, VM dedicada |
+| **VM Authentik** | Authentik + PostgreSQL + Redis | PostgreSQL | Sí, VM dedicada |
+| **VM Netbox** | Netbox + PostgreSQL + Redis | PostgreSQL | Sí, VM dedicada |
+| **VM Outline** | Outline + PostgreSQL + Redis | PostgreSQL | Sí, VM dedicada |
+| **VM ICSManager** | ICSManager + PostgreSQL (todo Docker) | PostgreSQL | Sí, VM dedicada |
+| **VM WildDuck** | WildDuck + MongoDB + Redis (todo Docker) | MongoDB | Sí, VM dedicada |
+| **VM Servicios** | Semaphore, Uptime Kuma, Technitium, Traefik | Ninguna pesada | Stateless/ligeros |
+| **VM Monitoring** | VictoriaMetrics, Loki, Grafana, Alertmanager | Time-series | Ya dedicada |
+| **VM Wazuh** | Wazuh Manager + Dashboard (OpenSearch) | OpenSearch | Ya dedicada |
+
+#### Ventajas operativas
+
+| Aspecto | Servicios mezclados | Caja negra por servicio |
+|---------|-------------------|----------------------|
+| Diagnosticar | "¿Qué de los 5 servicios consume IO?" | "La VM Zammad consume IO → es Zammad" |
+| Migrar | Separar primero, luego mover | Mover la VM entera |
+| Backup/Restore | Todo o nada | Restauras solo Zammad sin tocar nada |
+| Un técnico trabaja | Con miedo de romper otros servicios | Libertad total dentro de su VM |
+| Actualizar | Coordinar 5 servicios | Actualizar uno, si falla, rollback VM |
+| Escalar | Dar más RAM a la VM afecta a todos | Dar más RAM solo a Zammad |
+
+#### WildDuck y MongoDB
+
+MongoDB dockerizado dentro de la VM WildDuck. De momento es correo interno del equipo técnico, volumen bajo. Si el correo se abre a más usuarios o el volumen crece significativamente, se revalúa:
+- MongoDB en VM dedicada (si el tamaño lo justifica)
+- O migración a servicio gestionado
+
+Los tokens SMTP de WildDuck para avisos de PVE son operativamente críticos. El backup de la VM WildDuck los incluye automáticamente.
+
+#### Impacto en dimensionamiento
+
+La tabla actualizada de VMs:
+
+| VM | vCPU | RAM | Disco |
+|----|------|-----|-------|
+| Monitoring | 4 | 8-12 GB | 200-500 GB |
+| Wazuh | 4 | 12 GB | 100 GB |
+| Zammad | 2-3 | 4-6 GB | 50 GB |
+| Authentik | 1-2 | 2-3 GB | 20 GB |
+| Netbox | 1-2 | 2-3 GB | 20 GB |
+| ICSManager | 1-2 | 2-3 GB | 20 GB |
+| WildDuck | 1-2 | 2-3 GB | 30-50 GB |
+| PMG | 1-2 | 2-3 GB | 20 GB |
+| PDM | 1-2 | 1-2 GB | 20 GB |
+| Outline | 1 | 1-2 GB | 20 GB |
+| Servicios (stateless) | 1-2 | 2-3 GB | 20 GB |
+| Bastion | 1 | 1 GB | 20 GB |
+| WG Hub A | 1 | 512 MB | 10 GB |
+| WG Hub B | 1 | 512 MB | 10 GB |
+| Forgejo | exist. | exist. | exist. |
+| **Total** | **~24-32 vCPU** | **~44-58 GB** | **~580-910 GB** |
+
+---
+
+## 4.6 Proxmox Datacenter Manager (PDM)
+
+### El problema
+
+Con múltiples PVE en distintos DCs (Francia, potencialmente otros), la gestión es por nodo o por cluster. Si un cliente necesita mover una VM de un PVE a otro (por mantenimiento, por rendimiento, o por fallo), hay que hacerlo manualmente conectándose a cada PVE.
+
+### DECISION 4.6 (confirmada)
+
+**PDM como panel único de gestión multi-cluster.**
+
+Proxmox Datacenter Manager permite:
+- Vista global de todos los nodos PVE y clusters en un solo panel
+- Migración de VMs entre clusters/nodos (lo que los clientes necesitan)
+- Gestión centralizada de recursos, storage, red
+- Planificación de capacidad a nivel de datacenter
+
+```
+ ┌─────────────────────┐
+ │ PDM │
+ │ (panel único) │
+ └──────┬──────────────┘
+ │
+ ┌────────────┼────────────┐
+ │ │ │
+ ┌─────▼─────┐ ┌───▼──────┐ ┌──▼────────┐
+ │ PVE-1 │ │ PVE-2 │ │ PVE-3 │
+ │ Francia │ │ Francia │ │ Alemania │
+ │ 20 VMs │ │ 15 VMs │ │ 10 VMs │
+ └───────────┘ └──────────┘ └───────────┘
+
+El técnico ve todo desde PDM. Puede migrar VM-web
+de PVE-1 a PVE-2 desde la misma interfaz.
+```
+
+#### Recursos
+
+| Componente | CPU | RAM | Disco |
+|-----------|-----|-----|-------|
+| PDM (VM dedicada) | 1-2 vCPU | 1-2 GB | 20 GB |
+
+VM dedicada (patrón caja negra). Ligero: es solo plano de gestión, no compute.
+
+SSO con Authentik. Accesible solo vía WG (red de gestión).
+
+---
+
+## 4.7 Proxmox Mail Gateway (PMG)
+
+### El problema
+
+WildDuck gestiona el correo (buzones, IMAP, SMTP, tokens). Pero no tiene filtrado avanzado de spam ni antivirus. Si se abre el correo a más usuarios (más allá del equipo técnico), el volumen de spam y malware entrante será un problema.
+
+### DECISION 4.7 (confirmada)
+
+**PMG como gateway de filtrado delante de WildDuck.**
+
+```
+Internet (puerto 25)
+ │
+ ▼
+┌──────────────────────┐
+│ PMG │
+│ Proxmox Mail Gateway │
+│ │
+│ - Anti-spam │
+│ (SpamAssassin) │
+│ - Anti-virus │
+│ (ClamAV) │
+│ - Greylisting │
+│ - SPF/DKIM/DMARC │
+│ - Cuarentena con UI │
+│ - Reglas por dominio │
+│ - Reglas por usuario │
+│ │
+│ Puerto 25 entrante │
+│ → filtra │
+│ → reenvía limpio a │
+│ WildDuck │
+└──────────┬───────────┘
+ │ (email limpio)
+ ▼
+┌──────────────────────┐
+│ WildDuck │
+│ (buzones, IMAP, │
+│ tokens SMTP PVE) │
+└──────────────────────┘
+```
+
+#### Funcionalidades clave
+
+| Función | Qué hace |
+|---------|----------|
+| **Anti-spam** | SpamAssassin + reglas custom, scoring por mensaje |
+| **Anti-virus** | ClamAV, escanea adjuntos entrantes y salientes |
+| **Greylisting** | Retrasa primer intento desde IP desconocida (mata el 80% del spam) |
+| **SPF/DKIM/DMARC** | Verifica autenticidad del remitente |
+| **Cuarentena** | UI web donde el usuario ve mensajes filtrados y puede liberar falsos positivos |
+| **Reglas por dominio** | Políticas diferentes por dominio gestionado |
+| **Reporting** | Estadísticas: spam filtrado, virus detectados, volumen |
+
+#### Email saliente
+
+PMG también filtra salida: si un usuario comprometido envía spam, PMG lo detecta y bloquea antes de que salga y blacklistee la IP.
+
+```
+WildDuck (usuario envía)
+ │
+ ▼
+┌──────────────────────┐
+│ PMG │
+│ - Verifica que no │
+│ sea spam saliente │
+│ - Firma DKIM │
+│ - Rate limiting │
+└──────────┬───────────┘
+ │
+ ▼
+ Internet (destino)
+```
+
+#### Integración con el stack
+
+| Integración | Qué aporta |
+|-------------|-----------|
+| PMG → Loki (logs) | Logs de spam filtrado, virus detectados → visibles en Grafana |
+| PMG → Alertmanager | Alerta si el volumen de spam sube anómalamente |
+| PMG → Wazuh | Eventos de seguridad (virus detectados) correlacionados |
+| PMG SSO Authentik | Panel de administración con cuenta unificada |
+| PMG cuarentena → usuario | El usuario accede vía web a su cuarentena |
+
+#### Recursos
+
+| Componente | CPU | RAM | Disco |
+|-----------|-----|-----|-------|
+| PMG (VM dedicada) | 1-2 vCPU | 2-3 GB | 20 GB |
+
+ClamAV consume la mayor parte de la RAM (~1-1.5 GB para las firmas). SpamAssassin ligero. VM dedicada siguiendo patrón caja negra.
+
+#### Prioridades
+
+| Paso | Qué | Cuándo |
+|------|-----|--------|
+| Desplegar PMG con reglas básicas | Cuando WildDuck se abra a más usuarios |
+| Configurar DKIM signing | Junto con el despliegue |
+| Integrar logs PMG → Loki | Semana 1 post-despliegue |
+| Cuarentena accesible a usuarios | Junto con el despliegue |
+
+No es urgente mientras WildDuck solo sirva al equipo técnico. **Se despliega cuando se amplíe el correo.**
+
+---
+
+## Resumen Capa 4 completa (DECISIONES FINALES)
+
+```
+┌─────────────────────────────────────────────────────┐
+│ CAPA 4: RED Y CONECTIVIDAD │
+├─────────────────────────────────────────────────────┤
+│ │
+│ 4.1 VPN (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ WireGuard + wgdashboard + IPv6 │ │
+│ │ │ │
+│ │ Rol 1: PBS = WG server (backups) │ │
+│ │ - Peers: solo PVEs con storage │ │
+│ │ - Sin forwarding (aislados) │ │
+│ │ │ │
+│ │ Rol 2: WG Hub HA (clientes) │ │
+│ │ - Par HA keepalived │ │
+│ │ - Tecnicos + clientes + VMs │ │
+│ │ - nftables aislamiento/cliente │ │
+│ │ - wgdashboard (UI + API) │ │
+│ │ - Reemplaza 1-GW-por-cliente │ │
+│ │ │ │
+│ │ Rol 3: Mesh gestion (vRack) │ │
+│ │ - WG cifrado sobre vRack 25Gbps │ │
+│ │ - Servicios internos solo aqui │ │
+│ │ │ │
+│ │ Direccionamiento: IPv6 ULA │ │
+│ │ fdab:1234:bkp::/48 (backups) │ │
+│ │ fdab:1234:vpn::/48 (clientes) │ │
+│ │ fdab:1234:mgmt::/48 (gestion) │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 4.2 DNS (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ PowerDNS Auth (ya existente) │ │
+│ │ - Zonas clientes (pri/sec) │ │
+│ │ - vpn6.com.es (nombres WG) │ │
+│ │ - PTR IPv6 │ │
+│ │ - ← Netbox + wgdashboard API │ │
+│ │ │ │
+│ │ Technitium DNS (resolver nuevo) │ │
+│ │ - Resolver + bloqueo multi-tenant│ │
+│ │ - Panel web por cliente │ │
+│ │ - Perfiles: malware/ads/adult │ │
+│ │ - Reenvio zonas internas → PDNS │ │
+│ │ │ │
+│ │ Naming automatico: │ │
+│ │ - wgdashboard → PDNS API │ │
+│ │ - Netbox → netbox-dns → PDNS │ │
+│ │ - PVE → Netbox → DNS automatico │ │
+│ │ │ │
+│ │ Panel diagnostico (mes 3+) │ │
+│ │ - Self-service checks cliente │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 4.3 PROXY INVERSO (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Traefik (ya en uso, ampliar) │ │
+│ │ - Proxy inverso centralizado │ │
+│ │ - SSL Let's Encrypt automatico │ │
+│ │ - Clientes declaran servicio → │ │
+│ │ Traefik lo publica │ │
+│ │ - VMs sin IPv4 publica │ │
+│ │ (Traefik pone la cara publica) │ │
+│ │ - Config YAML dinamica via │ │
+│ │ Ansible/API │ │
+│ │ - Servicios internos solo via WG │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 4.4 SALIDA INTERNET NAT (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Dual-stack por VM: │ │
+│ │ - IPv4 CGNAT (100.64.x.x/24) │ │
+│ │ solo salida NAT, nunca entra │ │
+│ │ - IPv6 WG: acceso entrante │ │
+│ │ │ │
+│ │ Política restrictiva: │ │
+│ │ - 80,443,53,123: permitido │ │
+│ │ - Puerto 25: BLOQUEADO siempre │ │
+│ │ - 465,587: solo whitelist SMTP │ │
+│ │ - Resto: bloqueado por defecto │ │
+│ │ │ │
+│ │ Producto base: IP compartida │ │
+│ │ (puede cambiar sin preaviso) │ │
+│ │ Premium: IP dedicada fija │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 4.5 MODELO VMs (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Una VM = un servicio (caja negra)│ │
+│ │ App + BBDD + Redis todo dentro │ │
+│ │ - Zammad, Authentik, Netbox, │ │
+│ │ ICSManager, WildDuck, Outline │ │
+│ │ cada uno en su VM dedicada │ │
+│ │ - Backup/migración: VM entera │ │
+│ │ - Diagnóstico aislado │ │
+│ │ - Total: ~24-32 vCPU, 44-58 GB │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 4.6 PDM (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Proxmox Datacenter Manager │ │
+│ │ - Gestión multi-cluster PVE │ │
+│ │ - Migración VMs entre PVEs │ │
+│ │ - Vista global de recursos │ │
+│ │ - SSO Authentik, solo via WG │ │
+│ │ - VM dedicada: 1-2 vCPU, 1-2GB │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 4.7 PMG (confirmada, desplegar al ampliar correo) │
+│ ┌───────────────────────────────────┐ │
+│ │ Proxmox Mail Gateway │ │
+│ │ - Anti-spam (SpamAssassin) │ │
+│ │ - Anti-virus (ClamAV) │ │
+│ │ - SPF/DKIM/DMARC │ │
+│ │ - Greylisting │ │
+│ │ - Cuarentena con UI web │ │
+│ │ - Filtro saliente (anti-spam) │ │
+│ │ - Delante de WildDuck │ │
+│ │ - VM dedicada: 1-2 vCPU, 2-3GB │ │
+│ └───────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────┘
+
+Recursos Capa 4:
+- WG Hub A: 1 vCPU, 512 MB RAM, 10 GB disco
+- WG Hub B: 1 vCPU, 512 MB RAM, 10 GB disco
+- Technitium DNS: 1 vCPU, 512 MB - 1 GB RAM (en VM Servicios)
+- Traefik: contenedor Docker, 256-512 MB RAM (en VM Servicios)
+- PDM: 1-2 vCPU, 1-2 GB RAM, 20 GB disco
+- PMG: 1-2 vCPU, 2-3 GB RAM, 20 GB disco
+- PowerDNS: ya existente
+- WG en servidores: kernel module, 0 extra
+- NAT CGNAT: en WG Hub (nftables, 0 extra)
+
+Dimensionamiento revisado (todas las VMs de gestión):
+ ~24-32 vCPU, ~44-58 GB RAM, ~580-910 GB disco
+ Recomendación servidor: 128 GB RAM, 8-12c/16-24t, 2×2TB NVMe
+```
diff --git a/08-decision-incidentes.md b/08-decision-incidentes.md
new file mode 100644
index 0000000..9eff0fe
--- /dev/null
+++ b/08-decision-incidentes.md
@@ -0,0 +1,243 @@
+# Decision: Capa de gestion de incidentes y comunicacion (punto 5)
+
+## Contexto
+
+El equipo gestiona incidencias via Zammad (ticketing) y comunicacion directa (telefono, email). Las alertas de infraestructura se envian a Telegram. No hay herramienta de comunicacion interna del equipo ni gestion formal de guardias. La documentacion operativa esta dispersa en ficheros, cabezas, y CLAUDE.md por servidor.
+
+2 subcapas:
+- 5.1 Ticketing, alertas y comunicacion
+- 5.2 Documentacion operativa
+
+---
+
+## 5.1 Ticketing, alertas y comunicacion
+
+### Lo que ya existe
+
+| Herramienta | Estado | Funcion |
+|-------------|--------|---------|
+| **Zammad** | En produccion | Ticketing de soporte, gestion de incidencias de clientes |
+| **Telegram** | En uso | Canal de alertas criticas (personal) |
+| **Email** | En uso | Alertas informativas, comunicacion con clientes |
+| **Telefono** | En uso | Escalado urgente ("atiende este ticket") |
+
+### Opciones evaluadas (futuro)
+
+| Herramienta | Que hace | Recursos | Estado |
+|-------------|----------|----------|--------|
+| **Mattermost** | Chat de equipo con canales tematicos + webhooks | 512 MB RAM | **Futuro** (mes 3+) |
+| **Grafana OnCall** | Gestion de ciclo de vida de alertas (asignar, resolver, escalar, guardias) | Integrado en Grafana | **Futuro** (mes 3+) |
+| **PagerDuty/Opsgenie** | Escalado alertas SaaS | SaaS | Descartado (coste, dependencia) |
+
+### DECISION 5.1 (confirmada)
+
+#### Dia 1: Zammad + Telegram (lo que ya funciona)
+
+- **Zammad** sigue como centro de ticketing
+- **Telegram** sigue como canal de alertas criticas
+- **Email** para alertas informativas
+
+Integraciones nuevas con el stack:
+
+| Integracion | Que aporta | Prioridad |
+|-------------|-----------|-----------|
+| **Zammad SSO Authentik** | Misma cuenta para todo (SAML/OIDC nativo) | Semana 1 |
+| **Alertmanager → Telegram** | Alertas de infraestructura al movil | Semana 1 |
+| **Wazuh → Telegram** | Alertas de seguridad al movil | Semana 1 |
+| **Alertmanager → Zammad** | Alerta critica no atendida en 15min → crea ticket automaticamente | Mes 2 |
+| **Wazuh → Zammad** | Incidente seguridad → ticket prioridad critica | Mes 2 |
+| **Panel diagnostico → Zammad** | Cliente adjunta informe al abrir ticket | Mes 3+ |
+| **Netbox → Zammad** | Tecnico ve contexto del cliente en el ticket | Mes 3+ |
+
+#### Futuro (mes 3-6): Mattermost
+
+Chat interno del equipo como **panel de notificaciones centralizado**:
+
+| Canal | Que recibe |
+|-------|-----------|
+| #alertas-criticas | Alertmanager → webhook (solo critical/warning) |
+| #seguridad | Wazuh → webhook (FIM, rootkits, anomalias) |
+| #backups | Semaphore → webhook (GC/verify fallidos) |
+| #infra-general | Deploys, mantenimientos, cambios |
+| #tickets | Zammad → webhook (tickets nuevos/escalados) |
+
+Valor: historial buscable, contexto compartido, incorporacion de tecnicos nuevos.
+
+No es para chatear entre el equipo (para eso ya hay canales). Es donde el stack vuelca sus eventos y el equipo tiene visibilidad compartida.
+
+SSO con Authentik (OIDC nativo). Contenedor Docker, 512 MB RAM.
+
+**Cuando desplegar**: cuando el stack base este rodando y el equipo tenga soltura. No antes del mes 3.
+
+#### Futuro (mes 6+): Grafana OnCall
+
+Gestion de ciclo de vida de alertas con rotacion de guardias:
+
+- Alertas activas visibles, resueltas desaparecen
+- Asignacion: "yo la miro"
+- Escalado automatico: si nadie responde en X min → siguiente tecnico
+- Rotacion de guardias (on-call schedule)
+- Integrado en Grafana (mismo panel)
+- Notifica por Telegram/Mattermost/SMS/llamada
+
+**Cuando desplegar**: cuando el equipo crezca y haya guardias formales. No antes del mes 6.
+
+```
+Flujo futuro completo:
+Alertmanager detecta problema
+ │
+ ├──► Grafana OnCall (gestionar: asignar, resolver, escalar)
+ ├──► Mattermost #alertas (ver: contexto compartido)
+ └──► Telegram (backup: al movil del que esta de guardia)
+```
+
+---
+
+## 5.2 Documentacion operativa
+
+### El problema
+
+- Documentacion dispersa: CLAUDE.md por servidor, ficheros sueltos, conocimiento en cabezas
+- Si un tecnico se va, se va su conocimiento
+- Si llega un tecnico nuevo, no tiene donde aprender
+- Los runbooks (que hacer cuando X pasa) no existen formalmente
+- A 100+ servidores, "preguntale a David" no escala
+
+### Opciones evaluadas
+
+| Herramienta | Tipo | Editor | SSO | Recursos | Estado |
+|-------------|------|--------|-----|----------|--------|
+| **Outline** | Wiki moderna (tipo Notion) | Markdown visual, tiempo real | OIDC nativo | ~1 GB RAM | **ELEGIDA** |
+| **Bookstack** | Wiki clasica | WYSIWYG + markdown | OIDC/SAML | 512 MB | Descartada (menos moderna) |
+| **MkDocs + Git** | Docs como codigo | Ficheros .md en Forgejo | No aplica | 0 | Complementario (docs tecnicas con codigo) |
+| **Confluence** | Wiki empresarial | WYSIWYG | SAML | SaaS/pesado | Descartada (SaaS/coste) |
+
+### DECISION 5.2 (confirmada)
+
+#### Outline para conocimiento operativo
+
+Wiki moderna self-hosted. Interfaz tipo Notion, edicion colaborativa en tiempo real, busqueda full-text, permisos por coleccion.
+
+Estructura propuesta:
+
+```
+📁 Runbooks
+ ├── Servidor nuevo: checklist completo
+ ├── Cliente nuevo PBS: paso a paso
+ ├── Incidente de seguridad: protocolo
+ ├── Disco lleno: procedimiento
+ ├── Restore de backup: guia
+ ├── Failover WG Hub: pasos
+ └── Rotar credenciales: procedimiento
+
+📁 Arquitectura
+ ├── Stack completo (diagramas)
+ ├── Redes WG (topologia IPv6)
+ ├── DNS (esquema PowerDNS + Technitium)
+ ├── Decisiones tecnicas (resumen de estos documentos)
+ └── Mapa de servicios y VMs
+
+📁 Onboarding
+ ├── Primer dia del tecnico
+ ├── Accesos (SSH bastion, WG, paneles)
+ ├── Herramientas del stack
+ ├── Como usar Semaphore
+ └── Contactos y escalado
+
+📁 Clientes (acceso restringido)
+ ├── ACME: infra, contactos, particularidades
+ ├── BETA: infra, contactos
+ └── Plantilla cliente nuevo
+```
+
+Permisos:
+- Tecnicos: leen y editan todo
+- Comerciales: solo ven Clientes + Onboarding
+- Clientes: no acceden (su info esta en Zammad + panel diagnostico)
+
+SSO con Authentik desde el dia 1.
+
+#### Forgejo para documentacion tecnica con codigo
+
+Los docs que van pegados al codigo siguen en Forgejo (README.md, comentarios en playbooks, configs documentadas). No duplicar en Outline.
+
+| Tipo de documentacion | Donde |
+|-----------------------|-------|
+| Runbooks, procedimientos, onboarding | **Outline** |
+| Playbooks Ansible, configs, scripts | **Forgejo** (en el repo) |
+| Diagramas de arquitectura | **Outline** |
+| Decisiones tecnicas (estos documentos) | **Outline** (migrar cuando este listo) |
+| API docs, integraciones | **Forgejo** (con el codigo) |
+
+#### Recursos
+
+| Componente | RAM |
+|-----------|-----|
+| Outline app | ~512 MB |
+| PostgreSQL (puede compartir con otros) | ~256 MB |
+| Redis (puede compartir con otros) | ~64 MB |
+| **Total** | ~800 MB - 1 GB |
+
+Contenedor Docker en VM Servicios.
+
+#### Prioridades despliegue documentacion
+
+| Prioridad | Que | Esfuerzo |
+|-----------|-----|----------|
+| **Semana 2** | Desplegar Outline con SSO Authentik | 2h |
+| **Semana 2** | Crear estructura de colecciones (vacia) | 1h |
+| **Mes 1** | Escribir runbooks criticos (servidor nuevo, incidente seguridad, restore) | 1 dia |
+| **Mes 1** | Migrar decisiones tecnicas (estos docs) a Outline | 2h |
+| **Mes 2** | Documentar onboarding de tecnicos | 4h |
+| **Mes 2** | Documentar particularidades por cliente | Progresivo |
+
+---
+
+## Resumen Capa 5 completa (DECISIONES FINALES)
+
+```
+┌─────────────────────────────────────────────────────┐
+│ CAPA 5: GESTION DE INCIDENTES │
+├─────────────────────────────────────────────────────┤
+│ │
+│ 5.1 TICKETING Y ALERTAS (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Zammad (ya en produccion) │ │
+│ │ - Ticketing soporte │ │
+│ │ - SSO Authentik │ │
+│ │ - Integracion Alertmanager/Wazuh │ │
+│ │ │ │
+│ │ Telegram (alertas criticas) │ │
+│ │ - Alertmanager → bot │ │
+│ │ - Wazuh → bot │ │
+│ │ │ │
+│ │ Futuro (mes 3+): │ │
+│ │ - Mattermost (notificaciones │ │
+│ │ centralizadas del equipo) │ │
+│ │ Futuro (mes 6+): │ │
+│ │ - Grafana OnCall (guardias, │ │
+│ │ escalado, ciclo de vida) │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 5.2 DOCUMENTACION (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Outline (wiki tipo Notion) │ │
+│ │ - Runbooks, arquitectura, │ │
+│ │ onboarding, clientes │ │
+│ │ - Edicion colaborativa real-time │ │
+│ │ - SSO Authentik │ │
+│ │ - Permisos por coleccion │ │
+│ │ │ │
+│ │ Forgejo (docs con codigo) │ │
+│ │ - README, configs, playbooks │ │
+│ │ - Versionado con git │ │
+│ └───────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────┘
+
+Recursos Capa 5:
+- Zammad: ya existente
+- Outline: ~1 GB RAM (contenedor Docker en VM Servicios)
+- Mattermost (futuro): 512 MB RAM
+- Grafana OnCall (futuro): integrado en Grafana, 0 extra
+```
diff --git a/09-decision-almacenamiento.md b/09-decision-almacenamiento.md
new file mode 100644
index 0000000..e3550a6
--- /dev/null
+++ b/09-decision-almacenamiento.md
@@ -0,0 +1,625 @@
+# Decision: Capa de almacenamiento y datos (punto 6)
+
+## Contexto
+
+Infraestructura de backup distribuida geográficamente:
+- **PVE** (producción): Francia, datacenters OVH
+- **PBS1** (backup tránsito): Alemania, OVH
+- **PBS2** (backup archivo): Polonia, OVH (nuevo, por contratar)
+
+ZFS se usa en PBS para crear datastores por cliente con quotas/reservations (modelo multiplicador 6x). En PVE se usa ZFS estándar para almacenamiento de VMs.
+
+2 subcapas:
+- 6.1 Gestión de ZFS a escala
+- 6.2 Backup del backup (estrategia de retención y protección)
+
+---
+
+## 6.1 Gestión de ZFS a escala
+
+### Uso actual de ZFS
+
+ZFS se usa como herramienta de gestión de almacenamiento:
+- **En PBS**: un dataset por cliente con quota (size×6) y reservation (size×5)
+- **En PVE**: almacenamiento estándar de VMs
+- Propiedades optimizadas: recordsize=1M, compression=lz4, atime=off
+
+### Herramientas evaluadas
+
+| Herramienta | Qué hace | Estado |
+|-------------|----------|--------|
+| **Sanoid/Syncoid** | Snapshots automáticos + replicación ZFS | **Descartado**: PBS gestiona snapshots y replicación nativamente |
+| **zrepl** | Replicación ZFS continua | **Descartado**: PBS sync jobs cubren esto |
+| **zfswatcher** | Monitoring dedicado ZFS con alertas | **Descartado**: node_exporter ya exporta métricas ZFS |
+
+### DECISION 6.1 (confirmada)
+
+**Solo monitorización.** No se necesitan herramientas adicionales para ZFS. PBS y PVE gestionan sus snapshots y replicación con sus mecanismos nativos.
+
+Monitorización ya cubierta por el stack existente:
+
+| Métrica | Fuente | Alerta |
+|---------|--------|--------|
+| Pool health (degraded, faulted) | node_exporter → VictoriaMetrics | ZFSPoolDegraded (critical, 1min) |
+| Pool space > 85% | node_exporter → VictoriaMetrics | ZFSPoolSpaceHigh (warning, 30min) |
+| Scrub errors | node_exporter → VictoriaMetrics | ZFSScrubErrors (warning) |
+| IO latency | node_exporter → VictoriaMetrics | Dashboard Grafana |
+| ARC hit rate | node_exporter → VictoriaMetrics | Dashboard Grafana |
+
+Gestión ZFS via Ansible:
+- Playbook `optimizar_zfs.sh` convertido a role Ansible
+- Aplicar recordsize=1M, compression=lz4, atime=off a nuevos servidores
+- Crear/modificar datasets de clientes (role pbs_customer)
+
+---
+
+## 6.2 Estrategia de backup: modelo de 3 capas geográficas
+
+### El problema
+
+- Los PVE hacen backup en PBS, pero si PBS se pierde, se pierden todos los backups
+- Regla 3-2-1: 3 copias, 2 medios, 1 offsite
+- OVH Estrasburgo ardió en 2021. Un solo DC no es suficiente.
+- Si ransomware entra en PVE y PBS1, debe haber una copia inaccesible
+
+### DECISION 6.2 (confirmada)
+
+#### Arquitectura de 3 capas geográficas
+
+```
+Francia (OVH) Alemania (OVH) Polonia (OVH, nuevo)
+┌──────────────┐ ┌──────────────────┐ ┌──────────────────────┐
+│ PVE │ │ PBS1 (tránsito) │ │ PBS2 (archivo) │
+│ (producción) │──backup─►│ │◄─pull──│ │
+│ │ WG │ Retención corta: │ WG │ Retención larga: │
+│ Permisos: │ │ keep-last: 5 │ │ keep-daily: 7 │
+│ - Backup │ │ keep-daily: 3 │ │ keep-weekly: 4 │
+│ - Reader │ │ │ │ keep-monthly: 5 │
+│ - NO delete │ │ ~8 snapshots/VM │ │ ~16 snapshots/VM │
+│ │ │ │ │ │
+│ │ │ Acceso: │ │ Acceso: │
+│ │ │ - PVE escribe │ │ - MUY restrictivo │
+│ │ │ - PBS2 lee (sync)│ │ - Solo PBS2 inicia │
+│ │ │ - Técnicos leen │ │ conexiones │
+│ │ │ │ │ - Invisible al resto │
+└──────────────┘ └──────────────────┘ │ - Verify semanal │
+ └──────────────────────┘
+```
+
+#### Principio 1: PVE no puede borrar
+
+El usuario PVE en PBS1 tiene permisos mínimos:
+
+```
+Rol en PBS1 para PVE:
+ - DatastoreBackup (puede crear backups)
+ - DatastoreReader (puede restaurar)
+
+ NO tiene:
+ - DatastoreAdmin (puede borrar, podar)
+ - DatastorePowerUser (puede podar)
+```
+
+Si ransomware compromete PVE, puede crear backups (inofensivo) y restaurar, pero **no puede borrar los existentes**.
+
+#### Principio 2: PBS2 PULL de PBS1 (no push)
+
+PBS2 es quien inicia la conexión hacia PBS1:
+
+```
+PBS2 tiene sync jobs:
+ - remote: PBS1 (Alemania)
+ - remote-store: cada datastore
+ - credenciales: usuario read-only en PBS1
+ - schedule: diario (o cada 12h)
+
+PBS1 NO tiene:
+ - Credenciales de PBS2
+ - Conectividad hacia PBS2
+ - Conocimiento de que PBS2 existe
+```
+
+Si PBS1 es comprometido, el atacante no puede alcanzar PBS2 porque PBS1 no tiene credenciales ni ruta de red hacia PBS2.
+
+#### Principio 3: PBS2 es invisible
+
+```
+Firewall PBS2 (Polonia):
+ INPUT:
+ - ACCEPT SSH desde IP bastion (solo emergencia)
+ - ACCEPT WG UDP desde peer de gestión
+ - DROP todo lo demás
+
+ OUTPUT:
+ - ACCEPT hacia PBS1:8007 (sync, por WG)
+ - ACCEPT DNS, NTP
+ - DROP todo lo demás
+
+ → PBS2 no tiene puertos abiertos a internet
+ → PBS2 no es alcanzable desde PVE ni desde PBS1
+ → Solo PBS2 decide cuándo hablar con PBS1
+```
+
+#### Principio 4: PBS2 verifica integridad
+
+PBS2 ejecuta verify jobs sobre los backups sincronizados:
+
+```
+PBS2 verify jobs:
+ - schedule: semanal
+ - outdated-after: 14 días
+ - ignore-verified: true
+
+ Si verify falla → alerta Telegram + ticket Zammad (crítico)
+```
+
+Valida que lo que llegó de PBS1 no está corrupto. Si un backup corrupto se sincroniza, lo sabéis antes de necesitarlo.
+
+#### Conectividad entre DCs
+
+| Ruta | Transporte | Cifrado |
+|------|-----------|---------|
+| PVE (Francia) → PBS1 (Alemania) | WireGuard sobre vRack cross-DC o internet | WG cifrado |
+| PBS2 (Polonia) → PBS1 (Alemania) | WireGuard sobre vRack cross-DC o internet | WG cifrado |
+
+OVH vRack puede extenderse entre DCs y países (sin coste extra si ya está contratado). Si no, WireGuard sobre internet funciona bien: el tráfico de sync es eficiente (PBS deduplica, solo transfiere chunks nuevos).
+
+Sync inicial (primera vez): puede ser grande. Syncs diarios: solo incrementales, poco tráfico.
+
+#### Retención detallada
+
+```yaml
+# PBS1 (Alemania) - TRÁNSITO
+# Objetivo: buffer rápido, pocas copias, rota frecuente
+prune-schedule: daily
+keep-last: 5 # últimos 5 backups independiente de fecha
+keep-daily: 3 # 3 días adicionales
+# Total estimado: ~8 snapshots por VM
+# Espacio: moderado (pocos días de retención)
+
+# PBS2 (Polonia) - ARCHIVO
+# Objetivo: retención profunda, recuperación ante desastre
+prune-schedule: daily
+keep-daily: 7 # última semana completa
+keep-weekly: 4 # último mes en semanas
+keep-monthly: 5 # últimos 5 meses
+# Total estimado: ~16 snapshots por VM
+# Espacio: mayor (meses de retención, pero PBS deduplica)
+```
+
+PBS1 y PBS2 aplican su política de retención **independientemente**. PBS1 puede borrar snapshots viejos sin afectar a PBS2 (PBS2 ya tiene su copia).
+
+#### Escenarios de desastre y procedimientos de recuperación
+
+| Desastre | Impacto | Recuperación |
+|----------|---------|-------------|
+| Fallo HW en PVE (Francia) | VMs caídas | Restaurar desde PBS1 Alemania (minutos/horas) |
+| Fallo HW en PBS1 (Alemania) | Backups de tránsito perdidos | PBS2 Polonia tiene todo con retención de meses |
+| Ransomware en PVE | VMs cifradas | PVE no puede borrar PBS1. Restaurar desde PBS1. |
+| Ransomware en PVE + PBS1 | VMs + backups tránsito | PBS2 inaccesible desde ambos. Restaurar desde PBS2. |
+| Ransomware en PVE + PBS1 + PBS2 | Catastrófico | Extremadamente improbable: PBS2 no es alcanzable desde los otros |
+| DC Francia cae (tipo OVH Estrasburgo 2021) | PVE caído | PBS1 (Alemania) + PBS2 (Polonia) intactos |
+| DC Francia + Alemania caen | PVE + PBS1 caídos | PBS2 (Polonia) intacto |
+| Error humano borra datastore en PBS1 | Backups cliente borrados | PBS2 tiene copias con retención de meses |
+| Fallo HW en PBS2 (Polonia) | Copias históricas perdidas | Solo quedan copias de tránsito en PBS1 (retención corta). Opción PBS3 para clientes críticos. |
+
+#### Procedimiento si PBS1 se pierde
+
+1. Contratar/levantar un PBS1 nuevo (Alemania)
+2. Crear datasets ZFS, datastores y usuarios (playbook Ansible `pbs_customer`)
+3. PVE empieza a hacer backups al PBS1 nuevo → se rellena con copias frescas
+4. Configurar WG peer en PBS1 nuevo para que PBS2 pueda conectar
+5. PBS2 empieza a sincronizar (pull) del PBS1 nuevo cuando hay datos
+
+**Hueco**: ~1 día sin capa de tránsito. Las VMs siguen funcionando. PBS2 conserva las copias históricas intactas. El riesgo real es perder una VM durante ese hueco (sin backup disponible en PBS1), pero PBS2 tiene la última sincronización previa al fallo.
+
+#### Procedimiento si PBS2 se pierde
+
+1. Se pierden las copias históricas (retención larga: semanas/meses)
+2. PBS1 sigue funcionando con retención corta (3d/5 copias)
+3. PVE sigue haciendo backups normalmente a PBS1
+4. Contratar/levantar un PBS2 nuevo (Polonia) y configurar sync desde PBS1
+
+**Impacto**: durante el período sin PBS2, solo hay 1 capa de backup (PBS1, retención corta). Si PBS1 también falla durante ese periodo → datos perdidos.
+
+#### Opción PBS3 para clientes críticos (decisión de producto)
+
+Clientes que requieran máxima protección pueden contratar una capa adicional:
+
+```
+PBS3 (opcional por cliente):
+ - Ubicación: otro país/proveedor (ej. Hetzner Finlandia, Scaleway París)
+ - Modelo: PULL de PBS2 (misma lógica)
+ - Retención: igual o más larga que PBS2
+ - Coste: repercutido al cliente
+```
+
+Esto es una **decisión de producto/SLA**, no técnica:
+- El producto base incluye 2 capas (PBS1 tránsito + PBS2 archivo)
+- El producto premium añade PBS3 como 3ª capa
+- Documentar claramente en la definición de producto los riesgos de cada nivel
+- El cliente decide su nivel de protección según su criticidad y presupuesto
+
+#### Monitorización del modelo de backup
+
+| Alerta | Condición | Severidad |
+|--------|-----------|-----------|
+| PBSBackupOld48h | VM sin backup en 48h en PBS1 | Critical |
+| PBSSyncFailed | Sync PBS2←PBS1 falla | Critical |
+| PBSSyncOld48h | PBS2 no sincroniza en 48h | Critical |
+| PBSVerifyErrors | Verify en PBS2 encuentra errores | Critical |
+| PBS1DatastoreSpace90 | Datastore PBS1 > 90% quota | Warning |
+| PBS2DatastoreSpace90 | Datastore PBS2 > 90% | Warning |
+
+---
+
+## 6.3 Backup de configuraciones PVE/PBS a git
+
+### El problema
+
+Si un PVE o PBS se pierde, los datos de VMs están en PBS (backup). Pero la **configuración del propio nodo** (qué VMs había, cómo estaban configuradas, qué datastores, qué usuarios, qué red) está solo en el nodo. ICSManager sabe qué clientes hay, pero no tiene el detalle de cada `.conf` de VM ni cada regla de firewall.
+
+Reconstruir un nodo sin tener su config es posible pero lento: hay que averiguar qué había, preguntar, deducir. Tenerla guardada en git permite saber exactamente qué había y reconstruir rápido.
+
+### DECISION 6.3 (confirmada)
+
+**Copia diaria de directorios de configuración a un repo en Forgejo.**
+
+Son ficheros de texto, poca información (KB), y git muestra exactamente qué cambió y cuándo.
+
+#### Qué se copia
+
+| Servidor | Ruta | Contenido |
+|----------|------|-----------|
+| **PVE** | `/etc/pve/qemu-server/` | Configs de VMs (.conf: disco, RAM, red, opciones) |
+| **PVE** | `/etc/pve/lxc/` | Configs de contenedores |
+| **PVE** | `/etc/pve/storage.cfg` | Definiciones de almacenamiento |
+| **PVE** | `/etc/pve/user.cfg` | Usuarios y permisos |
+| **PVE** | `/etc/pve/datacenter.cfg` | Configuración del datacenter |
+| **PVE** | `/etc/pve/firewall/` | Reglas de firewall por nodo y VM |
+| **PVE** | `/etc/pve/corosync.conf` | Configuración del cluster |
+| **PVE** | `/etc/network/interfaces` | Configuración de red (vmbr, vRack, VLANs) |
+| **PBS** | `/etc/proxmox-backup/datastore.cfg` | Definiciones de datastores |
+| **PBS** | `/etc/proxmox-backup/user.cfg` | Usuarios PBS |
+| **PBS** | `/etc/proxmox-backup/acl.cfg` | Permisos por datastore |
+| **PBS** | `/etc/proxmox-backup/remote.cfg` | Remotos configurados |
+| **PBS** | `/etc/proxmox-backup/sync.cfg` | Sync jobs |
+| **PBS** | `/etc/proxmox-backup/verify.cfg` | Verify jobs |
+| **Ambos** | `/etc/wireguard/` | Configs WG + claves |
+| **Ambos** | ZFS properties (exportadas) | Quotas, reservations, recordsize por dataset |
+
+#### Implementación
+
+Playbook Ansible ejecutado diariamente desde Semaphore:
+
+```yaml
+# playbook: backup-configs.yml
+# Semaphore: diario, 06:00
+- hosts: all_pve:all_pbs
+ tasks:
+ - name: Exportar ZFS properties
+ shell: "zfs get -Hp quota,reservation,recordsize,compression -r pool9 2>/dev/null || true"
+ register: zfs_props
+ changed_when: false
+
+ - name: Guardar ZFS properties en fichero temporal
+ copy:
+ content: "{{ zfs_props.stdout }}"
+ dest: /tmp/zfs-properties.txt
+ changed_when: false
+
+ - name: Recoger configs PVE
+ fetch:
+ src: "{{ item }}"
+ dest: "backup-configs/{{ inventory_hostname }}/"
+ flat: no
+ loop:
+ - /etc/pve/storage.cfg
+ - /etc/pve/user.cfg
+ - /etc/pve/datacenter.cfg
+ - /etc/pve/corosync.conf
+ - /etc/network/interfaces
+ ignore_errors: true
+ when: "'pve' in group_names"
+
+ - name: Recoger configs de VMs PVE
+ shell: "ls /etc/pve/qemu-server/*.conf /etc/pve/lxc/*.conf /etc/pve/firewall/*.fw 2>/dev/null || true"
+ register: vm_configs
+ changed_when: false
+ when: "'pve' in group_names"
+
+ - name: Fetch configs de VMs
+ fetch:
+ src: "{{ item }}"
+ dest: "backup-configs/{{ inventory_hostname }}/"
+ flat: no
+ loop: "{{ vm_configs.stdout_lines | default([]) }}"
+ when: "'pve' in group_names"
+
+ - name: Recoger configs PBS
+ fetch:
+ src: "{{ item }}"
+ dest: "backup-configs/{{ inventory_hostname }}/"
+ flat: no
+ loop:
+ - /etc/proxmox-backup/datastore.cfg
+ - /etc/proxmox-backup/user.cfg
+ - /etc/proxmox-backup/acl.cfg
+ - /etc/proxmox-backup/remote.cfg
+ - /etc/proxmox-backup/sync.cfg
+ - /etc/proxmox-backup/verify.cfg
+ ignore_errors: true
+ when: "'pbs' in group_names"
+
+ - name: Fetch WG configs
+ fetch:
+ src: "{{ item }}"
+ dest: "backup-configs/{{ inventory_hostname }}/"
+ flat: no
+ loop:
+ - /etc/wireguard/
+ ignore_errors: true
+
+ - name: Fetch ZFS properties
+ fetch:
+ src: /tmp/zfs-properties.txt
+ dest: "backup-configs/{{ inventory_hostname }}/"
+ flat: no
+
+- hosts: localhost
+ tasks:
+ - name: Commit y push al repo Forgejo
+ shell: |
+ cd backup-configs/
+ git add -A
+ git diff --cached --quiet || git commit -m "Config backup {{ ansible_date_time.date }}"
+ git push
+```
+
+#### Resultado en Forgejo
+
+```
+infra/backup-configs/
+├── pve-node1/
+│ ├── etc/pve/qemu-server/
+│ │ ├── 100.conf ← VM cliente ACME
+│ │ ├── 101.conf ← VM cliente BETA
+│ │ └── ...
+│ ├── etc/pve/storage.cfg
+│ ├── etc/pve/user.cfg
+│ ├── etc/network/interfaces
+│ └── tmp/zfs-properties.txt
+├── pve-node2/
+│ └── ...
+├── pbs-alemania/
+│ ├── etc/proxmox-backup/datastore.cfg
+│ ├── etc/proxmox-backup/sync.cfg
+│ └── tmp/zfs-properties.txt
+└── pbs-polonia/
+ └── ...
+```
+
+Cada commit muestra el diff: "se añadió VM 105, se cambió la RAM de la 100 de 4GB a 8GB, se creó datastore nuevo".
+
+#### Valor operativo
+
+| Escenario | Sin backup de configs | Con backup en git |
+|-----------|----------------------|-------------------|
+| PVE se pierde | "¿Qué VMs había? ¿Con qué config?" | `git show pve-node1/etc/pve/qemu-server/` → lista exacta |
+| Alguien cambia config de VM | Nadie se entera | Diff en el commit diario |
+| Técnico nuevo necesita ver infra | SSH a cada nodo | Lee el repo en Forgejo |
+| Auditoría: "¿cuándo se cambió esto?" | No hay registro | `git log --follow fichero.conf` |
+| Reconstruir nodo | Horas/días adivinando | Minutos leyendo configs exactas |
+
+#### Complemento con Ansible (evolución)
+
+Fase 1 (día 1): backup de configs a git = **foto de lo que hay**
+Fase 2 (mes 1-2): Ansible gestiona configs = **declaración de lo que debe haber**
+
+Ambos conviven: Ansible aplica la config deseada, el backup a git verifica que lo real coincide con lo declarado. Si alguien cambia algo manualmente, el diff del backup lo delata.
+
+---
+
+## 6.4 Réplica de VMs de gestión a oficina (contingencia fuera de OVH)
+
+### El problema
+
+Toda la infraestructura de gestión (monitoring, seguridad, SSOT, SSO) vive en un único servidor en OVH. Si OVH pierde ese servidor, nos quedamos ciegos: sin métricas, sin logs, sin alertas, sin Netbox, sin Authentik. Los datos de clientes están protegidos por 3 capas geográficas (6.2), pero la infra que nos da visibilidad no.
+
+### DECISION 6.4 (confirmada)
+
+**Replicación ZFS incremental de las VMs críticas de gestión al servidor de oficina (Xeon 96GB existente).**
+
+Coste: cero (hardware que ya existe). Las VMs están apagadas en oficina, solo se encienden en contingencia.
+
+#### Qué se replica
+
+| VM | Replicar | Razón |
+|----|----------|-------|
+| **Monitoring** | Sí | Sin ella: sin Grafana, sin métricas, sin logs, ceguera total |
+| **Wazuh** | Sí | Sin ella: sin detección de seguridad, sin FIM |
+| **Servicios** | Sí | Netbox (SSOT), Authentik (SSO), Semaphore, Outline |
+| **Bastion** | No | Sin estado, se recrea en minutos con Ansible |
+| **WG Hub A/B** | No | Sin estado, configs en git (6.3), se recrean |
+
+#### Arquitectura
+
+```
+OVH (servidor gestión) Oficina (Xeon 96GB, existente)
+┌──────────────────────┐ ┌──────────────────────┐
+│ PVE gestión │ │ PVE contingencia │
+│ pool: local-zfs │ ZFS send/recv │ pool: local-zfs │
+│ │ incremental │ │
+│ VM Monitoring ──────│──── WG ────────►│ VM Monitoring (rpl) │
+│ VM Wazuh ───────────│──── WG ────────►│ VM Wazuh (rpl) │
+│ VM Servicios ───────│──── WG ────────►│ VM Servicios (rpl) │
+│ │ │ │
+│ │ Cada 4h │ VMs apagadas │
+│ │ ~1-5 GB/sync │ Solo encienden en │
+│ │ cifrado por WG │ contingencia │
+└──────────────────────┘ └──────────────────────┘
+```
+
+#### Implementación
+
+Playbook Ansible ejecutado desde Semaphore cada 4 horas:
+
+```yaml
+# playbook: replicate-to-office.yml
+# Semaphore: cada 4h
+- hosts: pve_gestion
+ vars:
+ office_host: "oficina-pve.mgmt.vpn6.com.es" # por WG
+ vms_to_replicate:
+ - vm-monitoring
+ - vm-wazuh
+ - vm-servicios
+ tasks:
+ - name: Crear snapshot incremental
+ shell: "zfs snapshot local-zfs/{{ item }}@replicate-$(date +%Y%m%d-%H%M)"
+ loop: "{{ vms_to_replicate }}"
+
+ - name: Enviar incremental a oficina
+ shell: |
+ LATEST=$(zfs list -t snapshot -o name -s creation -r local-zfs/{{ item }} | grep replicate | tail -1)
+ PREV=$(zfs list -t snapshot -o name -s creation -r local-zfs/{{ item }} | grep replicate | tail -2 | head -1)
+ if [ "$PREV" != "$LATEST" ]; then
+ zfs send -i "$PREV" "$LATEST" | ssh {{ office_host }} zfs recv -F tank/{{ item }}
+ else
+ zfs send "$LATEST" | ssh {{ office_host }} zfs recv -F tank/{{ item }}
+ fi
+ loop: "{{ vms_to_replicate }}"
+
+ - name: Rotar snapshots antiguos (mantener últimos 12 = 48h)
+ shell: |
+ zfs list -t snapshot -o name -s creation -r local-zfs/{{ item }} | grep replicate | head -n -12 | xargs -r -n1 zfs destroy
+ loop: "{{ vms_to_replicate }}"
+```
+
+#### Ancho de banda
+
+| Parámetro | Valor |
+|-----------|-------|
+| Tamaño total VMs a replicar | ~400-700 GB |
+| Sync inicial (primera vez) | Grande: programar de noche o seed con disco físico |
+| Sync incremental cada 4h | ~1-5 GB (solo bloques cambiados) |
+| Ancho de banda sostenido | ~5-10 Mbps (factible con fibra de oficina) |
+| Transporte | WireGuard cifrado (red de gestión) |
+
+#### Plan de contingencia
+
+| Paso | Acción | Tiempo |
+|------|--------|--------|
+| 1 | Detectar que el servidor OVH está caído | Minutos (Uptime Kuma externo, ping, o aviso OVH) |
+| 2 | Encender VMs réplica en PVE oficina | 2 minutos |
+| 3 | Actualizar DNS/WG para apuntar a oficina | 5 minutos |
+| 4 | Monitoring + Wazuh + Servicios operativos | ~10 minutos total |
+
+**No es HA automático** (no hace falta). Es un plan B manual que se activa en emergencia. Runbook documentado en Outline.
+
+#### Qué NO se replica aquí
+
+- VMs de clientes (producción): protegidas por 3 capas geográficas PBS (6.2)
+- Configs de PVE/PBS: protegidas por backup a git (6.3)
+- WG Hub / Bastion: sin estado, se recrean con Ansible
+
+Esta capa protege específicamente la **infraestructura de gestión** para no quedarse ciego si OVH tiene un fallo mayor.
+
+#### Monitorización
+
+| Alerta | Condición | Severidad |
+|--------|-----------|-----------|
+| ReplicaOfficeOld8h | Última réplica a oficina > 8h | Warning |
+| ReplicaOfficeFailed | Playbook de réplica falla | Critical |
+
+---
+
+## Resumen Capa 6 completa (DECISIONES FINALES)
+
+```
+┌─────────────────────────────────────────────────────┐
+│ CAPA 6: ALMACENAMIENTO Y DATOS │
+├─────────────────────────────────────────────────────┤
+│ │
+│ 6.1 ZFS (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Solo monitorización │ │
+│ │ - node_exporter → métricas ZFS │ │
+│ │ - Alertas: degraded, space, scrub│ │
+│ │ - Ansible: optimización nuevos │ │
+│ │ Sanoid/Syncoid/zrepl: DESCARTADO │ │
+│ │ (PBS gestiona nativamente) │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 6.2 BACKUP 3 CAPAS (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ PVE (Francia) │ │
+│ │ → backup a PBS1 (Alemania) │ │
+│ │ → permisos: solo Backup+Reader │ │
+│ │ → NO puede borrar │ │
+│ │ │ │
+│ │ PBS1 (Alemania) - TRÁNSITO │ │
+│ │ → retención corta (3d/5 copias) │ │
+│ │ → PVE escribe, PBS2 lee │ │
+│ │ │ │
+│ │ PBS2 (Polonia) - ARCHIVO │ │
+│ │ → retención larga (7d/4w/5m) │ │
+│ │ → PULL de PBS1 (no push) │ │
+│ │ → Invisible: no alcanzable │ │
+│ │ → Verify semanal │ │
+│ │ │ │
+│ │ Conectividad: WG sobre vRack │ │
+│ │ cross-DC o internet │ │
+│ │ │ │
+│ │ 3 países, 3 jurisdicciones │ │
+│ │ Francia → Alemania → Polonia │ │
+│ │ │ │
+│ │ Recuperación PBS1 perdido: │ │
+│ │ → Nuevo PBS1 + PVE replica │ │
+│ │ → PBS2 pull cuando hay datos │ │
+│ │ → Hueco ~1 día │ │
+│ │ │ │
+│ │ Recuperación PBS2 perdido: │ │
+│ │ → Copias históricas perdidas │ │
+│ │ → PBS1 sigue con retención corta│ │
+│ │ → Opción PBS3 para clientes │ │
+│ │ críticos (decisión de producto)│ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 6.3 CONFIGS A GIT (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ Backup diario de /etc/pve/ y │ │
+│ │ /etc/proxmox-backup/ a Forgejo │ │
+│ │ - Configs VMs, storage, users │ │
+│ │ - Red, firewall, WG, ZFS props │ │
+│ │ - Playbook Ansible + Semaphore │ │
+│ │ - Cada commit = diff de cambios │ │
+│ │ - Coste: 0 (texto, KB, Forgejo │ │
+│ │ ya existe) │ │
+│ └───────────────────────────────────┘ │
+│ │
+│ 6.4 RÉPLICA OFICINA (confirmada) │
+│ ┌───────────────────────────────────┐ │
+│ │ ZFS send/recv incremental cada │ │
+│ │ 4h a servidor oficina (Xeon 96GB)│ │
+│ │ - VMs: Monitoring, Wazuh, │ │
+│ │ Servicios │ │
+│ │ - VMs apagadas, solo emergencia │ │
+│ │ - Transporte: WG cifrado │ │
+│ │ - Contingencia: ~10 min levant. │ │
+│ │ - Coste: 0 (HW ya existe) │ │
+│ │ - Protege: infra de gestión │ │
+│ │ (no datos de clientes) │ │
+│ └───────────────────────────────────┘ │
+│ │
+└─────────────────────────────────────────────────────┘
+
+Recursos Capa 6:
+- PBS1: ya existente (Alemania)
+- PBS2: servidor nuevo por contratar (Polonia)
+ - Dimensionado según volumen de datos × retención
+ - Solo necesita disco (CPU/RAM mínimos, PBS es eficiente)
+- PBS3 (opcional): por cliente premium, otro proveedor/país
+- ZFS monitoring: ya cubierto por node_exporter (0 extra)
+- Réplica oficina: servidor Xeon 96GB existente (0 coste)
+```