# Fermenty App — Flujo, navegación y contrato API

> Documento de trabajo para la simulación móvil (`app.fermenty.*`) que define
> el flujo y los contenidos de la futura app Flutter.
> **Estado: EN CURSO — ver §6 "Handoff / próximos pasos".** Última act.: 2026-07-06.

---

## 1. Principios (decididos)

1. **La app es cliente de la API desde el primer día.** Las pantallas Blade de
   `app.*` son carcasas finas (Alpine.js) que consumen `api.fermenty.*` `/v1`
   por fetch con token Bearer (localStorage) — igual que lo hará Flutter.
   Nada de leer modelos Eloquent directamente ni sesiones Laravel.
2. **Exploración sin cuenta (guest-first).** Todas las pestañas principales se
   pueden ver sin login. El login/registro aparece solo al ACTUAR (iniciar un
   lote, perfil, like/publicar). Bienvenida cálida que invita a registrarse.
3. **"Tracker" en vez de "Notificaciones".** La pestaña es el hub de
   notificaciones + tareas/agenda + trazabilidad de lotes en curso. Más
   adelante será la entrada a agenda y trazabilidad completa.

## 2. Estructura de navegación (bottom nav) — IA del design kit (act. 2026-07-06)

| Tab | Ruta sim | Acceso | Pantalla |
|---|---|---|---|
| Inicio | `/home` | público (guest → invitación) | consejo del día + lotes en marcha + recetas destacadas |
| Fermentos | `/recetas` | público | espejo del `/fermentos` web: secciones Gratuitos/Premium, foto, días, dificultad; CTA → receta |
| Planificador | `/plan` | público (guest → teaser) | avisos + cronología agrupada por día (como `/planificador` web) + mis lotes |
| Lab | `/herramientas` | público | Laboratorio: hub como la web + calc. alcohol (`/herramientas/calculadora-alcohol`), salmuera (`…/calculadora-salmuera`) y materiales (`…/materiales`) |
| Comunidad | `/comunidad` | público (solo lectura) | feed con pills de filtro por tipo (como la web) + composer con tipo |

Topbar (AppHeader): campana → `/plan` (avisos); avatar → `/perfil` — Perfil vive
arriba, por eso Comunidad ocupa la quinta pestaña.
Fuera del nav: `/` bienvenida (mark oficial + CTAs + enlace al tour), `/onboarding`
(tour 4 slides), `/login`, `/register`, `/premium`, `/perfil`, `/lotes/crear`,
`/lotes/{id}`, `/recetas/{slug}`, `/comunidad/{id}`, `/comunidad/usuario/{id}`.

**Shell (act. 2026-07-06):** el frame-simulador (390×820) sólo se renderiza en
desktop — `layouts/mobile.blade.php` detecta el user-agent y en dispositivo real
va a pantalla completa (`.fm-app-root`, safe-areas, rotación libre); se puede
desactivar del todo con `MOBILE_PREVIEW=false`. En dispositivo real se muestra
además un splash de carga inline (mark + lockup sobre green-900) hasta
`alpine:init`/`load`. Las pantallas "bare" oscuras usan `fm-screen--dark` para
cubrir todo el alto con el fondo verde.

## 3. Contrato pantalla ↔ API

| Pantalla | Endpoint(s) | Auth | Estado API |
|---|---|---|---|
| Home (explorar) | `GET /v1/catalogue` (guest) / `GET /v1/dashboard` (auth) | mixto | ✅ ambos |
| Lotes | `GET /v1/batches` | Bearer | ✅ |
| Detalle de lote | `GET /v1/batches/{id}` (steps + ingredients con `display` en unidades del usuario) | Bearer | ✅ |
| Avanzar paso | `POST /v1/batches/{id}/advance` | Bearer | ✅ |
| Crear lote | `GET /v1/catalogue` + `POST /v1/batches` (límite free → 403 `upgrade_required`) | Bearer | ✅ |
| Tracker | `GET /v1/tracker` → `{agenda, notifications, active_batches, unread_count}` | Bearer | ✅ |
| Catálogo | `GET /v1/catalogue` | público | ✅ |
| Comunidad | `GET /v1/community` (paginado) | público | ✅ |
| Perfil | `GET /v1/me` | Bearer | ✅ |
| Login/Registro | `POST /v1/login` / `POST /v1/register` → `{token, user}` | público | ✅ |
| Logout | `POST /v1/logout` (revoca token actual) | Bearer | ✅ |

