# Fermenty — Gestión de consejos en el admin

> Briefing para Claude Code. Una pantalla de administración para dar de alta y editar
> los consejos. **Empieza por un reconocimiento corto**: la tabla existe y está
> clasificada, pero sus columnas y sus valores son justo lo que la pantalla tiene que
> exponer, y no se pueden suponer.
>
> Referencias: `docs/Lexico.md` (§2 Consejo · Aviso · §6 Definición vs. hecho · Un solo
> dueño), `docs/Planes.md` (§2 Las tres capas del acompañamiento, §10),
> `docs/Encaminamiento.md` (§4.3).
>
> Documento vivo · v1 · 2026-08-03 · **Informe de Fase 0 en §10, con los nombres reales.**

---

## 1. El encargo

**Un menú en el admin para dar de alta, editar y gestionar los consejos**, con su
clasificación por posición/nivel ya existente como eje de navegación.

Es contenido editorial: prosa del *por qué se hace así*. Se escribe a mano, se corrige
a menudo, y es lo que `Encaminamiento.md` §4.3 manda publicar por SEO.

***No es*** una herramienta de textos en general. Hay cuatro cosas que se parecen y
solo una entra (§3).

***No es*** un editor de comportamiento. Ninguna condición del motor se toca desde un
formulario.

***No es*** ocasión de arreglar el reparto. Si aparece un candado de plan en la tabla,
se respeta tal cual y se anota (§5).

---

## 2. Fase 0 — Reconocimiento

**Entregable: un informe corto. Cero código.** Seis preguntas:

**a) La tabla.** Nombre real, columnas, tipos, índices, restricciones. Migración que
la crea y las que la han tocado después.

**b) La clasificación.** Qué columna sostiene la posición/nivel, qué valores admite
—lista literal, no descripción— y si es enum de base, constante de PHP o texto libre.
De esa lista sale la navegación entera de la pantalla, así que hay que traerla exacta.

**c) A qué se ata cada consejo.** ¿Cuelga de un paso, de una guía, de una receta, de
varios? ¿Puede existir uno sin dueño? ¿Hay orden entre los de un mismo sitio?

**d) Quién los lee.** Todos los sitios que renderizan consejos: web pública, PWA,
Panel, correos. Editar un texto que sale en cinco pantallas sin saber cuáles son es
publicar a ciegas.

**e) Idiomas.** ¿Hay columnas por idioma, tabla de traducciones, o un solo texto? Y si
hay tres idiomas en producción, ¿están los tres poblados o solo el español?

**f) Dos cabos sueltos que pueden ser la misma tabla o no:**
- El **«consejo del día»** del panel de inicio: ¿sale de aquí o de otro sitio?
- Las **pistas de pago** de la ficha de tanda: ¿son filas de esta tabla con un
  interruptor, o algo aparte?

**Y de paso:** ¿qué patrón de CRUD sigue ya el admin? La pantalla nueva se parece a
las que hay, no inventa uno.

> **No debe inventarse.** Lo que no se encuentre se declara ausente. Un nombre de
> columna supuesto convierte el briefing siguiente en ficción.

---

## 3. Lo que la herramienta no debe poder tocar

Cuatro cosas se parecen y el Léxico las separa a propósito:

| Qué | Es | ¿Entra? |
|---|---|---|
| **Consejo** | Definición, atada a un sitio, estática | **Sí** |
| **Aviso del motor** | Atado a una condición | Solo si el texto vive en esta tabla (§2f) |
| **Trueque** | Atado a una condición. No existe todavía | No |
| **Insight** | Hecho, de una persona y un momento | **Nunca** |

**El insight es la línea que no se cruza.** Pertenece a una tanda concreta y está
congelado. Un administrador editándolo estaría corrigiendo un hecho desde una
definición, que es precisamente lo que el modelo de datos existe para impedir. Y sería
el tercer dueño de la voz del mentor, después del trabajo que costó dejarla en uno.

