# Operación segura, despliegue local y migración Docker

Esta guía define el camino incremental para mantener el servicio actual con PM2, preparar su migración a Docker en el servidor accesible por Tailscale y conservar una vuelta atrás segura.

## 1. Estrategia recomendada

La migración no requiere eliminar el servicio actual ni hacer un corte único. El orden recomendado es:

1. Estabilizar y automatizar PM2 en el equipo actual.
2. Preparar Docker en el servidor de destino.
3. Restaurar una copia de la base de datos en el destino.
4. Arrancar el contenedor en un puerto alternativo y con `SCHEDULER_ENABLED=false`.
5. Verificar `/health`, login, consultas, scrapers seleccionados y envío de correo controlado.
6. Detener el scheduler local y activar el scheduler remoto.
7. Mantener PM2 local detenido, pero disponible como rollback durante un periodo prudente.
8. Retirar el servicio local sólo cuando el remoto haya demostrado estabilidad.

El esfuerzo es moderado, no especialmente alto. Las partes delicadas son Playwright/navegadores, la copia consistente de MySQL, los secretos y evitar dos schedulers activos. La migración incremental reduce esos riesgos.

## 2. Variables de tiempo

La aplicación utiliza estas convenciones:

- `TZ=Europe/Madrid`: zona del proceso y de los logs PM2.
- `APP_TIMEZONE=Europe/Madrid`: hora local mostrada por la aplicación.
- `SCRAPING_TIMEZONE=Europe/Madrid`: zona utilizada por el scheduler.
- Las fechas de APIs y auditorías se expresan en ISO 8601 UTC y terminan en `Z`.

Ejemplo de conversión: `2026-08-07T23:17:00Z` equivale a `2026-08-08 01:17` en Madrid durante el horario de verano. No son dos ejecuciones distintas.

Comprobar la zona del proceso:

```powershell
pm2 env lliria-properties | Select-String "TZ|APP_TIMEZONE|SCRAPING_TIMEZONE"
```

## 3. Health checks

Los endpoints no requieren autenticación y no crean una sesión:

- `GET /health/live`: confirma que el proceso Node responde.
- `GET /health`: confirma proceso y conexión con MySQL.

Prueba manual:

```powershell
Invoke-RestMethod http://127.0.0.1:3000/health/live
Invoke-RestMethod http://127.0.0.1:3000/health
```

`/health` devuelve HTTP `200` cuando MySQL está disponible y HTTP `503` cuando la aplicación está viva pero la base de datos no supera la comprobación.

El resultado no publica host, usuario, contraseña ni detalles internos del error de MySQL.

## 4. Despliegue PM2 automatizado

Validar primero sin migraciones, sustitución de `dist` ni reinicio:

```powershell
npm run deploy:local -- -ValidateOnly -SkipMigrations
```

Despliegue normal:

```powershell
npm run deploy:local
```

Si cambió `package-lock.json`:

```powershell
npm run deploy:local -- -InstallDependencies
```

El script `tools/deploy-local.ps1` realiza:

1. Comprobación de `.env`.
2. Compilación en un directorio aislado.
3. Verificación sintáctica de `app.js`.
4. Migraciones compatibles hacia delante.
5. Copia de seguridad de la versión anterior de `dist`.
6. `pm2 reload` o primer `pm2 start`.
7. Espera activa hasta que `/health` confirme proceso y MySQL.
8. `pm2 save` y auditoría JSONL local.

Si falla después de sustituir `dist`, recupera automáticamente el código anterior y recarga PM2. Las migraciones de base de datos no se revierten automáticamente; deben seguir siendo compatibles con la versión previa.

Artefactos y auditoría:

```text
storage/deployments/releases/<fecha>/
storage/deployments/backups/<fecha>/
storage/deployments/audit.jsonl
```

## 5. Despliegue PM2 manual

Utilizar este procedimiento si el script no está disponible:

```powershell
Set-Location C:\xampp\htdocs\captajaus\lliria-properties-scraper

# Sólo cuando hayan cambiado las dependencias
npm ci

npm run build
npm run migrate
pm2 reload ecosystem.config.js --only lliria-properties --update-env

$health = Invoke-RestMethod http://127.0.0.1:3000/health
if ($health.status -ne "ok" -or $health.checks.database.status -ne "up") {
  throw "El servicio no está preparado"
}

pm2 save --force
```

Antes de desplegar manualmente, conservar una copia de `dist`. Si la verificación falla, restaurar esa copia y volver a ejecutar `pm2 reload`. No borrar la copia hasta completar la validación funcional.

## 6. Rotación de logs

Instalación y configuración automatizada:

```powershell
npm run logs:configure
```

Valores predeterminados:

- Rotación al superar `20M`.
- Rotación diaria a medianoche.
- 14 archivos retenidos.
- Compresión activada.