Pendiente de API (fase siguiente): quick-log de mediciones, logs diarios,
acciones de comunidad (like/post/comentario), marcar notificación leída,
device tokens para push.

## 4. Convenciones del cliente (sim y Flutter)

- Token en `localStorage.fm_token` (sim). Sin token = modo guest.
- Cabeceras: `Accept: application/json`, `Authorization: Bearer <token>`.
- Errores: 401 → limpiar token y mostrar login; 403 con `code: upgrade_required`
  → pantalla/modal Premium; 422 → `{message, errors}` estándar Laravel.
- Cantidades: la API entrega valor canónico métrico + `display` ya formateado
  según `unit_system` del usuario.
- URLs entre subdominios: `config('app.mobile_url')` y `config('app.api_url')`
  (env `MOBILE_APP_URL`, `API_APP_URL`; default `http://{app|api}.fermenty.local:8084`).

## 5e. Hecho en esta tanda (2026-07-06, 4ª ronda — auditoría: SEO, carga, apaisado)

- **SEO**: `meta robots noindex` en el layout de la app — `app.*` son carcasas
  JS; el SEO vive en fermenty.es y así no compiten/duplican.
- **Carga**: Bootstrap eliminado del bundle móvil (no se usaba ninguna clase;
  242 KB → 12 KB; el ERP lo conserva en app.scss). Quitado un `@import` de
  Google Fonts duplicado en `_base.scss` (traía Inter, del esquema viejo).
  **Alpine self-host** en `public/vendor/alpine-3.min.js` (sin CDN en el
  critical path). **Preconnect** al host del API en el `<head>`. FontAwesome
  se queda en CDN (cachea tras la primera visita; candidato futuro a subset).
- **Splash**: SIEMPRE ≥2 s en el arranque (abrir/recargar/llegar de fuera,
  vía PerformanceNavigationTiming + referrer); la navegación interna entre
  pestañas se libera sin mínimo.
- **Apaisado/tablet**: nueva clase `.fm-cards` — 1 columna en vertical, 2
  columnas al 50 % desde 600 px, y `.fm-pad` capado a 680 px centrado.
  Aplicada al catálogo de Fermentos, a "Recetas para empezar" (Inicio) y a
  las dos secciones del Lab.
- **Bienvenida con sesión**: si hay token, en vez de registro/login se
  muestra "Hola, {nombre}" + botón "Entrar »".

## 5d. Hecho en esta tanda (2026-07-06, 3ª ronda — feedback de pantallas)

- **Fix fotos del catálogo**: el binding `:style` en *string* de Alpine reemplaza
  el atributo `style` estático (no lo fusiona) — por eso no se veían las fotos.
  Todas las imágenes de fondo usan ahora `:style` con **objeto**
  (`{ backgroundImage: … }`). Tarjeta de fermento extraída al partial
  `mobile/partials/_ferment-card.blade.php` (compartida por Inicio y Fermentos).
- **Inicio**: lotes en marcha a 2 por fila (anillo % arriba en el color de la
  categoría, título 2 líneas/63 chars, "Día n/N · %"); "Recetas para empezar"
  usa las mismas tarjetas con foto que /fermentos (3 gratuitos con receta).
- **Fermentos**: tabs arriba — **Mis lotes · Gratuitos · Premium**; con lotes
  activos, "Mis lotes" es la pestaña por defecto; guest ve invitación.
