# Afinador público de kéfir de agua — documento vivo

> Cómo está construida `/herramientas/afinador-kefir-agua`, qué decisiones la
> sostienen y dónde están las costuras. Nace del briefing del 2026-08-07 (más su
> cierre SEO), ejecutado el 2026-08-08 tras la Fase 0.
>
> Referencias: `Afinador_Web.md` (los principios), `Prosa_Afinador_Kefir_Web.md`
> y `Prosa_Estante_Herramientas.md` (copy cerrada), `SEO_Cierre_Afinador_Kefir.md`,
> `Kefir_Ficha_v0.3.md` (la ficha, ahora con las anclas de pH escritas).
>
> **Documento vivo** · v1 · 2026-08-08

---

## 1. Lo que la Fase 0 cambió del briefing

La investigación previa movió cuatro supuestos, y las decisiones las tomó el
propietario el 2026-08-08:

| Supuesto del briefing | Lo encontrado | Decisión |
|---|---|---|
| `POST /afinador/formular` no existía | Existía `/api/afinador/kombucha` + todo el peldaño de conversión (token, puente, correo) | **Endpoint unificado nuevo + la ruta vieja de alias** |
| El motor estaba inerte | Encendido en la Parte 2A (árbol sin commitear entonces) | El propietario lo commiteó antes de empezar |
| El pH vivía solo en código | Las anclas viven en la ficha (`ph.anclas`); faltaba solo el doc | Doc actualizado; la fórmula del prototipo muere |
| Los perfiles eran grado de fermentación | Son horas fijas en la ficha | **Resolutor en el afinador** (ver §3) |

Y dos cierres que ya no son abiertos: los volúmenes de CO₂ salen del motor con
respaldo (estequiometría + Henry), y `dulzor.equilibrado_hasta = 50` /
`seco = 58 g/L @ 72 h` son los del motor.

## 2. Las piezas

```
routes/publico.php            POST /api/afinador/formular  (fermento en el cuerpo)
                              POST /api/afinador/kombucha  (alias para el JS desplegado)
AfinadorController            formular() → escalera compartida → calcularKombucha|calcularKefir
                              paginaKefir() → SSR de la propuesta de fábrica
App\Mentor\AfinadorKefirPublico   el recorte público del kéfir (este doc, §3-§4)
App\Mentor\AfinadorPublico        el de kombucha, intacto
resources/views/herramientas/afinador-kefir.blade.php   la página (SSR)
public/landing/pages/afinador-kefir.{css,js}            estilos y widget
lang/{es,en,de}/pagina_afinador_kefir.php               copy web (en/de: solo lo que rinde el endpoint)
tests/Feature/AfinadorKefirWebTest.php                  los diez del §10 del briefing
```

Un solo endpoint para todos los fermentos: la escalera, la clasificación de
avisos y el recorte de trueques pasan por un sitio. La entrada y la salida SÍ
son por fermento — kombucha pregunta preset+override y responde en días;
el kéfir pregunta perfil+gas+ajustes y responde en horas. Unificar la forma
habría sido fingir que comparten matemática, que es justo lo que el estante
explica que no pasa.

## 3. El resolutor — la decisión central

Los perfiles de la ficha son horas fijas (dulce 80 g/L @ 40 h · equilibrado
60 @ 48 h · seco 58 @ 72 h). Con eso, mover la temperatura no movería el
calendario — y esa interacción es la única cosa que un cuaderno no hace.

El afinador lo resuelve **sin tocar ficha ni modelo**:

1. El consumo objetivo del perfil = lo que SUS horas consumen ejecutando el
   motor sobre la **receta canónica a 25 °C** (donde se anclaron los perfiles).
   No hay números nuevos: el objetivo sale del propio motor.
2. La hora de corte del visitante = bisección sobre el motor (el motor es el
   oráculo) hasta alcanzar ese consumo en SUS condiciones (temperatura,
   gránulos, estado).

Consecuencias que son features: la propuesta de fábrica reproduce exactamente
las horas del perfil (paridad con lo que formula la app); el pH al corte se
conserva entre temperaturas (mismo consumo ⇒ mismo pH — la semántica del grado
de fermentación); y si mañana cambia la cinética del modelo, el resolutor sigue
siendo verdad sin tocarlo.