**Si un aviso resulta editable, se edita el mensaje, nunca la condición.** Cambiar
lógica desde una caja de texto es desplegar comportamiento sin test que lo respalde.

---

## 4. Qué necesita la pantalla

Sale del informe, pero el esqueleto es previsible:

- **Listado navegable por la clasificación**, que es el eje natural: se busca «los
  consejos de este paso», no «el consejo número 412».
- **Filtro por fermento/guía**, porque el volumen crece por ahí.
- **Alta y edición**, con el dueño (a qué se ata) como campo obligatorio y validado
  contra lo que exista — no texto libre.
- **Orden**, si el informe confirma que lo hay.
- **Borrado con consecuencia visible**: cuántas pantallas dejan de mostrarlo.
- **Dónde sale esto**, en la propia ficha de edición. Con (d) resuelto es barato, y
  evita el error de escribir para la web algo que también aparece en un correo.

**Un aviso de dimensión:** si son cientos de filas, el listado sin buscador es
inservible desde el primer día. Si son decenas, un buscador es adorno. El informe da
el número.

---

## 5. El candado de plan, si aparece

`Planes.md` §2 sitúa el consejo en gratis, público e indexable: es contenido editorial
y el SEO entero. Tras el muro sería pagar por escribirlo y luego esconderlo.

Si la tabla tiene un interruptor de plan, **la pantalla lo muestra y lo respeta tal
como está**. No lo retira, no lo esconde, no lo pone por defecto en nada.

Pero **no ofrece el interruptor en el alta**: crear consejos de pago desde la
herramienta institucionaliza una contradicción que `Planes.md` §10 ya tiene apuntada
para resolverse en el briefing de reparto — y §8 dice hacia qué lado se resuelve.

---

## 6. Idiomas

Si hay tres idiomas y la tabla solo sostiene uno, **eso se decide antes de construir
la pantalla, no después**. Añadir columnas de idioma a una tabla con contenido dentro
es la migración cara, y el contenido editorial solo crece.

Si ya los sostiene, la pantalla tiene que dejar ver **qué está traducido y qué no**.
Un consejo con el alemán vacío no es un consejo terminado, y sin esa señal nadie se
entera hasta que un alemán ve español.

---

## 7. 🔴 Decisiones que salen del informe

**a) Si los avisos del motor entran.** Hoy su texto vive en ficheros de idioma, que se
despliegan; los consejos viven en base y se editan en caliente. Son dos flujos de
cambio distintos. Moverlos a base de datos es una decisión seria y **no se hace de
paso**: o entran con su propia discusión, o la herramienta es solo de consejos y lo
dice en pantalla.

**b) El «consejo del día».** Si es una quinta fuente de texto, hay que saberlo antes de
construir la cuarta.

**c) Herencia de consejos.** Si algún día un consejo puede colgar de guía, receta o
paso y gana el más específico, eso es herencia con sobreescritura aplicada al texto y
encaja con el mecanismo que ya existe. **No entra ahora**: hoy la clasificación es la
que es y la pantalla la refleja.

---

## 8. Fuera de alcance

- Insights y cualquier voz del mentor.
- Condiciones del motor.
- Trueques, que no existen.
- Mover los avisos de ficheros de idioma a base de datos.
- Retirar o cambiar candados de plan.
- Herencia de consejos por nivel de la cadena.

---

## 9. No debe inventarse

- Ni nombres de columna, ni valores de la clasificación: salen del informe.
- Ni consejos de ejemplo sembrados en migración.
- Ni traducciones automáticas de lo que esté vacío.
- Ni un criterio nuevo de cuándo un consejo es de pago.

---

## 10. Informe de reconocimiento (Fase 0) · 2026-08-03

Sobre `main` (db9bccd) y la BD de trabajo `fermenty_dev`, contada con `COUNT(*)`.

### a) La tabla: `hints`

