# Runbook de operaciones

El documento menos glamuroso y el que más dolor evita. La base de desarrollo se
perdió dos veces por algo que cabe en tres líneas. Aquí van esas líneas.

## Antes de cualquier migración: backup obligatorio

No se corre una migración sobre datos reales sin un dump previo. Las migraciones
estructurales de este proyecto son **irreversibles** (`down()` lanza excepción):
el único camino de vuelta es el backup.

## Importar un dump de phpMyAdmin: quitar el `USE`

Los dumps de phpMyAdmin traen una línea `USE \`fermenty_dev\`;` (y a veces
`CREATE DATABASE`). Si cargas ese dump en otra base, el `USE` la ignora y escribe
en `fermenty_dev`, machacándola. Ocurrió dos veces. Antes de importar:

```bash
grep -vE '^(USE|CREATE DATABASE)' dump.sql | mysql -h127.0.0.1 -u<user> -p <destino>
# con claves foráneas: SET FOREIGN_KEY_CHECKS=0; al principio del import
```

Regla: **el destino de un import lo decides tú en la línea de `mysql`, nunca el
dump.**

## Permisos de `storage/` (subidas de foto)

`storage/app/public/` y sus subcarpetas las escribe el proceso web (`www-data`),
no tu usuario. Si una subcarpeta (p. ej. `batch-photos/`) es de `tu-usuario:775`,
`www-data` no puede crear dentro y las subidas fallan con:

```
Unable to create a directory at .../storage/app/public/batch-photos/<id>.
```

Arreglo, una vez por entorno:

```bash
php artisan storage:link                       # symlink public/storage (una vez)
sudo chown -R www-data:www-data storage/app/public
sudo chmod -R 775 storage/app/public           # o 777 si el grupo no basta
```

Es el paso que se olvida en el despliegue siguiente y cuesta media hora de
desconcierto. Revísalo cuando el entorno sea nuevo o el usuario del deploy cambie.

El respaldo de las fichas del motor (paso 7) también cae aquí: el admin exporta a
`storage/app/fichas/{slug}.json`, dentro de `storage/`, así que el permiso ya está
cubierto por lo de arriba. NO se escribe en `database/` (código versionado).

## Fichas del motor: la verdad es git, el admin es copia de trabajo

La ficha de un fermento (coeficientes del motor) vive en dos sitios con roles
distintos — separados a propósito para que el admin de producción no ensucie el
árbol de git:

  - **`database/fichas/{slug}.json` — la VERDAD, rastreada en git.** Se mantiene
    DESDE LOCAL: editas, commiteas con el motivo (el «porqué» del coeficiente), y
    el deploy la lleva. El admin nunca la escribe.
  - **`fermentation_guides.ficha` (base) + `storage/app/fichas/` (respaldo).** Es
    la copia de trabajo. El admin de producción ajusta la base y deja el respaldo
    en storage; ese ajuste es real pero NO está versionado hasta que lo llevas a
    `database/fichas/` desde local y lo commiteas.

  - **`php artisan fichas:import` — MANUAL, no en cada deploy.** Vuelca
    `database/fichas/*.json` a la base (el fichero gana). Se corre **solo cuando un
    `git pull` trae una ficha afinada**. Es idempotente; avisa de fichas en base
    sin versionar (un ajuste de admin que falta llevar a git).
  - Nunca editar la columna a mano en producción esperando que persista: el próximo
    `fichas:import` con un fichero nuevo la pisa. Lo que persiste es lo commiteado.

### Cómo sembrar o afinar una ficha (el ciclo que sí cierra)

El admin escribe solo al respaldo, así que un valor metido en el admin **no llega
solo a la verdad**. Para que un ajuste quede en la historia:

1. **Edítalo en el admin LOCAL**, con el Afinador delante (no en producción — ahí
   la base tendría los valores pero el fichero de git seguiría con huecos, y el
   primer `fichas:import` te los borraría).
2. El respaldo aparece en `storage/app/fichas/{slug}.json`.
3. **Cópialo a `database/fichas/{slug}.json` y commitéalo con el MOTIVO** en el
   mensaje (por qué ese coeficiente cambió). Este paso es manual a propósito: es el
   momento en que decides que el ajuste merece quedar versionado.
4. Deploy, y una vez `php artisan fichas:import` para cargarlo en producción.

**Salvaguarda:** si el fichero pondría `null` donde la base tiene un valor (señal
de que alguien ajustó en el admin sin versionar), `fichas:import` **omite esa guía
y avisa** en vez de borrar en silencio. Para sobrescribir a propósito: `--force`.

## Migración inmutable

Una migración ya corrida **no se edita nunca**. Las correcciones van en una
migración nueva. Editarla rompe la reproducibilidad: el entorno que ya la corrió
no la vuelve a aplicar, y los que no, aplican otra cosa.

## `fermenty_test` para la suite

`phpunit.xml` apunta a MySQL `fermenty_test` (aquí no hay `pdo_sqlite`, sí
`pdo_mysql`). Para correr la suite en un entorno nuevo: crear la base vacía; la
suite usa `RefreshDatabase`, así que corre toda la cadena de migraciones sola.

## Orden de despliegue de un release con migración

1. Backup de producción.
2. Correr la cadena de migraciones en una base aparte primero (verifica que
   aplican limpias sobre datos reales, no solo sobre `migrate:fresh`).
3. Desplegar (`deploy.sh`).
4. **Solo si el `git pull` trajo cambios en `database/fichas/`**: `php artisan
   fichas:import` (el fichero gana). No es un paso fijo de cada deploy.
5. Reimportar prod→dev quitando el `USE` (ver arriba).

**Un release cuyo front depende de un cambio de esquema es UN despliegue, no dos:**
código y migración van juntos, o reproduces el bug en producción. (Ej.: el fix del
PWA que lee `flavor_result` no funciona sin la migración `100010` que crea ese
campo.)