Si algún día los perfiles se redefinen como `consumido_obj` EN el motor
(decisión de modelo, con panel, cartas y backtest detrás), este resolutor se
borra y nadie lo echa de menos: es una capa, no una matemática.

## 4. El contrato del endpoint

Entrada (`fermento: kefir_de_agua`): `perfil` · `temp` · `volumen` · `gas`
(poco/normal/mucho, eje separado) · `ajustes{granulos, fruta, estado, corte_h}`
— cada campo opcional; ausente = el perfil manda; rangos validados en servidor.

Salida — solo lo presentable, nada que el cliente formatee o filtre:

- `cantidades` (nombre resuelto, pesable), `calendario` en **horas relativas**
  con notas ya rendidas, `curva` (reserva/h, paso 1 h), `tramo_decide` y
  `suelo_gas` (lectura de pantalla, no coeficientes), `trueques` (≤2, orden
  del motor), `avisos_duros` (≤2) y `avisos_blandos`, `numeros` (cadenas
  es-ES ya redondeadas), `corte_h`, `botella_h`, `ajustes` (dónde quedaron los
  mandos).
- La prioridad de trueques vive en el MODELO: `ModeloKefirDeAgua::trueques()`
  los emite de más a menos importante y las superficies cortan. Los textos del
  prototipo entraron al motor como códigos (`kefir_tramo_decide`,
  `kefir_seco_y_mucho_gas`, `kefir_dulce_al_borde`, `kefir_frio_paciencia`, y
  los blandos `kefir_sin_calcio` y `kefir_encadenado_contado`), con voz en
  es/en/de. En la plantilla no hay ni un texto de mentor — hay un test que
  lo vigila.

## 5. Renderizado, límite y SEO

- **SSR**: la página se sirve con la propuesta de fábrica (25 °C, 2 L,
  equilibrado, normal) calculada dentro; el widget arranca del JSON servido y
  no gasta endpoint hasta que alguien toca un mando. Sin JavaScript hay página.
- **Fechas**: el HTML servido habla en horas relativas («a las 48 h»); el JS
  convierte a fecha local al cargar. Cada rastreo ve el mismo HTML.
- **Limitador**: la escalera de sesión ES COMPARTIDA entre afinadores (gracia
  180 s + 15 cálculos), más `throttle:120,60` por IP en la ruta. Al saltar:
  mensaje en la voz del proyecto y la última propuesta sigue en pantalla.
- **SEO**: canonical propia, BreadcrumbList + SoftwareApplication (sin FAQPage
  nueva: cautela del cierre SEO §6), sitemap **enumerado del router** para
  /herramientas/* (la lista a mano se rompía en silencio), `noindex` +
  `Disallow` para el puente `/afinador/p/` y `/api/`, menú «Afinadores» →
  estante con marca de nuevo que caduca por config
  (`web.novedades.afinadores_hasta`), triángulo de enlaces (guía ↔ afinador ↔
  estante ↔ kombucha) con textos variados.

## 6. Pendientes con dueño

- **La receta canónica declara 500 ml de agua para 2 L** (`guia-clasica-de-
  kefir-de-agua`): las cantidades escalan fiel a eso y en pantalla se ve raro.
  Es dato de receta (admin), no de código. → propietario.
- **Migrar el JS de kombucha** al endpoint unificado y quitar el alias.
  Commit propio, sin prisa.
- **Backport del SSR a la página de kombucha** (hoy no sirve propuesta de
  fábrica) — anotado en el cierre SEO §9.
- **La página en EN/DE**: los caminos están en `config/idiomas.php`; falta la
  prosa traducida y entrar en `traducidas`. El endpoint ya habla en/de.
- **Fuentes autoalojadas**: `tokens/fonts.css` sigue en Google Fonts para TODO
  el sitio («swap to self-hosted before shipping»). Esta página no añadió
  familias, pero la deuda es del sitio entero.
- **Consejos de nivel fermento**: cero filas para todos los fermentos. El hueco
  está en la maqueta (`#akConsejos`), la decisión §7a/§7b sigue abierta.
- **La tensión fTemp/inflexión**: anotada como TODO en la ficha; cambio de
  modelo, no de interfaz.

---

*Documento vivo. Si el prototipo y el motor discrepan, gana el motor. Si la
ficha y el código discrepan, se corrige la ficha primero y el código después.*
