# PRD — Cardápio Joinville (Menu) | Campo | Valor | |-------|--------| | **Produto** | Pizza Digital | | **Versão app auditada** | v0.5.0 | | **Ambiente** | https://pizza.itecnologys.com · VM125 · loja `pizza-digital-piloto` | | **Spec de origem** | `docs/CARDAPIO-JOINVILLE.md + docs/SPEC-CARDAPIO.md` | | **Módulo** | Cardápio / PDV sheet de pizza | | **Rotas UI** | `/pdv` · Admin menu | | **APIs** | `GET/POST/PATCH/DELETE /api/menu/*` · `GET /api/menu/sizes` | | **Data** | 2026-11-09 | | **Estilo** | PRD operacional detalhado (visão de desenvolvedor) | | **Audiência** | Dev / QA / Product | > Como ler: cada seção descreve o **processo real** (o que o sistema faz hoje), > com passos de usuário, chamadas de API, regras de negócio e estados. > Status: ✅ construído · ⚠️ parcial · ⏳ especificado / não entregue · 🐛 gap observado. ## 1. Contexto do desenvolvedor O cardápio Joinville não é só uma lista de sabores: é o **catálogo canônico** que o PDV usa para montar `OrderItem`. Pesquisa regional (`CARDAPIO-JOINVILLE.md`) virou seed + `PizzaSizeBase`. Preço catalogado = **Grande 40 cm**; tamanho/split resolvem no pedido. ### Artefatos de código - UI: `apps/web/src/PdvPage.tsx` (sheet tamanho/sabores) - API: `apps/api/src/menu/menu.controller.ts` · `menu.service.ts` · `pizza-sizes.ts` - Dados: `MenuCategory`, `MenuItem`, `PizzaSizeBase`, `MenuItemStation`, `MenuItemRecipeLine` - Seed: `apps/api/prisma/seed.ts` ### Inventário auditado 8 categorias ativas · **92 itens ativos** · 6 tamanhos · cat. Broto legado `active=false`. --- ## 2. Processo — carregar cardápio no PDV 1. Operador autentica (`POST /api/auth/login` com `storeSlug`). 2. Front chama `GET /api/menu` → `{ pizzaSizes, categories[] }` filtrando `active=true`. 3. Front monta abas/lista por categoria. 4. Item com `halfHalf=true` abre sheet; demais vão direto ao carrinho. **Estados** - Sem token → 401 - Categoria sem itens ativos → não deve renderizar (ou vazia) - 🐛 Em 2026-11-09 a UI chegou a mostrar “Nenhuma categoria…” com API OK — validar fetch/storeSlug no client --- ## 3. Processo — montar pizza (passo a passo) 1. Atendente escolhe fulfillment: Entrega / Balcão / Mesa. 2. Clica sabor (ex.: Calabresa) → sheet. 3. Escolhe `sizeCode` (broto|pequena|media|grande|gigante|familia). 4. Sistema lê `maxFlavors` / `priceFactor` / `diameterCm` / `slices` de `PizzaSizeBase`. 5. Seleciona sabores até o limite; se exceder → erro UI. 6. Preço unitário = `max(priceCents dos sabores) * priceFactor`. 7. Opcional: borda (`Bordas especiais`). 8. Confirma → linha no pedido: `Pizza Grande 40cm · SaborA / SaborB`. 9. `POST /api/orders` com items (`sizeCode`, `halfHalf`, notes). ### Tabela de regras (congelada) | sizeCode | cm | fatias | max sabores | fator | |----------|----|--------|-------------|-------| | broto | 25 | 4 | 1 | 0,55 | | pequena | 30 | 6 | 1 | 0,70 | | media | 35 | 8 | 2 | 0,85 | | grande | 40 | 12 | 3 | 1,00 | | gigante | 45 | 16 | 4 | 1,20 | | familia | 50 | 20 | 4 | 1,35 | --- ## 4. Processo — CRUD gestor 1. Gestor com capability `menu.manage`. 2. `POST /api/menu/categories` / `PATCH` / soft-delete. 3. `POST /api/menu/items` com `priceCents`, `halfHalf`, estações, `stockMode`. 4. `GET /api/menu/ficha-meta` para BOM/ingredientes. 5. Item inativo (`active=false`) some do PDV, permanece no banco. --- ## 5. Sequência (dev) ```text Login → GET /menu → render cats → tap pizza → sheet(size, flavors) → calc priceForSize → add line → POST /orders → KDS tickets por MenuItemStation ``` ## 6. Aceite / gaps - ✅ Seed Joinville + tamanhos - ✅ Cálculo split no PDV - ⚠️ UI Admin CRUD - ⏳ PWA público `/c/:slug` - 🐛 Validar PDV vazio intermitente ## 7. Notas de implementação (dev) - `halfHalf=true` no MenuItem ⇒ sheet de tamanho no PDV (mesmo para pizza “inteira”). - Broto legado: categoria `active=false`; não use SKU Broto separado. - Preço: sempre derive de `priceCents` (Grande) × `PizzaSizeBase.priceFactor`. - Após `POST /orders`, conferir `OrderTicket` gerados via estações do item. - Se PDV listar vazio: checar JWT `storeId`, `GET /api/menu` no Network e `active` das categories.