# Revisión de uso — agosto 2026 (bitácora, seguimiento, pistas, cata, horas)

Cinco correcturas de Manu tras usar la app con un kéfir en marcha (29-08-2026).
Todas aplicadas en las tres superficies donde tocaba (ERP web, PWA, API v1).
Relacionado: `UX_Revision_2026-07.md` (la ronda anterior), `Cierre_del_lazo.md`
(bucle 1: la cata), `Lexico.md`.

## 1 · La receta base, a un vistazo («Receta e ingredientes» en la bitácora)

**Problema.** En una tanda completada no había forma de saber cuál fue la receta
base sin volver al catálogo.

- `BatchResource` expone `recipe` (id, name, slug, description, is_official,
  base_yield) — `show`, `cata` y `fresh()` de la API lo cargan. Los ingredientes
  congelados ya viajaban (`ingredients`); ahora llevan `step_title` para agrupar.
- **PWA** `lote-bitacora`: tarjeta plegable «Receta e ingredientes» (receta base,
  enlace a su ficha, inicio, cantidad, ingredientes generales y por paso). El
  resumen (`lote-resumen`) y el asistente enseñan el nombre de la receta en la
  cabecera (chip que lleva a la bitácora).
- **ERP** `batches/logs/index`: el mismo bloque como `<details>` (sin JS);
  `batches/show`: fact «Receta» en la cabecera (enlaza a la bitácora) y fila
  «Receta base» en «Datos del lote» de la crónica.

## 2 · Seguimiento (PWA): fecha de inicio y cantidad preparada

**Problema.** El paso 1 enseñaba los ingredientes totales pero no decía si eran
para 3 L o para 10, ni cuándo había empezado la tanda.

- `BatchResource`: `started_at` (el instante real, `fermentationStartedAt()`,
  UTC), `es_rapido` (ver §5) y `target_yield` (ya existía).
- Asistente: fila de chips bajo el estado — **Inicio** (con hora si es un
  fermento rápido), **cantidad** y **receta**. El bloque «Ingredientes» y los
  ingredientes de cada paso llevan «para 3 L». Cada paso completado dice «Hecho
  28 ago, 08:15».
- ERP `batches/show`: facts «Cantidad» y «Receta» en la cabecera; «Inicio» con
  hora cuando el ancla la tiene.

## 3 · Pistas de una en una

**Problema.** Todos los consejos del paso salían en cascada.

- **PWA** (`asistente`): `pistasDe(s)` elige **como mucho tres por paso, al azar
  si hay más** (una sola vez por paso, no cambian al refrescar `b`); se ve una y
  se pasa con **swipe** (`touchstart/touchend`, umbral 40 px) o con flechas;
  puntos + «1/3».
- **ERP** (`batches/show`): mismo criterio en servidor (`shuffle()->take(3)`),
  carrusel con `data-pistas` (flechas, puntos, swipe) en el JS de la vista.

## 4 · «Avísame para catar» — el despertador tras la cata

**Problema.** Tras catar («mañana estará bien», «en 12 h en un kéfir») no había
forma de pedir un aviso a esa hora.

- `NotificationService::programarRecordatorio(Batch, Carbon $cuando, ?nota)`:
  una `FermentyNotification` normal (`reminder`, canal `both`) con título
  `TITULO_RECORDATORIO` («Te pediste volver a catar»). **Uno por lote**: pedir
  otro borra el pendiente (aún no sonado); los ya sonados quedan como historia.
  `recordatorioPendiente()`, `cancelarRecordatorio()`.
- Reglas: solo tandas **activas**; la hora debe ser futura; **salta las horas
  de silencio** (22–8) del envío de correo — la hora la eligió el usuario;
  `marcarAtendidos('cata')` lo marca leído solo si **ya sonó** (catar ahora no
  anula el aviso de esta noche).
- API: `POST /v1/batches/{batch}/recordatorio` `{en_horas | at, nota?}` (`at`
  ISO con zona o local del usuario) → 201 `{data:{id, scheduled_at}}`;
  `DELETE /v1/batches/{batch}/recordatorio/{id}`. `BatchResource.recordatorio`
  (solo en la ficha completa, con `dailyLogs` cargado — en listados sería N+1).
- PWA (`_quicklog`): tarjeta «Avísame para catar» con atajos según el ritmo
  (rápido: 4/8/12 h/mañana; lento: 12 h/mañana/2 días) + `datetime-local`;
  **se abre sola tras una cata** sin aviso puesto; muestra el programado y
  permite quitarlo. `fmApi.del()` nuevo en el layout.