Cambiar límites:

```powershell
powershell -NoProfile -ExecutionPolicy Bypass -File tools\configure-pm2-logrotate.ps1 -MaxSize 50M -Retain 30
```

Comprobar configuración:

```powershell
pm2 conf pm2-logrotate
pm2 list
```

No ejecutar `pm2 flush` sin haber decidido expresamente perder los logs actuales. Para liberar espacio, forzar primero una rotación, comprobar el archivo rotado y mover los históricos a almacenamiento externo antes de eliminarlos.

## 7. Gestión manual de `.env` y copias antiguas

Los secretos no deben copiarse a documentación, tickets, logs, GitHub ni imágenes Docker.

### Comparar únicamente nombres de variables

Este procedimiento no imprime los valores:

```powershell
$activeKeys = Get-Content -LiteralPath .env |
  Where-Object { $_ -match '^\s*[A-Za-z_][A-Za-z0-9_]*\s*=' } |
  ForEach-Object { ($_ -split '=', 2)[0].Trim() }

$backupPath = Resolve-Path '.env.before-session-secret-20260803-002122.bak'
$backupKeys = Get-Content -LiteralPath $backupPath |
  Where-Object { $_ -match '^\s*[A-Za-z_][A-Za-z0-9_]*\s*=' } |
  ForEach-Object { ($_ -split '=', 2)[0].Trim() }

Compare-Object $activeKeys $backupKeys
```

### Retirada segura de la copia

1. Confirmar la ruta exacta con `Resolve-Path`.
2. Si debe conservarse, moverla a almacenamiento cifrado fuera del proyecto.
3. Confirmar que el `.env` activo contiene todas las variables necesarias.
4. Eliminar únicamente la ruta literal confirmada:

```powershell
$backupPath = Resolve-Path '.env.before-session-secret-20260803-002122.bak'
Remove-Item -LiteralPath $backupPath
```

5. Si la copia pudo salir del equipo, rotar `SESSION_SECRET`, credenciales MySQL, correo, API keys y tokens relacionados.

La copia ya queda protegida frente a una futura incorporación accidental a Git mediante `.gitignore` y frente al contexto de Docker mediante `.dockerignore`.

## 8. Preparación del servidor Docker por Tailscale

Antes de conectar es necesario conocer uno de estos datos completos:

- IP Tailscale completa del servidor terminado en `.16`, o
- nombre MagicDNS del servidor.

No se debe publicar el puerto 3000 en la interfaz pública. Durante la prueba, enlazar el servicio sólo a la IP Tailscale o limitarlo mediante firewall.

### Fase de prueba paralela

- Servicio remoto en puerto `3001`.
- Base de datos clonada o usuario MySQL de sólo lectura para las primeras pruebas.
- `SCHEDULER_ENABLED=false`.
- `SESSION_COOKIE_SECURE=false` mientras se use HTTP exclusivamente dentro de Tailscale.
- Activar `SESSION_COOKIE_SECURE=true` cuando exista HTTPS.

Validaciones mínimas:

```text
/health/live responde 200
/health responde 200 y database=up
login correcto
dashboard y filtros correctos
una ejecución manual controlada de scraper
persistencia tras reiniciar el contenedor
logs y rotación operativos
backup y restauración MySQL probados
```

### Corte definitivo

1. Pausar ejecuciones y obtener una copia final consistente de MySQL.
2. Restaurarla en el servidor.
3. Detener o desactivar el scheduler local.
4. Activar `SCHEDULER_ENABLED=true` sólo en Docker.
5. Reiniciar el contenedor y verificar `/health`.
6. Mantener PM2 local detenido como rollback; no eliminarlo todavía.

Nunca deben quedar simultáneamente dos instancias con scheduler activo sobre la misma base de datos.

## 9. GitHub futuro, sin activarlo ahora

El proyecto ya incluye una base de `.gitignore`, pero no se inicializa ningún repositorio como parte de esta fase.

Cuando se decida usar GitHub:

```powershell
git init
git branch -M main
git add .
git status
git commit -m "Initial private repository"
git remote add origin <URL_REPOSITORIO_PRIVADO>
git push -u origin main
```

Antes del primer `git add`:

```powershell
git check-ignore -v .env
git check-ignore -v .env.before-session-secret-20260803-002122.bak
git status --ignored
```

Requisitos para automatizar despliegues desde GitHub más adelante:

- Repositorio privado.
- Ningún `.env` o backup versionado.
- Build y pruebas en cada cambio.
- Despliegue sólo desde una rama protegida.
- Secretos almacenados en el servidor o gestor de secretos, no en el repositorio.
- Runner con acceso autorizado a la red Tailscale o runner propio en el servidor.
- Health check obligatorio y rollback si falla.

Hasta entonces, `tools/deploy-local.ps1` proporciona trazabilidad local sin depender de Git.

