# Fermenty — Matemática del motor

> **Extraído del prototipo `afinador-kombucha_9.jsx`, no derivado.** Cada fórmula de aquí
> está en el código del Afinador; los comentarios interpretativos van marcados.
>
> Sirve para dos cosas: especificar el motor antes de implementarlo en `App\Mentor`, y
> poder evaluarlo a mano contra el backtest sin escribir código.
>
> Destino sugerido: `docs/Matematica_Motor.md` · 2026-07-23

**Ancla de todo:** obrador LIRONA — 70 g/L azúcar · 6 g/L té · 10 % arrancador · 23 °C → **7–8 días**. Vive en `carbTotal.ref` y `diasRef`, y no se toca: es el punto contra el que se contrastaron las seis recetas reales. Ojo, desde 2026‑07‑25 la carta «equilibrada» ya **no** cae encima del ancla — baja a 60 g/L a propósito (§2), porque el rango llegaba a 100 g/L y eso está fuera de todo lo que el obrador ha fermentado nunca.

---

## 1. Las entradas

**Cuatro ejes continuos** (0–1), no dos. Éste es el primer punto donde la documentación previa se quedaba corta:

| Eje | Qué mueve |
|---|---|
| `intensidad` | Cuánto carbohidrato entra (el cuerpo del resultado) |
| `equilibrio` | Dónde se corta la F1 (dulce ↔ ácida) |
| `gas` | Volúmenes de CO₂ objetivo en el cierre |
| `complejidad` | Cuánto arrancador, en sentido inverso |

**Más tres elecciones discretas:** tipo de azúcar, tipo de té, saborizante y su dosis.
**Más el contexto:** temperatura y volumen.

Los presets (*suave, comercial, equilibrada, complejo, ácida, fuerte*) son **puntos en ese espacio de cuatro ejes más las tres elecciones** — no dos coordenadas.

---

## 2. Constantes de la ficha

| Constante | Valor | Qué es |
|---|---|---|
| `carbTotal` | 30 / 75 / **70** ref | Rango de carbohidrato al vaso (g/L). `min`/`max` solo mapean `intensidad` a gramos; `ref` normaliza `fS0` y es el ancla del backtest — por eso no se movió al bajar el rango (2026‑07‑25: era 40 / 100) |
| `teGL` | 4 / 8 / **6** ref | Té (g/L) |
| `arrancador` | 8 / 22 / **10** ref | Porcentaje de arranque |
| `tempViable` | 16 – 30 °C | Fuera de esto el motor no acompaña |
| `tempIdeal` | 20 – 25 °C | Banda de calidad |
| `tempRef` | 23 °C | Donde vale `diasRef` |
| `progreso` | 0,30 – 0,88 | Fracción de carbohidrato consumido |
| `diasF1` | 5 – 30 | **Suelo de identidad**: antes de 5 días no es kombucha |
| `volumen` | 1 – 50 L | Rango de validez del modelo |
| `horasPor100L` | 12 | Penalización de escala |
| `q10` | 2,0 | Duplica la velocidad cada 10 °C |
| `expS0` | 0,45 | Exponente del efecto del azúcar sobre el tiempo |
| `expArr` | 0,30 | Exponente del efecto del arrancador |
| `diasRef` | 8 | Días en condiciones de referencia |
| `factorAcido` | 0,12 | g/L de ácido por g/L de carbohidrato consumido |
| `kGasAcidez` | 0,18 | Cuánto adelanta el corte si vas a gasificar |
| `pHSeguridad` | 4,2 | Por encima, no acidifica lo bastante |
| `pHIdentidad` | 2,8 | Por debajo, es vinagre |
| `gLporVolumen` | 4,0 | g/L de azúcar por volumen de CO₂ |
| `abvPorGL` | 0,06 | % ABV por g/L consumido |
| `presionMax` | 8,0 bar | Umbral de aviso |
| `residualCO2` | 0,5 vol | Ya disueltos al cerrar (la F1 es abierta) |
| `tasaPorCarb` | 0,057 | Velocidad de consumo del residual (sacarosa) |
| `tasaPorCarbSimple` | 1,2 | De los azúcares simples de la fruta |
| `ventanaCierre` | ×0,8 – ×1,2 | Ancho de la ventana del cierre |
| `margenMin` | 1,0 día | Margen exigido entre «perfecta» y «peligrosa» |
| `fraccionEtanolSobrevive` | 0,06 | En F1 abierta las acéticas se comen casi todo |
| `conversionAcetica` | 0,25 | Lo que aún se oxida en botella |
| `burbuja` | 19 / 24 °C | Fronteras fina / media / gruesa |

**Conversiones verificadas** (estándar cervecero/enológico): 1 g/L azúcar → 0,25 vol CO₂ · 1 g/L azúcar → 0,06 % ABV. **Cada volumen de CO₂ trae ~0,24 % de alcohol: van soldados.**

---

## 3. Tablas de ingredientes

**Azúcar** — tres modificadores: cuánto carbohidrato, a qué velocidad se ataca, cuánto aroma aporta.