- **Campana del topbar**: badge de avisos no leídos (usa `unread_count` de
  `GET /v1/notifications`, cache 60 s en sessionStorage; se invalida al marcar
  leído y al cerrar sesión).
- **Perfil**: espejo del /perfil web — avatar+infos+badges (plan, nivel,
  fermento favorito), Estadísticas, Últimos lotes; **Editar perfil**
  (`/perfil/editar`: nombre, nivel, favorito) y **Configuración**
  (`/perfil/configuracion`: idioma, unidades, switches) como pantallas detrás,
  accesibles también desde el topbar. `GET /v1/me` devuelve ahora `stats`
  (total/activos/completados/recetas/posts) + `avatar_url` + `favorite_ferment`;
  `BatchResource` añade `total_days`.
- **Splash**: sólo la primera carga de la sesión aplica el mínimo de 2 s; al
  navegar entre pestañas se oculta en cuanto el contenido está listo.
- **Catálogo ordenado por uso**: `GET /v1/catalogue` ordena por lotes creados
  (`withCount('batches')` desc; empate por nombre) → Kombucha, Kéfir… primero
  en Inicio y Fermentos.
- **Mis lotes vacío**: "Explorar recetas" (salta a la pestaña Gratuitos) +
  botón "Nuevo lote" (renombrado desde "Añadir lote", también en Planificador).
- **Comunidad**: CTA al blog público (`config('app.url')/blog`, nueva pestaña).
- **Renombrar lote** en `/lotes/{id}`: lápiz junto al nombre → input inline
  (Enter guarda, Esc cancela). Nuevo `PUT /v1/batches/{batch}` (name only,
  `BatchPolicy@update`; espejo del update web) + test propio.

## 5c. Hecho en esta tanda (2026-07-06, 2ª ronda — pantallas orientadas a la web)

- **Fermentos** (`/recetas`): reconstruida como el `/fermentos` web — secciones
  Gratuitos/Premium, tarjeta con foto (`thumbnail_url`, letra de respaldo),
  descripción, días + dificultad + chip de categoría, CTA a la receta.
  `GET /v1/catalogue` ampliado: `image`, `category{name,slug,color}` y
  `recipe{slug,name}` (primera receta publicada de la guía vigente).
- **Planificador** (antes "Plan"): cronología agrupada por día con marcador de
  fecha, Hoy/Mañana/atrasado y tipo de actividad (Límite/Nuevo paso/Fin
  estimado/Recordatorio), como el `/planificador` web.
- **Lab** (antes "Tools"): hub espejo del `/laboratorio` web + 3 pantallas
  nuevas: calculadora de alcohol (fórmulas de `AbvCalculator`/`AbvCompliance`
  en JS), calculadora de salmuera (datos/fórmula de `BrineCalculator`) y
  materiales y equipo (mismo contenido que la vista web). FAQ enlaza a la web.
- **Comunidad**: pills de filtro por tipo con counts (`GET /v1/community?tipo=`
  + `counts`), composer con selector de tipo, hora relativa en posts.
- **Bienvenida**: wordmark oficial solo-letras (`fermenty-wordmark[-light].svg`
  copiados del Design System), botón del tour destacado, "Ya tengo cuenta »".
- **Tour**: placeholder de ilustración por slide (334×190, @2x 668×380) listo
  para el artwork final; splash con mínimo 2 s visible.
- **Landing pública**: el botón "Entrar" apunta según dispositivo — móvil →
  `app.*/login`, desktop → `erp.*/login` (user-agent en `landing-nav`).
  ⚠️ En prod requiere `MOBILE_APP_URL=https://app.fermenty.es` en el `.env`.

## 5b. Hecho en esta tanda (2026-07-06)

- **Bottom nav:** Comunidad sustituye a Perfil como quinta pestaña (Perfil queda
  en el topbar). Padding inferior con `env(safe-area-inset-bottom)`.