- ERP (`batches/show`, «Añadir log de hoy»): fila `.fmty-aviso` con los mismos
  atajos (forms POST) + hora concreta; `POST /panel/batches/{batch}/recordatorio`,
  `DELETE …/recordatorio/{id}` (`BatchLogController`).
- ⚠ **Entrega.** El aviso llega a la campana de la app/Plan y al correo (cada
  15 min, `ProcessPendingNotificationsJob`). **No hay push**: el móvil no vibra
  solo — sigue en la lista de huecos (`mobile-app-sim-state`). Cuando exista
  FCM/Web Push, este recordatorio es el primer candidato.

## 5 · Horas en la bitácora y hora exacta de arranque

**Problema.** En un kéfir de 24–48 h, «día 2» no dice nada: importa si se montó
a las 8:00 o a las 21:00, y a qué hora se hizo cada cosa.

### Arranque exacto
- `BatchService::arrancar(Batch, ?Carbon $startDate, ?Carbon $startAt)` y
  `create()` aceptan **`start_at`** (instante UTC). `start_date` = su día; el
  evento `start` se escribe **a esa hora** con `metadata.exacto = true`.
- `Batch::fermentationStartedAt()` toma el evento `start` como ancla si es
  **exacto** (aunque en UTC caiga en otro día que `start_date`) o si cae el
  mismo día (regla anterior). Cache y fallbacks sin cambios.
- Entrada: API `store`/`arrancar` (`start_at` ISO; en el futuro → 422), web
  `BatchController@store`, `FormularController` (`validar`, `arrancar`) — la
  hora se interpreta en la zona del usuario (`User::desdeSuHora`) y, si está
  en el **futuro**, se convierte en `start_date` (programar para ese día).
- UI: «Cuándo empiezas» pasa de «Hoy / Otro día» a **«Ahora / Otro momento»**
  con `datetime-local` (PWA `formular`, `batch-create`; ERP `create` legacy y
  panel de formulación; borrador en `batches/show`).

### Horas en la bitácora
- `DailyLog::anotar(nota, user)`: **única puerta** de escritura de notas; cada
  apunte entra como línea `HH:MM · texto` en la hora del usuario (las notas son
  una sola columna de texto). Quick-log web y API y el log completo pasan por
  ahí. `white-space: pre-line` en todas las superficies.
- Medidas y catas: `created_at` es la hora (`measurements[].at` en la API); se
  pinta delante de cada chip (PWA `_bitacora`, ERP `logs/index`), en el hilo de
  catas de la crónica y en el historial de eventos del panel.
- Pasos: `completed_at` con hora en el asistente; eventos del resumen PWA con
  hora.
- `Batch::esRapido()`: motor en horas, pasos con `min_horas` o guía ≤ 3 días.
  Decide dónde se enseña la hora del inicio (en una kombucha de 10 días es
  ruido) y los atajos del despertador.

### Helpers de zona horaria (`User`)
`tz()`, `aSuHora(instante)` (UTC → local, para mostrar), `desdeSuHora(string)`
(`datetime-local` o ISO → UTC). La app corre en UTC; la zona la captura
`CaptureTimezone`.

## Tests
`tests/Feature/MobileApiV1Test.php`: `test_start_at_anchors_the_batch_to_the_exact_minute`,
`test_reminder_can_be_set_replaced_and_cancelled`,
`test_quicklog_note_carries_its_time_and_recipe_travels`.

## Pendiente
- Push real (FCM/Web Push) para que el despertador vibre en el móvil.
- El editor del log completo (ERP `logs/show`) reescribe las notas en bloque:
  las horas se conservan como texto, pero un apunte nuevo desde ahí entra sin
  hora salvo por `store()`.

## Añadidos el mismo día

- **§4 · «Avísame para catar» es SOLO PREMIUM** (decisión de Manu): el plan gratuito no lo
  ve (PWA `user.is_premium`, ERP `$isPremium`) y el servidor responde `upgrade_required`
  (403 API / redirección a `/premium` web). Figura en la lista de ventajas Pro de la PWA.
- **Correo:** cabecera con el lockup real y previsualizaciones completas — `MAIL_SERVER_SETUP.md` §7.
- **Sesión compartida PWA ↔ panel** — `Dominios_Navegacion.md`, último apartado. Requiere
  `SESSION_DOMAIN=.fermenty.es` en producción.