Creada por `database/migrations/2026_08_02_100000_unificar_consejos_en_hints.php`
(ayer), que absorbió `guide_step_hints` (2026-04-16) y los consejos diarios de
`lang/{es,en,de}/tips.php`, y borró ambos orígenes. Ninguna migración la ha tocado
después. Modelo: `App\Models\Hint`.

| Columna | Tipo | Notas |
|---|---|---|
| `id` | bigint PK | |
| `level` | **enum BD** `general` · `category` · `ferment` · `step` | la clasificación (b) |
| `fermentation_category_id` | FK nullable → `fermentation_categories`, cascade | solo si `level=category` |
| `guide_id` | FK nullable → `fermentation_guides`, cascade | solo si `level=ferment` |
| `guide_step_id` | FK nullable → `guide_steps`, cascade | solo si `level=step` |
| `locale` | string(5), default `'es'` | idioma de la fila |
| `title` | string(120) nullable | |
| `content` | text | el admin actual valida max 500 |
| `symptom` | enum BD nullable: `flojo`, `demasiado_dulce`, `demasiado_acido`, `sin_gas`, `sobrepresion`, `moho` | **inerte: nadie la escribe ni la lee** |
| `is_daily` | boolean default false | marca «apto para portada» (solo nivel general) |
| `order` | unsigned int default 1 | orden dentro del mismo sitio |
| timestamps | | |

Índices: `(guide_step_id, order)` y `(level, locale, is_daily)`.

**Regla de integridad** (evento `saving` en `Hint`, no en BD): exactamente la FK del
nivel rellena y las otras dos nulas; `general` sin ninguna. Las constantes viven en
PHP también: `Hint::NIVELES`, `Hint::SINTOMAS`.

**Dimensión: 93 filas.** 90 son el consejo del día (30 × es/en/de, `level=general`,
`is_daily=1`) y **3** son de paso (`level=step`, solo español). `category` y `ferment`
existen en el esquema y en las relaciones (`FermentationCategory::hints()`,
`FermentationGuide::hints()`) pero tienen **cero filas y cero renderizadores**: nadie
los pinta todavía. Son decenas: el buscador es adorno (§4).

### b) La clasificación

`level`, enum de base de datos **y** constante `Hint::NIVELES`, valores literales:
`general`, `category`, `ferment`, `step`. No hay texto libre. La navegación de la
pantalla sale de esos cuatro.

### c) A qué se ata

Un consejo cuelga de **exactamente un** dueño según su nivel (o de ninguno si es
`general`); nunca de varios. Borrar el dueño borra sus consejos (cascade), y los
hints de paso **no** se congelan en el snapshot de tanda — decisión explícita en el
docblock de `Hint`: si se borra el paso, las tandas viejas pierden esa prosa. Hay
orden (`order`) entre los de un mismo sitio, y se usa.

### d) Quién los lee

> **Nota 2026-08-03 (briefing v2):** la fila de la web pública ya no existe — el
> renderizador se retiró al ejecutar `Consejos_Admin_Pantalla.md` §3: en público
> los consejos de paso no existen. El resto de la tabla sigue vigente, más la
> pantalla nueva `/acp/consejos`.

| Sitio | Dónde | Qué | Candado |
|---|---|---|---|
| ~~Web pública~~ | ~~`Web\FermentosController::show`~~ | retirado 2026-08-03 — v2 §3 | — |
| Panel, ficha de tanda | `BatchController::show:330` → `batches/show.blade.php:404` | hints del paso **actual**, solo español | `$canSeePremiumHints = user->isPremium()` |
| Panel, inicio | `DashboardController:45` | consejo del día | ninguno |
| PWA, inicio | `mobile/inicio.blade.php:10` (server-render) | consejo del día | ninguno |
| Admin | `admin/guia_pasos/index+form`, `admin/fermentos/show` | hints de paso en español, lectura y edición en línea | — |

**Ni correos, ni API móvil**: cero apariciones en `app/Mail`, `app/Notifications`,
`routes/api.php`, `app/Http/Resources`. La PWA no muestra hints de paso en el lote
(las vistas `mobile/lote-*` cargan por API y la API no los sirve).