| | carbPorG | velocidad | aroma |
|---|---|---|---|
| Moreno *(default)* | 0,97 | 0,92 | 0,06 |
| Blanco | 1,00 | 1,00 | 0,00 |
| Panela | 0,90 | 0,82 | 0,14 |
| Miel (Jun) | 0,80 | 1,08 | 0,12 |
| Fructosa | 1,00 | 1,22 | −0,08 |

**Té** — es medio, no combustible: **no aporta carbohidrato, no toca S₀, ni gas, ni presión, ni ABV.** Solo aroma y cuerpo. Blanco 0,02/0,15 · verde 0,05/0,30 · mezcla 0,12/0,60 · negro 0,16/0,90 · rojo 0,15/0,95.

**Saborizante** — carbPorG, dosis calibrada (g/L) y fracción de azúcar simple:
ninguno 0/0/0 · hierbas 0,03/12/0,5 · extracto 0,01/3/0,5 · fresca 0,10/40/1,0 · seca 0,60/4/1,0 · liofilizada 0,70/4/1,0.

> Invariante de control del obrador: `carbPorG × dosis ≈ 3–4 g/L` para todas las formas. 40 g de fresca ≈ 4 g de seca — el 10:1 es agua.

---

## 4. El presupuesto de carbohidrato

**Dos partidas, no una:** lo que acaba en el vaso, y lo que se va en gas.

```
factorEq    = lerp(1,09 → 0,91, equilibrio)        // una dulce pide algo más de partida
carbVaso    = lerp(30 → 75, intensidad) × factorEq
volObj      = lerp(0,5 → 4,0, gas)
carbParaGas = max(0, volObj − 0,5) × 4,0
carbTotal   = carbVaso + carbParaGas

carbFruta       = dosisFruta × sabor.carbPorG
carbSimpleFruta = carbFruta × sabor.simple

gramosAzucar = max(30, carbTotal − carbFruta) / azucar.carbPorG
S0           = gramosAzucar × azucar.carbPorG        // carbohidrato efectivo
te           = lerp(4 → 8, intensidad)
```

**El equilibrio tira de la misma palanca (±9 %)**, y el gas también: por cada 0,25 vol, 1 g/L más de azúcar inicial. Es la estequiometría usada como compensación — el gas se merienda parte del residual, así que hay que reponerlo.

**El arrancador sale de la complejidad, al cuadrado e invertido:**

```
arrancador = 8 + (22 − 8) × (1 − complejidad)²
pHInicial  = clamp(5,6 − 0,06 × arrancador, 3,8 → 5,6)
```

---

## 5. La primera fermentación

**El punto de corte:**

```
progresoBase  = lerp(0,30 → 0,88, equilibrio)
correcciónGas = 0,18 × gas
progresoObj   = clamp(progresoBase − correcciónGas, 0,15 → 0,95)
```

> Si vas a gasificar, se corta antes: el carbónico pondrá parte del punto ácido.

**La cinética** — tres factores multiplicativos sobre los días de referencia:

```
fTemp = 2,0 ^ ((T − 23) / 10)                    // Q10
fS0   = (S0 / 70) ^ 0,45                          // más azúcar, más días
fArr  = (10 / arrancador) ^ 0,30                  // más arranque, menos días

diasCin = 8 × (progresoObj / 0,6) × fS0 × fArr / (fTemp × azucar.velocidad)

si T < 18:   diasCin × (1 + (18 − T) × 0,15)      // castigo extra por frío
diasCin += (volumen / 100) × (12 / 24)            // penalización de escala
```

**El suelo de identidad, y por qué no se parchea:**

```
suelo   = diasCin < 5
diasF1  = max(diasCin, 5)
progreso = suelo ? clamp(progresoObj × (5 / diasCin), 0,15 → 0,95) : progresoObj
```

> Es una **secuencia** (levaduras primero, acéticas después), no una velocidad: no se acelera con nada. Si la cinética honesta llega antes del mínimo, el motor **no infla el azúcar para disimularlo**. Calcula el desenlace real —esperar al suelo empuja el progreso pasado el objetivo, sale más ácida— y lo avisa.

**Ventana devuelta:** `[max(diasF1 × 0,9, 5) , diasF1 × 1,2]` — no puede cruzar el suelo.

---

## 6. El cierre

```
residual   = S0 × (1 − progreso)
carbCierre = residual + carbFruta

fTempC = 2,0 ^ ((tempCierre − 23) / 10)
tasa   = (carbCierre × 0,057 + carbSimpleFruta × 1,2) × fTempC × azucar.velocidad

carbNecesario = carbParaGas
alcanzable    = carbNecesario ≤ carbCierre
diasCierre    = alcanzable ? carbNecesario / tasa : ∞
```

> **La fruta trae azúcares simples: se atacan al instante** (tasa 1,2 frente a 0,057). Por eso acelera tanto el cierre.

**Ventana devuelta:** `diasCierre × [0,8 , 1,2]`.