- **Frame sólo en desktop:** detección de user-agent en el layout; en móvil real
  la app va a pantalla completa con `viewport-fit=cover` + safe-areas (rotación
  incluida). Nueva config `app.mobile_preview` (`MOBILE_PREVIEW`).
- **Splash de carga** inline (autosuficiente, sin depender del bundle) en modo
  dispositivo real: mark + lockup con pulso sobre green-900; se oculta en
  `alpine:init`/`load` (failsafe 8 s).
- **Bienvenida** rediseñada con el artwork oficial (mark 96px + lockup), fondo
  verde cubriendo toda la pantalla (`fm-screen--dark`) y enlace al tour.
- **Onboarding** actualizado: 4 slides (recetas por categoría, lotes, avisos,
  laboratorio+comunidad), dots clicables, CTA final "Probar sin cuenta" +
  "Crear cuenta gratis".
- **Limpieza de esquema antiguo:** fuera el naranja/dorado pre-Scheme 2
  (`_custom.scss`, `mobile/icons/*` sin uso, sombra naranja del shell, rgba
  dorado viejo de la bienvenida).

## 5. Hecho en esta tanda (2026-06-12)

- Link rápido **📱 App sim** en la navbar del admin (`config('app.mobile_url')`).
- API v1 nueva: `GET /catalogue` y `GET /community` públicos; `GET/POST /batches`,
  `GET /batches/{id}`, `POST /batches/{id}/advance`, `GET /tracker` autenticados.
- `BatchResource` ampliado: `target_yield`, `steps[]` (con guide step info),
  `ingredients[]` (con `display` formateado por usuario).
- `PlannerService` extraído (compartido por el planificador web y `/v1/tracker`);
  `PlannerController` web refactorizado para usarlo.

## 6. Handoff / próximos pasos (continuar aquí)

> Suite verde (46 tests) al cierre. Sin migraciones nuevas en esta tanda.
> En la otra máquina: `git pull` y listo (no hay paquetes nuevos).

1. **Tests de la API nueva** (`tests/Feature/`): catálogo/comunidad públicos
   (200 sin token), batches 401 sin token, flujo crear→ver→avanzar con
   `Sanctum::actingAs`, límite free → 403 `upgrade_required`. Patrón: como
   `QuickLogAndPlannerTest` (grafo manual, sin factories). OJO: rutas por host —
   en tests usar URL completa `http://api.fermenty.local/v1/...`.
2. **Layout sim** (`resources/views/layouts/mobile.blade.php`): quitar los
   `@auth` del topbar/bottomnav (siempre visibles; el cliente decide por token);
   añadir helper JS `fmApi` (base URL de `config('app.api_url')`, token de
   localStorage, wrapper fetch con manejo 401) + `window.fmAuthed()`.
3. **Rutas sim** (`routes/mobile.php`): convertir a `Route::view`/closures puras
   (la data llega por API). Renombrar pestaña notificaciones → `/tracker`
   (name `tracker`); `/lotes/crear` ANTES de `/lotes/{id}` (bug de orden actual);
   eliminar POST login/register/logout de servidor (los hace la API) y borrar
   los controladores `Mobile\Login/Register/Home/Welcome/Onboarding` obsoletos.
   `Mobile\Batch/Catalogue/ProfileController` referenciados hoy NO existen.
4. **Vistas sim** (carcasas Alpine + fmApi, modo guest en cada una):
   `home` (explorar/dashboard), `batches` (lista), `batch-detail` (pasos +
   ingredientes + botón avanzar; arreglar `route('mobile.batches')` →
   `route('batches')`), `batch-create` (catálogo + cantidad + POST), `catalogue`,
   `community` (feed público; reemplaza el teaser Premium actual), `tracker`
   (agenda+notifs+lotes), `profile` (guest → invitación cálida; auth → me +
   logout), `login`/`register` (POST a la API, guardar token, respetar `?next=`).
5. **Bottom nav**: label "Tracker" con icono de notificaciones existente.
6. Actualizar este doc (§5/§6) al avanzar.
