# Deploy — modo mantenimiento y páginas de error

> Movido desde `CLAUDE.md`, que solo conserva el resumen de una línea.

`deploy/deploy.sh` **cierra el sitio mientras despliega**: `php artisan down --render="errors::503"`
al principio y `php artisan up` al final. El `--render` pre-renderiza la vista a HTML plano en
`storage/framework/maintenance.php`, que `public/index.php` incluye **antes de `vendor/autoload.php`**
→ la página se sigue viendo aunque `composer install` esté reescribiendo `vendor/` o `npm run build`
haya borrado el manifest de Vite.

- **`resources/views/errors/503.blade.php` tiene reglas propias** por eso mismo: CSS **inline**
  (nada de `@vite`), SVG inline, **cero** dependencias de sesión / rutas / contenedor
  (`url()->previous()`, `auth()`… reventarían: se renderiza desde consola).
- **El idioma no se resuelve por request** (es un HTML fijo para todos): la vista embebe los
  **6 idiomas** en un diccionario JS armado con `__($key, [], $locale)` y elige por
  `navigator.language`. Las cadenas siguen viviendo en `lang/*.json` como siempre.
- Si el deploy **falla a la mitad, el sitio queda en mantenimiento a propósito** (mejor eso que
  código a medias). Se reabre con `sudo -u www-data php artisan up`.
- `DEPLOY_BYPASS_SECRET=<frase> bash deploy/deploy.sh` permite ver el sitio real durante el
  mantenimiento visitando `https://dominio/<frase>` una vez.
- Familia de vistas de error propias (layout verde + partículas, todas traducidas):
  `403`, `404`, `429`, **`500`**, **`503`**. **`APP_DEBUG` debe estar en `false` en prod** o las
  excepciones muestran la pantalla de debug (fondo oscuro con stack trace) en vez de estas vistas;
  el deploy avisa si lo detecta en `true`.

## Gotcha: `public/build` necesita `775`, no `755` (falló el deploy del 2026-09-21)

`npm ci`/`npm run build` (PASO 3) corren como el usuario de deploy (`lcastellanos`), no como
`www-data` — a diferencia de casi todo lo demás del script. Si `public/build` queda en `755` (dueño
`www-data`, sin escritura de grupo), Vite no puede vaciar `public/build/assets` al reconstruir y el
build falla con `EACCES`; como el deploy falla a la mitad, el sitio **queda en mantenimiento a
propósito** y no se reabre solo.

`vendor/`, `storage/` y `bootstrap/cache/` son `775` (dueño `www-data`, grupo `www-data` con
escritura) — `lcastellanos` es miembro del grupo `www-data` en el servidor, así que con `775`
funciona igual que en esas carpetas. `public/build` quedó en `755` (un `755` suelto, no se sabe
exactamente de qué paso) tras el deploy en caliente del 2026-09-17. **Fix aplicado en el servidor:**
`chmod 775 public/build`. Si vuelve a pasar, es el mismo síntoma.

**Segundo síntoma relacionado, mismo deploy:** `fix-permissions.sh` (PASO final, corre como
`www-data`) reportó "2 sin permiso para cambiar" — son `bootstrap/cache/services.php` y
`bootstrap/cache/packages.php`, la caché de descubrimiento de paquetes que Composer regenera en
**cada** `composer install` (PASO 2, corre como `lcastellanos`) con `+x` y dueño `lcastellanos`, no
`www-data`. `chmod` solo lo puede hacer el dueño (o root), así que `www-data` no tiene autoridad para
tocarlos — **no es un fallo nuevo, va a repetirse en cada deploy** si no se corrige en la raíz.
**Fix:** `deploy.sh` ahora le quita el `+x` a esos dos archivos por **nombre explícito**, justo
después de `composer install` (mientras `lcastellanos` todavía es el dueño) — a propósito no es un
glob `*.php` ni toca todo `bootstrap/cache/`, para que un archivo distinto con `+x` que aparezca ahí
siga marcado como "sin permiso para cambiar" en vez de pasar desapercibido.

## Robustez del script (2026-09-21): binarios, rama, cambios locales, `--ff-only`, flags, rollback

Comparando con el `deploy.sh` de otro proyecto hermano (puri-facil.com) se detectaron 7 mejoras sin
trade-off aparente, ya incorporadas:

1. **Chequeo de binarios al principio** (`php`, `composer`, `npm` salvo `--skip-assets`): si falta
   uno, aborta con mensaje claro **antes** de activar el modo mantenimiento — antes el script fallaba
   a medio camino con un error de bash confuso.