### e) Idiomas

La tabla **ya sostiene idiomas**: filas hermanas con `locale`, no columnas por idioma
ni tabla aparte. El español es la fuente que edita el admin (el CRUD actual filtra
`enIdioma(Idiomas::defecto())`); la caída a español cuando falta traducción está
centralizada en `Hint::enLocale()` y `Hint::consejoDelDia()`. Poblado real: los 30
diarios están en los tres idiomas; los 3 de paso, **solo en español**. No hace falta
migración de idiomas (§6, primera mitad): lo que hace falta es la señal de «qué está
traducido» (§6, segunda mitad).

### f) Los dos cabos sueltos

- **Consejo del día: es esta tabla.** `Hint::consejoDelDia()` — `level=general` +
  `is_daily=1`, rota por día natural (`dayOfYear % count`), misma regla en web y PWA.
  No hay quinta fuente.
- **Pistas de pago: son filas de esta tabla, sin interruptor.** El candado **no es una
  columna**: es código en `BatchController::show:324` (`isPremium()`), y solo en la
  ficha de tanda del Panel. ~~Las mismas filas salen gratis y públicas en la web~~
  *(cierto a fecha del informe; el briefing v2 resolvió la asimetría al otro lado:
  el renderizador público se retiró el 2026-08-03 y los consejos de paso son Pro —
  `Consejos_Admin_Pantalla.md` §3, `Planes.md` §2).*

### Los avisos del motor (§7a)

**No viven en esta tabla.** La propia migración lo deja escrito: `notification_rules`
(condiciones) e `insights` (salida por tanda) son sistemas distintos y no se tocaron;
los textos del motor se renderizan vía `App\Mentor\Voz` con códigos en ficheros de
idioma. Tres sistemas, tres tablas — la herramienta es **solo de consejos** y puede
decirlo en pantalla sin perder nada.

### El patrón de CRUD del admin

`routes/admin.php`: prefijo `/acp`, puerta propia, `Route::resource` por entidad y
recursos anidados para lo que cuelga de un fermento (`fermentos.pasos`,
`fermentos.ingredientes`, `fermentos.recetas`). Controladores en
`App\Http\Controllers\Admin\*`, vistas Blade en `resources/views/admin/<entidad>/`
(index/form) con las clases `fermenty-*` del layout `admin/layouts/admin.blade.php`.

Los hints **ya se editan hoy**, pero en línea dentro del formulario del paso
(`GuideStepsController::store/update`, filas dinámicas `hints[i][...]` con id oculto y
sincronización borra-lo-quitado). Solo alcanza el nivel `step` y solo español. La
pantalla nueva sería el primer sitio donde los niveles `general`, `category` y
`ferment` —y los idiomas— se pueden gestionar.

### Decisiones que este informe deja servidas (§7)

- **§7a**: los avisos no entran; herramienta solo de consejos, dicho en pantalla.
- **§7b**: el consejo del día no es quinta fuente; en la pantalla es «nivel general
  con `is_daily`», con su rotación explicada en la ficha.
- **§5**: no hay candado que mostrar en la tabla; nada que retirar ni respetar más
  allá de no inventar uno.
- **Nueva, que el briefing no preveía**: `symptom` existe con seis valores y ningún
  lector. La pantalla puede mostrarlo tal cual o dejarlo fuera hasta que algo lo lea;
  lo que no debe es estrenarlo como criterio nuevo (§9).
- **Otra**: los niveles `category` y `ferment` están vacíos y sin renderizador. Darlos
  de alta desde la pantalla crearía contenido que hoy no sale en ningún sitio — hay
  que decidir si la pantalla los ofrece ya (y la ficha avisa «esto aún no se muestra»)
  o los deja grises hasta que exista quien los pinte.

---

*Documento vivo. Con el informe en §10, el siguiente paso es el briefing de la
pantalla con estos nombres.*
