# Runbook — Despliegue a PRODUCCIÓN · CRM Agente de Carga

> **Quién ejecuta:** un operador con acceso al servidor de producción y a la base de
> datos de prod. **Este runbook no lo ejecuta ningún agente automático.**
> **Prerrequisito de proceso:** el PR de `feature/crm-workspace-altas-org-usuario`
> debe estar **revisado y mergeado** a la rama que corresponda antes de desplegar.
>
> **Antes de empezar: respaldo.** Toma un backup de la base `crm_core` de prod
> (`pg_dump`) y del `.env` actual. Sin respaldo verificado, no continúes.

Este cambio es grande (auth/RBAC): login 100% vía Workspace/Hub, **sesión local
(patrón SIWEB)**, gestión de compañías/usuarios/roles y provisión vía Hub. Léelo
completo antes de tocar prod.

---

## 0. Alcance del cambio

- **Auth:** el login deja de hablar directo con Keycloak; todo pasa por el Hub
  (App Launcher → `/auth/sso?relay=<uuid>` → `POST {HUB_URL}/api/v1/auth/sso-exchange`).
- **Sesión local:** cookie de sesión propia (HS256) + token KC guardado en **valkey**
  por `crm_sid`. Requiere valkey arriba. Se controla con `SESSION_STORE_ENABLED`.
- **RBAC/Compañías:** compañías en `a76.company` por tenant; roles por carril
  (Ventas, Operaciones, Facturación, Consulta); permisos por módulo.

**Migraciones de esquema:** este set **no** agrega tablas nuevas (usa `core.*`,
`a76.company` y valkey). Aun así, **verifica migraciones pendientes** en prod antes
de desplegar (paso 5). No corras migraciones a ciegas.

---

## 1. Prerrequisitos de infraestructura

- [ ] DNS de prod (p. ej. `crm.aduanasoft.com`) apuntando al server de prod.
- [ ] Docker + Docker Compose en el server.
- [ ] Servicios del stack: `backend`, `frontend`, `postgres`, `valkey`, `minio`,
      `celery_worker`, `celery_beat`.
- [ ] **valkey** operativo (lo usan la sesión local y `hub_token`).
- [ ] nginx + TLS (certbot) para servir app + API en el **mismo origen**.
- [ ] Acceso al **Hub de producción** (no el de testing) y al client/realm correctos.

---

## 2. Variables de entorno (`.env` en el server de prod)

Basado en `deploy/env.testing.example`, pero con **hosts, dominio y secretos de
producción**. Nunca subas el `.env` con secretos al repo.

```env
# --- Seguridad ---
ENVIRONMENT=production
DEV_LOCAL_AUTH=false
SECRET_KEY=<openssl rand -hex 32>

# --- Sesión local (patrón SIWEB) ---
SESSION_STORE_ENABLED=true
SESSION_IDLE_MINUTES=30
SESSION_MAX_HOURS=10
VALKEY_URL=redis://valkey:6379/0

# --- Workspace / Hub de PRODUCCIÓN (mismo Hub que genera el relay) ---
WORKSPACE_URL=https://<hub-produccion>
HUB_URL=https://<hub-produccion>
INTERNAL_HUB_URL=https://<hub-produccion>
VITE_HUB_URL=https://<hub-produccion>

# --- Keycloak (single-realm / single-client) ---
KEYCLOAK_URL=https://<keycloak-produccion>/kcauth
VITE_KEYCLOAK_URL=https://<keycloak-produccion>/kcauth
KEYCLOAK_REALM=master
KEYCLOAK_CLIENT_ID=aduanasoft
KEYCLOAK_CLIENT_SECRET=<secret del producto provisionado en prod>

# --- Dominio del CRM (mismo origen app + API vía nginx) ---
ORIGIN=https://<dominio-crm-produccion>
APP_PUBLIC_URL=https://<dominio-crm-produccion>
VITE_API_URL=https://<dominio-crm-produccion>/api/
INTERNAL_API_URL=http://backend:8000/api/
CORS_ORIGINS=https://<dominio-crm-produccion>

# --- PostgreSQL ---
CORE_DB_HOST=postgres
CORE_DB_PORT=5432
CORE_DB_NAME=crm_core
CORE_DB_USER=<usuario>
POSTGRES_APP_PASSWORD=<password fuerte>

# --- MinIO / S3 ---
S3_ENDPOINT_URL=http://minio:9000
S3_ACCESS_KEY=<access>
S3_SECRET_KEY=<secret>
S3_BUCKET=crm
S3_REGION=us-east-1
S3_USE_SSL=false
```

> **Importante:** `ENVIRONMENT=production` hace que el bootstrap de permisos solo dé
> `super_admin` al **primer** usuario (no a todos). Es el comportamiento deseado en prod.

---

## 3. Build del frontend — **en CI, no en el server**

La VM de prod no debe compilar el frontend (el build satura RAM). Compila la imagen
en **Jenkins/CI** y publícala al registry, o compílala en una máquina de build:

```bash
docker compose -f docker-compose.yml -f docker-compose.prod.yml build frontend
# push al registry interno si aplica
```

El backend usa la imagen/código directamente (uvicorn sin --reload).

---

## 4. Despliegue

```bash
# En el server de prod, en el directorio del proyecto:
docker compose -f docker-compose.yml -f docker-compose.prod.yml pull        # si usas registry
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
docker compose ps            # verifica que todos queden healthy
```

---

## 5. Post-despliegue (datos) — **operador humano, con respaldo hecho**

1. **Verificar migraciones pendientes** (si el proyecto usa Alembic u otro):
   revisar y aplicar **solo** las que correspondan, con backup previo.
2. **Seed base** (compañía por tenant + carriles), equivalente a `seed_crm.py`
   (`ensure_company` + roles Ventas/Operaciones/Facturación/Consulta).
3. **Sync de permisos** (registra el catálogo por módulo):

```bash
docker compose exec backend python -c "from api.v1.modules.core.permissions.service import PermissionService; from core.database import CoreSessionLocal; PermissionService(CoreSessionLocal()).sync_permissions()"
```

---

## 6. Verificación / smoke test

```bash
curl -fsS https://<dominio-crm-produccion>/api/health && echo OK
```

- [ ] Login vía **App Launcher del Workspace** entra sin bucle.
- [ ] El dashboard carga compañías del tenant (switcher con el tenant correcto).
- [ ] **Usuarios**: lista carga tras refrescar; alta por invitación crea enlace.
- [ ] **Roles y permisos**: catálogo por módulo carga; marcar un permiso lo guarda
      (toast) y persiste al refrescar.
- [ ] Licencia válida (sin bucle 401 / "sesión expirada").

---

## 7. Rollback

1. `docker compose ... up -d` con la **imagen/tag anterior** del frontend/backend.
2. Restaurar `.env` anterior si se cambió.
3. Restaurar el backup de `crm_core` **solo** si hubo cambios de datos irreversibles.
4. `SESSION_STORE_ENABLED=false` revierte al comportamiento previo de sesión sin
   redeploy de código (feature-flag), como mitigación rápida.