2. **Bloqueo si hay cambios locales sin commitear** (`git diff --quiet` + `git diff --cached --quiet`
   en el PASO 1, antes del `fetch`/`pull`): si hay algo, muestra `git status --short` y aborta. Esto
   requirió resolver el punto siguiente primero (ver "docs de asistencia" abajo), porque antes el
   working tree del servidor quedaba "sucio" a propósito entre deploys.
3. **Verificación de rama antes de tocar nada**: compara `git rev-parse --abbrev-ref HEAD` contra
   `production` (hardcodeado — este proyecto no despliega otra rama) y **aborta con instrucciones**
   si no coincide, en vez del `git checkout production` a ciegas de antes (que pisaría en silencio un
   checkout manual dejado para debug).
4. **`git pull --ff-only`** en vez de `git pull` a secas: si el histórico divergió, falla en vez de
   crear un merge automático no revisado en el servidor.
5. **Flag `--dry-run`**: envuelve todo lo que modifica estado (git, composer, npm, artisan, chmod,
   `install -d`) en una función `run()` que en modo dry-run solo imprime `[dry-run] <comando>`. Los
   chequeos de solo lectura (binarios, rama, cambios locales) corren igual, para que el dry-run sirva
   de vista previa real del plan completo. **No se probó contra un deploy real** (el entorno local no
   tiene `php`/`sudo -u www-data`/systemd del servidor) — sí se probó la sintaxis (`bash -n`), el
   parseo de flags, y la lógica de rama/cambios-locales aislada en un repo Git descartable.
6. **Flags `--skip-assets`** (salta PASO 3, `npm ci` + `npm run build`) y **`--skip-perms`** (salta
   `fix-permissions.sh`, la corrección masiva de `storage`/`vendor`/`bootstrap/cache`/`public/build`)
   — para deploys donde esos pasos no aplican.
7. **Rollback en el mensaje de error**: `PREVIOUS="$(git rev-parse --short HEAD)"` se guarda justo
   antes del `pull`; si el deploy falla en cualquier paso posterior, el `trap` de salida
   (`on_deploy_exit`) imprime el commit previo y el comando exacto (`git reset --hard $PREVIOUS`)
   para volver atrás — además del aviso ya existente de "el sitio sigue en mantenimiento".

**Deliberadamente NO se copiaron** dos puntos del script hermano:
- Un trap que **siempre** reabre el sitio al salir, pase lo que pase. ITG ya decide lo contrario a
  propósito (ver más arriba: "el sitio queda en mantenimiento a propósito" si el deploy falla a la
  mitad) — es una decisión de producto válida, no un bug, y ya estaba documentada.
- Una verificación post-`config:cache` de que una key crítica leída con `env()` fuera de `config/`
  se siga leyendo bien cacheada (el bug clásico de `env()` en vez de `config()`, que con config
  cacheada devuelve `null` en silencio). Se revisó: `grep -rl "env(" app routes resources/views` no
  devuelve nada — ITG no tiene ese patrón hoy, así que no hay ningún caso conocido que proteger.

### Los docs de asistencia (`CLAUDE.md` y afines) ya no se borran del servidor

Antes, `CLAUDE.md`, `docs/CLAUDE-VUEXY.md` y `docs/ESTRUCTURA-TICKETS-BLADE.md` se **restauraban con
`git checkout --` antes del `pull`** (para que el working tree quedara limpio y el pull no chocara con
"local changes would be overwritten") y se **borraban de nuevo después** (`rm -f`) — quedaban
versionados en git pero nunca presentes en el servidor. Eso dejaba el working tree "sucio" entre
deploys (esos archivos siempre aparecían como borrados sin commitear) y hacía imposible el chequeo del
punto 2 de arriba. **Ahora simplemente se quedan** en el servidor como cualquier otro archivo
versionado, sin tratamiento especial (ya llegan con permisos restrictivos, no hace falta que
`deploy.sh` los gestione).

⚠️ **Bug preexistente detectado al migrar (2026-09-21):** la lista también incluía
`docs/MIGRACION-SERVICE-EXCEPTIONS.md` — un doc que **ya no existe**, borrado el 2026-09-17
(`92e2a59e`, "la migración está hecha") sin sacarlo de la lista. El `git checkout --` viejo lo toleraba
en silencio (con varios pathspecs, si uno no matchea nada, git aborta el comando entero y ningún
archivo se restaura). La lista correcta son los 3 docs que sí existen.