---

## 7. Presión — sí se modela, con la ley de Henry

```
solubilidad(T) = 1,7 × e^(−0,0329 T)      // vol/bar absoluto
barDesdeVol(v,T) = v / solubilidad(T) − 1
volDesdeBar(b,T) = (b + 1) × solubilidad(T)
```

> Ajustado a tablas cerveceras: 0,88 vol/bar a 20 °C, 0,76 a 25 °C. **Consecuencia: el mismo gas es más presión cuando hace calor.**

**La ventana de reacción** — lo que importa no es el potencial, es el margen:

```
volPeligro  = volDesdeBar(8,0 , tempCierre)
carbPeligro = min(max(0, volPeligro − 0,5) × 4,0 , carbCierre)
diasPeligro = carbPeligro / tasa
margen      = diasPeligro − diasCierre          // exigido ≥ 1 día

volPotencial   = 0,5 + carbCierre / 4,0
presiónObjetivo = barDesdeVol(volObjetivo, tempCierre)
presiónOlvido   = barDesdeVol(volPotencial, tempCierre)
```

---

## 8. Alcohol

```
abvF1    = S0 × progreso × 0,06 × 0,06        // las acéticas se comen ~94 %
abvF2    = (alcanzable ? carbNecesario : carbCierre) × 0,06 × (1 − 0,25)
abvTotal = abvF1 + abvF2
```

Umbrales legales: aviso a 0,5 %, límite a 1,2 % (UE 1169/2011).

> **Presión y alcohol suben juntos**, porque salen del mismo azúcar.

---

## 9. Química

```
ácido = S0 × progreso × 0,12
pH    = clamp(3,9 − 0,55 × ln(ácido / 2,5) , 2,4 → 4,9)

acidezPercibida = clamp((ácido/10) × 0,8 + (volObjetivo/4) × 0,25 , 0 → 1)
```

**El pH sale de una fórmula logarítmica, no de una tabla de puntos.**

---

## 10. Derivados

```
banda = 1 dentro de 20–25 °C, cae linealmente hasta 13 °C por abajo y 32 °C por arriba

fCompS0  = 0,85 + 0,30 × (S0/70 − 1)
fCompArr = 1 − ((arrancador − 10)/100) × 0,8
compDosis = (dosisFruta / 140) × 0,12
complejidad = clamp(diasF1/12 × banda × fCompS0 × fCompArr + compDosis
                    + azucar.aroma + te.aroma , 0 → 1)

horasCata = clamp(24 / fTemp , 8 → 48)
burbuja   = tempCierre < 19 ? fina : tempCierre > 24 ? gruesa : media
```

---

## 11. 🔴 Divergencias con lo que se construyó en el paso 7

El prototipo es un modelo bastante más completo que la ficha sembrada. Hay que reconciliarlos antes de implementar `App\Mentor`, o serán dos modelos divergiendo en silencio.

| Tema | Paso 7 | Afinador |
|---|---|---|
| **Respuesta a la temperatura** | Lineal, 0,05/0,06 por grado con topes 0,6–1,6 | **Q10 = 2,0**, exponencial. Modelos distintos, no calibraciones distintas |
| **Ejes del perfil** | Dos (azúcar, corte) | **Cuatro** + tres elecciones discretas |
| **Presión** | Se decidió *no* modelar | **Se modela**, con Henry y umbral de 8 bar |
| **pH** | Tabla de puntos interpolados | **Fórmula logarítmica** |
| **Barandilla** | `carbohidrato_max_al_cerrar_g_l` | **Margen temporal** (≥1 día entre perfecta y peligrosa) |
| **Escala/volumen** | Se descartó | **Existe**: `horasPor100L` y aviso a partir de 40 L |
| **Perfil de ingrediente** | carb + acidez | **carb + velocidad + aroma** (azúcar), aroma + cuerpo (té), carb + dosis + simple (sabor) |
| **Suelo de identidad** | No existía | **5 días mínimos**, no negociables |
| **ABV** | Fuera (era `AbvCompliance`) | **Se calcula**, y está soldado a la presión |

**La divergencia de fondo:** el paso 7 sembró un modelo simplificado derivado de conversaciones; el Afinador es el modelo real y está más avanzado. **Manda el Afinador.**

Lo que hay que decidir antes de implementar:

1. **Reescribir la estructura de la ficha** para que sea la del Afinador. La del paso 7 no la puede contener.
2. **La barandilla es temporal, no de cantidad.** Es más inteligente de lo que propusimos: no limita cuánto azúcar, exige que haya al menos un día entre el punto perfecto y el peligroso. Eso protege sin prohibir.
3. **Presión y ABV sí se modelan**, con la advertencia honesta de que el umbral de 8 bar depende de la botella.
4. **Los coeficientes marcados «a dedo, pendientes de backtest»** en el prototipo —tablas de azúcar, té y saborizante— son los primeros candidatos a que el backtest mueva.

---

*Documento vivo. Cuando el Afinador cambie, se corrige aquí antes que en el código.*
