Import Pizza Digital SPECs and docs from Obsidian vault sync
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
39128587c6
commit
d669f56398
30 changed files with 5022 additions and 2 deletions
1
.last-sync-pull
Normal file
1
.last-sync-pull
Normal file
|
|
@ -0,0 +1 @@
|
|||
2026-11-09T19:44:56Z
|
||||
1
.last-sync-push
Normal file
1
.last-sync-push
Normal file
|
|
@ -0,0 +1 @@
|
|||
2026-11-09T19:44:43Z
|
||||
40
Pizza-Digital.md
Normal file
40
Pizza-Digital.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Pizza Digital — Hub Obsidian
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **SPEC** | [[SPEC]] · v0.3.2 |
|
||||
| **Host** | big1 · VM **125** · `10.10.10.125` · user `mini` |
|
||||
| **URL** | https://pizza.itecnologys.com |
|
||||
| **Vault** | `Pizza-Digital/` |
|
||||
| **Fonte VM** | `/home/mini/pizza-digital/` |
|
||||
| **Sync** | [[SYNC-PIZZA-DIGITAL]] |
|
||||
| **Última sync** | 2026-11-09T19:44:42Z |
|
||||
|
||||
## Specs
|
||||
|
||||
- [[SPEC]] — Spec mãe
|
||||
- [[docs/SPEC-FASE1]] — MVP Cozinha Digital
|
||||
- [[docs/SPEC-CANAIS-DIGITAIS]] — Canais digitais
|
||||
- [[docs/SPEC-CARDAPIO]] — Cardápio
|
||||
- [[docs/SPEC-ESTOQUE]] — Estoque / BOM
|
||||
- [[docs/SPEC-MATRIZ-ACESSO]] — Matriz de acesso
|
||||
- [[docs/SPEC-USUARIOS]] — Usuários / sessão
|
||||
- [[docs/SPEC-KDS-CLAIM]] — KDS claim
|
||||
- [[docs/SPEC-KDS-PIZZAIOLO-MODELO-B]] — KDS pizzaiolo Modelo B
|
||||
- [[docs/SPEC-DESPACHO-CITYFOODS-PARITY]] — Despacho CityFoods
|
||||
- [[docs/SPEC-CITYFOODS-VIDEO-PARITY]] — Paridade vídeos
|
||||
- [[docs/SPEC-LACUNAS-VIDEO-SIRIFOOD]] — Lacunas Sirifood
|
||||
- [[docs/SPEC-SIM-OPS-DEMO]] — Demo /sim
|
||||
|
||||
## Documentos de construção
|
||||
|
||||
- [[README]] — Bootstrap Docker
|
||||
- [[docs/CHANGELOG-SPEC]] — Changelog
|
||||
- [[docs/RELEASE-v0.0.0]] — Release
|
||||
- [[docs/CARDAPIO-JOINVILLE]] — Pesquisa Joinville
|
||||
- [[docs/FICHAS-TECNICAS-PESO]] — Fichas (g)
|
||||
- [[docs/API-WHATSAPP-INBOUND]] — WhatsApp inbound
|
||||
- [[docs/PROMPT-CENTRAL-PEDIDOS]] — Prompt Cursor
|
||||
- [[docs/MIGRATION-ACCESS-MATRIX]] — Migração ACS
|
||||
- [[docs/SSH-CLAUDE-CLOUD-HANDOFF]] — Handoff SSH
|
||||
14
README.md
14
README.md
|
|
@ -1,3 +1,13 @@
|
|||
# pizza-digital
|
||||
# Pizza Digital
|
||||
|
||||
Pizza Digital — Specs, docs e projeto (VM125)
|
||||
Documentação e SPECs do projeto **Pizza Digital** (VM125 · 10.10.10.125).
|
||||
|
||||
- Hub: [Pizza-Digital.md](Pizza-Digital.md)
|
||||
- Spec mãe: [SPEC.md](SPEC.md)
|
||||
- Sync Obsidian: [SYNC-PIZZA-DIGITAL.md](SYNC-PIZZA-DIGITAL.md)
|
||||
- App: https://pizza.itecnologys.com
|
||||
- Vault Obsidian: pasta Pizza-Digital/ em ligbox/obsidian-vault
|
||||
|
||||
## Acesso Forgejo
|
||||
|
||||
https://git.itecnologys.com/itecnologys/pizza-digital
|
||||
|
|
|
|||
724
SPEC.md
Normal file
724
SPEC.md
Normal file
|
|
@ -0,0 +1,724 @@
|
|||
# SPEC — Pizza Digital
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Versão do SPEC** | **0.3.2** |
|
||||
| **Status** | Fase 1 cozinha no ar · **Prioridade seguinte: entrada por canais digitais** |
|
||||
| **Data** | 2026-07-23 · rev. matriz acesso 2026-10-19 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **Host Dev** | Proxmox BIG · VMID **125** · `10.10.10.125` · user `mini` |
|
||||
| **URL pública** | `https://pizza.itecnologys.com` (legado SPEC: pizza.myvexx.com) |
|
||||
| **Detalhe Fase 1** | [`docs/SPEC-FASE1.md`](docs/SPEC-FASE1.md) |
|
||||
| **Canais digitais** | [`docs/SPEC-CANAIS-DIGITAIS.md`](docs/SPEC-CANAIS-DIGITAIS.md) · §5.5 |
|
||||
| **Cardápio** | [`docs/SPEC-CARDAPIO.md`](docs/SPEC-CARDAPIO.md) · RF-MEN · Joinville |
|
||||
| **Estoque** | [`docs/SPEC-ESTOQUE.md`](docs/SPEC-ESTOQUE.md) · RF-EST · BOM ↔ menu |
|
||||
| **Matriz de acesso** | [`docs/SPEC-MATRIZ-ACESSO.md`](docs/SPEC-MATRIZ-ACESSO.md) · `/acessos` · RF-ACS |
|
||||
| **Sequência oficial** | §15 — S0…S9 · Track C · Track M/E · Matriz ACS |
|
||||
| **Referência principal** | CityFoods — Cozinha Digital + Cardápio Digital (`@cityfoods_app`) |
|
||||
| **KDS / estações** | kitchenOS + restaurant-pos + este SPEC |
|
||||
| **Rotas / dispatch** | **rusterando** = referência oficial (ORS, driver tour) — AGPL: modelo operacional, não fork do core |
|
||||
| **Repos open-source** | kitchenOS, rusterando, URY Mosaic, MaSoVa, Tasty-Station |
|
||||
|
||||
---
|
||||
|
||||
## 1. Visão
|
||||
|
||||
**Pizza Digital** é uma plataforma de operação de delivery para pizzarias, restaurantes e outras cozinhas, com foco em **Cozinha Digital (KDS)**: eliminar comanda de papel, organizar produção por estações, reduzir erros e acelerar despacho.
|
||||
|
||||
Não é um marketplace. É o **sistema operacional da cozinha + delivery próprio**, no mesmo espírito da CityFoods:
|
||||
|
||||
> Cozinha Digital não é sobre robôs — é inteligência e fluxo à prova de falhas, do pedido à entrega.
|
||||
|
||||
### 1.1 Problema
|
||||
|
||||
Operações de delivery perdem tempo e clientes por:
|
||||
|
||||
- Comandas de papel (perda, rasura, leitura ruim)
|
||||
- Pedidos de vários canais em telas diferentes
|
||||
- Falta de priorização (pedido novo não sai junto de outros prontos)
|
||||
- Erros de observação / montagem
|
||||
- Motoboys mal roteados (ida e volta na mesma região)
|
||||
- Zero métrica de tempo por estação
|
||||
|
||||
### 1.2 Promessa
|
||||
|
||||
Uma plataforma única que:
|
||||
|
||||
1. Recebe pedidos (PDV, cardápio digital, marketplaces)
|
||||
2. Distribui por **estações de cozinha** (KDS)
|
||||
3. Identifica pedidos por **etiquetas**
|
||||
4. Prioriza e agrupa saída
|
||||
5. Despacha com **gestão de rotas**
|
||||
6. Mede **tempo e produtividade** em tempo real
|
||||
|
||||
### 1.3 Posicionamento
|
||||
|
||||
| | CityFoods (referência) | Pizza Digital (nossa) |
|
||||
|--|------------------------|------------------------|
|
||||
| Foco | Delivery multi-tipo | **Pizzaria primeiro**, depois outras cozinhas |
|
||||
| Diferencial citado | “Primeira Cozinha Digital” | Cozinha Digital + templates de estação por tipo de negócio |
|
||||
| Mercado | SC / BR SaaS | Começar local / controlado, evoluir para SaaS multi-tenant |
|
||||
|
||||
---
|
||||
|
||||
## 2. Objetivos e métricas de sucesso
|
||||
|
||||
### 2.1 Objetivos de produto (MVP)
|
||||
|
||||
- Substituir comanda de papel por KDS touch
|
||||
- Operar **1 pizzaria piloto** de ponta a ponta (pedido → pronto → despacho)
|
||||
- Reduzir erros de montagem / observação
|
||||
- Medir tempo médio por estação e por pedido
|
||||
|
||||
### 2.2 KPIs (piloto)
|
||||
|
||||
| KPI | Meta inicial (90 dias) |
|
||||
|-----|------------------------|
|
||||
| Erros de pedido (reclamação / refazer) | −50% vs baseline |
|
||||
| Tempo médio pedido → pronto | −20% |
|
||||
| Pedidos sem papel na cozinha | 100% no piloto |
|
||||
| Atraso de despacho (> SLA) | −30% |
|
||||
| Adoção diária do KDS | ≥ 95% dos pedidos passam pelo KDS |
|
||||
|
||||
### 2.3 Não-objetivos (MVP)
|
||||
|
||||
- App consumidor nativo (iOS/Android) completo
|
||||
- Marketplace próprio
|
||||
- Contabilidade / ERP completo
|
||||
- Multi-país / multi-moeda
|
||||
- IA generativa na cozinha
|
||||
|
||||
---
|
||||
|
||||
## 3. Personas e papéis
|
||||
|
||||
| Papel | Quem | Precisa |
|
||||
|-------|------|---------|
|
||||
| **Operador KDS** | Cozinheiro / pizzaiolo | Tela grande, bump rápido, ver observações |
|
||||
| **Expedição** | Embalador / conferente | Pedido completo, etiqueta, agrupar saída |
|
||||
| **Caixa / PDV** | Atendente | Lançar pedido balcão / telefone |
|
||||
| **Despachante** | Quem libera motoboy | Agrupar por região, priorizar |
|
||||
| **Entregador** | Motoboy | Lista de entregas, status, navegação básica |
|
||||
| **Gestor** | Dono / gerente | Métricas, cardápio, usuários, relatórios |
|
||||
| **Admin SaaS** (fase 2) | iTecnologys | Multi-loja, billing, suporte |
|
||||
|
||||
---
|
||||
|
||||
## 4. Escopo por fases
|
||||
|
||||
### Fase 0 — Fundação (esta SPEC + bootstrap)
|
||||
|
||||
- Repositório, monorepo, CI mínimo
|
||||
- Modelo de domínio e eventos
|
||||
- Ambiente local (Docker)
|
||||
|
||||
### Fase 1 — MVP Cozinha Digital (core) ✅ em andamento
|
||||
|
||||
> Detalhe completo: [`docs/SPEC-FASE1.md`](docs/SPEC-FASE1.md) · Host: VM **125** `10.10.10.125`
|
||||
|
||||
- Pedidos manuais (PDV web)
|
||||
- KDS multi-estação (pizzaria)
|
||||
- Status: `novo → em_preparo → pronto → despachado → entregue`
|
||||
- Priorização / reordenação de fila
|
||||
- Etiquetas (impressão ESC/POS ou PDF fallback)
|
||||
- Painel gestor básico (tempos)
|
||||
- Stack: NestJS + React + Postgres + Redis + Socket.io (Docker na VM 125)
|
||||
|
||||
### Fase 2 — Delivery operacional (após KDS estável)
|
||||
|
||||
> Prioridade de construção: **KDS multi-estação primeiro**; delivery/rotas só depois do piloto de cozinha (S5).
|
||||
> Referência oficial de dispatch: **[rusterando](https://github.com/holg/rusterando)** (kitchen → driver tour → ORS). Detalhe: §15.1 e §15.2.
|
||||
|
||||
- Gate `order.ready` → fila de despacho
|
||||
- Agrupar por bairro (manual + sugestão) + atribuir motoboy + reordenar saída (“2 cliques” CityFoods)
|
||||
- OpenRouteService (padrão rusterando) para ordem da rota
|
||||
- Mapa no despacho · PWA entregador · tracking
|
||||
- Pagamento/comissão por entrega · fechamento de caixa básico
|
||||
|
||||
### Fase 3 — Canais digitais e SaaS (PRIORIDADE pós-S5)
|
||||
|
||||
> Track **C** (canais) começa em paralelo assim que o piloto de cozinha (S5) estiver estável — **não esperar o fim de S6–S8**.
|
||||
> Ver §5.5 e [`docs/SPEC-CANAIS-DIGITAIS.md`](docs/SPEC-CANAIS-DIGITAIS.md).
|
||||
|
||||
- **C1** Cardápio digital público (PWA) + checkout (Pix / dinheiro na entrega)
|
||||
- **C1** Pedidos digitais no quadro `/pedidos` com badge de origem
|
||||
- **C2** Integração iFood (webhook → pedido normalizado)
|
||||
- Multi-tenant / multi-loja
|
||||
- Relatórios operacionais avançados
|
||||
- Templates de cozinha: pizzaria, hamburgueria, sushi, dark kitchen
|
||||
|
||||
---
|
||||
|
||||
## 5. Conceito operacional — Cozinha Digital
|
||||
|
||||
### 5.1 Estações (template Pizzaria)
|
||||
|
||||
| Estação | Responsabilidade | Exemplos de itens |
|
||||
|---------|------------------|-------------------|
|
||||
| **Massa** | Abrir / preparar base | Massa, borda |
|
||||
| **Montagem** | Molho, queijo, toppings | Sabores, adicionais |
|
||||
| **Forno** | Assar | Pizza em forno |
|
||||
| **Expedição** | Embalar, conferir, etiquetar | Pedido completo |
|
||||
| **Bebidas** (opcional) | Separar frios | Refrigerante, água |
|
||||
|
||||
**Princípio KDS (CityFoods — claim exclusivo):**
|
||||
|
||||
1. A **fila chega para todas** as estações.
|
||||
2. Uma estação **escolhe e puxa (PEGAR)** um ou mais pedidos para si.
|
||||
3. Com o claim, **as outras estações ficam bloqueadas** de pegar aquele pedido.
|
||||
4. Só a estação dona dá **BUMP** (pedido pronto).
|
||||
|
||||
Regras de mapeamento (histórico / tickets):
|
||||
|
||||
- Cada item do cardápio mapeia para **1+ estações** (referência de produção)
|
||||
- Claim é ao nível do **pedido completo**, não por item
|
||||
|
||||
### 5.2 Template futuro — outras cozinhas
|
||||
|
||||
| Tipo | Estações padrão |
|
||||
|------|-----------------|
|
||||
| Hamburgueria | Grelha · Fritura · Montagem · Expedição |
|
||||
| Sushi | Corte · Montagem · Expedição |
|
||||
| Dark kitchen genérica | Prep · Quente · Frios · Expedição |
|
||||
|
||||
### 5.3 Etiquetas
|
||||
|
||||
Cada pedido (ou item, configurável) gera etiqueta com:
|
||||
|
||||
- Código curto do pedido (ex: `#A42`)
|
||||
- Canal (PDV / iFood / Cardápio)
|
||||
- Cliente (nome parcial)
|
||||
- Itens resumidos + observações em destaque
|
||||
- Horário / timer SLA
|
||||
- QR opcional para conferência
|
||||
|
||||
Objetivo: eliminar grampear comanda ilegível na embalagem.
|
||||
|
||||
### 5.4 Priorização (inspirado CityFoods “2 cliques”)
|
||||
|
||||
O despachante/gestor pode:
|
||||
|
||||
- Reordenar fila de saída
|
||||
- Agrupar pedidos da mesma região para sair juntos
|
||||
- Marcar pedido como **urgente** (sobresai visualmente no KDS)
|
||||
|
||||
### 5.5 Entrada por canais digitais (PRIORIDADE)
|
||||
|
||||
> Detalhe de produto: [`docs/SPEC-CANAIS-DIGITAIS.md`](docs/SPEC-CANAIS-DIGITAIS.md) · RFs: RF-CAN, RF-PED-08/09, RF-MEN-04/06/07, RF-INT-01/03/05
|
||||
|
||||
**Problema:** pedidos chegam por telefone, WhatsApp e iFood em telas diferentes — a loja digita de novo no PDV e erra.
|
||||
|
||||
**Promessa:** o cliente pede no canal digital; a loja vê **o mesmo Order** no quadro `/pedidos` e a cozinha trata igual a pedido de balcão.
|
||||
|
||||
#### Dimensões (substituem `channel` misturado)
|
||||
|
||||
| Dimensão | Valores | Uso na UI |
|
||||
|----------|---------|-----------|
|
||||
| `origin` | `pdv` · `cardapio` · `ifood` · `whatsapp` · `telefone` | Badge no card (CARDÁPIO / IFOOD / …) |
|
||||
| `fulfillment` | `entrega` · `retirada` · `mesa` | Faixa ENTREGA / RETIRADA (CityFoods) |
|
||||
|
||||
#### Canais de entrada → um quadro
|
||||
|
||||
```text
|
||||
Cardápio PWA /c/:slug ─┐
|
||||
WhatsApp (deep-link) ─┼─► Order Ingest (schema único)
|
||||
iFood webhook ─┤ │
|
||||
PDV / telefone (staff) ─┘ ▼
|
||||
/pedidos (badge origem + fulfillment)
|
||||
│
|
||||
▼
|
||||
KDS · Etiqueta · Despacho
|
||||
```
|
||||
|
||||
#### Track C (após piloto S5; C1 pode prototipar em paralelo)
|
||||
|
||||
| Track | Entrega |
|
||||
|-------|---------|
|
||||
| **C1** | Cardápio público + checkout (Pix / pagar na entrega) → `/pedidos` ≤ 500 ms |
|
||||
| **C2** | Webhook iFood → normalizador + idempotência |
|
||||
|
||||
**Regra de ouro:** nenhum canal tem UI de cozinha própria — tudo passa pelo Order bus e pelo `/pedidos`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Fluxos principais
|
||||
|
||||
### 6.1 Fluxo feliz — delivery pizzaria (canal digital ou PDV)
|
||||
|
||||
```text
|
||||
[Cliente] Cardápio /c/:slug | WhatsApp | iFood
|
||||
[Staff] PDV /pedidos (+ Novo)
|
||||
→ Order Ingest normaliza (origin + fulfillment + itens)
|
||||
→ Aparece em /pedidos (badge origem · faixa ENTREGA/RETIRADA)
|
||||
→ Roteia itens → estações KDS
|
||||
→ Operadores dão claim/bump por estação
|
||||
→ Expedição confere + imprime etiqueta (origem + fulfillment)
|
||||
→ Pedido = PRONTO
|
||||
→ Despacho atribui motoboy / agrupa rota
|
||||
→ Entregador: saiu → entregue
|
||||
→ Métricas gravadas (tempos por etapa)
|
||||
```
|
||||
|
||||
### 6.2 Estados do pedido
|
||||
|
||||
```text
|
||||
rascunho → confirmado → em_producao → pronto → em_rota → entregue
|
||||
↓
|
||||
cancelado
|
||||
```
|
||||
|
||||
Estados de **item na estação**:
|
||||
|
||||
```text
|
||||
na_fila → em_preparo → concluido | cancelado
|
||||
```
|
||||
|
||||
### 6.3 Diagrama lógico
|
||||
|
||||
```text
|
||||
Canais Core Operação
|
||||
───── ──── ────────
|
||||
PDV ──────┐
|
||||
Cardápio ─┼─► Order Ingest ─► Router ─► KDS Stations
|
||||
iFood ────┘ │ │ │
|
||||
│ │ ▼
|
||||
│ └──► Labels / Print
|
||||
▼
|
||||
Order Store ◄── WebSocket fan-out
|
||||
│
|
||||
▼
|
||||
Dispatch / Routes ─► Driver PWA
|
||||
│
|
||||
▼
|
||||
Analytics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Requisitos funcionais
|
||||
|
||||
### 7.1 Pedidos (RF-PED)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-PED-01 | Criar pedido no PDV (itens, adicionais, obs, tipo: delivery/balcão/mesa) | Sim |
|
||||
| RF-PED-02 | Schema único de pedido independente do canal | Sim |
|
||||
| RF-PED-03 | Editar pedido até entrar em produção (política configurável) | Sim |
|
||||
| RF-PED-04 | Cancelar pedido com motivo | Sim |
|
||||
| RF-PED-05 | Observações em destaque no KDS | Sim |
|
||||
| RF-PED-06 | Número sequencial diário + código curto legível | Sim |
|
||||
| RF-PED-07 | Ingestão via webhook (iFood etc.) | Track C2 |
|
||||
| RF-PED-08 | Campos `origin` + `fulfillment` (em vez de channel misturado) | Track C1 |
|
||||
| RF-PED-09 | Pedido digital aparece no `/pedidos` em tempo real (WS) | Track C1 |
|
||||
|
||||
### 7.2 KDS / Cozinha Digital (RF-KDS)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-KDS-01 | Tela KDS por estação (filtro de área) | Sim |
|
||||
| RF-KDS-02 | Atualização em tempo real (WebSocket, sem polling) | Sim |
|
||||
| RF-KDS-03 | Timer visual (verde / amarelo / vermelho por SLA) | Sim |
|
||||
| RF-KDS-04 | Bump item / bump pedido na estação | Sim |
|
||||
| RF-KDS-05 | Multi-estação no mesmo pedido | Sim |
|
||||
| RF-KDS-06 | Som/alerta opcional de novo ticket | Sim |
|
||||
| RF-KDS-07 | Modo tela cheia / tablet / TV | Sim |
|
||||
| RF-KDS-08 | Offline degradado (fila local + sync) | Fase 2 |
|
||||
|
||||
### 7.3 Etiquetas (RF-ETQ)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-ETQ-01 | Gerar etiqueta ao concluir expedição (ou sob demanda) | Sim |
|
||||
| RF-ETQ-02 | Impressão térmica ESC/POS | Sim (com fallback PDF) |
|
||||
| RF-ETQ-03 | Layout configurável por loja | Fase 2 |
|
||||
|
||||
### 7.4 Despacho e rotas (RF-ROT)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-ROT-01 | Lista de pedidos prontos para despacho | Fase 1 (lista simples) |
|
||||
| RF-ROT-02 | Atribuir entregador | Fase 2 |
|
||||
| RF-ROT-03 | Agrupar por bairro / proximidade | Fase 2 |
|
||||
| RF-ROT-04 | Reordenar saída (priorização) | Fase 1 |
|
||||
| RF-ROT-05 | App/PWA entregador com status | Fase 2 |
|
||||
| RF-ROT-06 | Cálculo de pagamento de entregadores | Fase 3 |
|
||||
|
||||
### 7.5 Cardápio e PDV (RF-MEN)
|
||||
|
||||
> Detalhe: [`docs/SPEC-CARDAPIO.md`](docs/SPEC-CARDAPIO.md) · pesquisa [`docs/CARDAPIO-JOINVILLE.md`](docs/CARDAPIO-JOINVILLE.md)
|
||||
> Ponte estoque: [`docs/SPEC-ESTOQUE.md`](docs/SPEC-ESTOQUE.md)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-MEN-01 | CRUD categorias, itens, preços, adicionais | Sim |
|
||||
| RF-MEN-02 | Mapear item → estações KDS | Sim |
|
||||
| RF-MEN-03 | Tempo SLA por categoria/item | Sim |
|
||||
| RF-MEN-04 | Cardápio digital público (PWA cliente) | **Track C1** |
|
||||
| RF-MEN-05 | Combos / meia-a-meia (pizzaria) | Fase 1 (meia-a-meia) |
|
||||
| RF-MEN-06 | Horário de funcionamento / loja aberta-fechada no cardápio | Track C1 |
|
||||
| RF-MEN-07 | Áreas de entrega + taxa por bairro no cardápio | Track C1 |
|
||||
| RF-MEN-08 | Tamanhos com preço (`broto`/`grande`/…) | Track M1 |
|
||||
| RF-MEN-09 | Pausar item (manual ou estoque baixo) | Track M1 |
|
||||
| RF-MEN-10 | Ficha técnica (BOM) ligada ao item | Track M1 ↔ EST |
|
||||
| RF-MEN-11 | Bebida `stockMode=sku` 1:1 | Track M1 |
|
||||
| RF-MEN-12 | Vitamina/suco natural via BOM | Track M1 |
|
||||
|
||||
### 7.6 Gestão e métricas (RF-GES)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-GES-01 | Dashboard: pedidos abertos, atrasados, tempo médio | Sim |
|
||||
| RF-GES-02 | Tempo médio por estação | Sim |
|
||||
| RF-GES-03 | Relatório de fluxo do pedido (timestamps) | Sim |
|
||||
| RF-GES-04 | Usuários, papéis, permissões | Sim |
|
||||
| RF-GES-05 | Multi-loja / tenant | Fase 3 |
|
||||
|
||||
### 7.7 Integrações (RF-INT)
|
||||
|
||||
| ID | Requisito | Fase |
|
||||
|----|-----------|------|
|
||||
| RF-INT-01 | Webhook genérico de pedido | Track C1 |
|
||||
| RF-INT-02 | iFood (ou agregador BR) | Track C2 |
|
||||
| RF-INT-03 | Pagamento online (Pix / dinheiro na entrega) | Track C1 |
|
||||
| RF-INT-04 | Impressoras de rede | 1 |
|
||||
| RF-INT-05 | Deep-link WhatsApp → cardápio da loja | Track C1 |
|
||||
|
||||
### 7.8 Canais digitais (RF-CAN)
|
||||
|
||||
| ID | Requisito | Track |
|
||||
|----|-----------|-------|
|
||||
| RF-CAN-01 | PWA cardápio público por loja (`/c/:slug`) | C1 |
|
||||
| RF-CAN-02 | Carrinho cliente → Order com `origin=cardapio` | C1 |
|
||||
| RF-CAN-03 | Escolha fulfillment entrega/retirada no cardápio | C1 |
|
||||
| RF-CAN-04 | Taxa / área de entrega no checkout | C1 |
|
||||
| RF-CAN-05 | Pedido digital no `/pedidos` ≤ 500 ms (WS) | C1 |
|
||||
| RF-CAN-06 | Badge de origem no card (CARDÁPIO / IFOOD / …) | C1 |
|
||||
| RF-CAN-07 | Webhook iFood → normalizador | C2 |
|
||||
| RF-CAN-08 | Idempotência / dedupe de pedidos externos | C2 |
|
||||
|
||||
### 7.9 Estoque (RF-EST)
|
||||
|
||||
> Detalhe: [`docs/SPEC-ESTOQUE.md`](docs/SPEC-ESTOQUE.md) · dispara a partir do Order · pausa o Cardápio
|
||||
|
||||
| ID | Requisito | Track |
|
||||
|----|-----------|-------|
|
||||
| RF-EST-01 | CRUD insumos (unidade, min, custo) | E1 |
|
||||
| RF-EST-02 | Ficha técnica por MenuItem | E1 |
|
||||
| RF-EST-03 | Baixa automática no pedido (`em_producao`) | E1 |
|
||||
| RF-EST-04 | Entrada compra / ajuste manual | E1 |
|
||||
| RF-EST-05 | Alerta estoque mínimo (WS) | E1 |
|
||||
| RF-EST-06 | Pausa automática do item no cardápio | E2 |
|
||||
| RF-EST-07 | Estorno em cancelamento elegível | E1 |
|
||||
| RF-EST-08 | CMV teórico por dia/item | E2 |
|
||||
| RF-EST-13 | Ficha por pizza com qty em **gramas** (38 tipos) | E1 |
|
||||
| RF-EST-14 | Catálogo `fichas-pizzas-peso.json` | E1 |
|
||||
|
||||
**Regra de ouro:** Cardápio declara `stockMode` (`bom`\|`sku`\|`none`); Estoque executa a baixa.
|
||||
**Pizzas:** baixa por **peso (g)** por tipo — [`docs/FICHAS-TECNICAS-PESO.md`](docs/FICHAS-TECNICAS-PESO.md).
|
||||
|
||||
---
|
||||
|
||||
## 8. Requisitos não-funcionais
|
||||
|
||||
| ID | Tema | Requisito |
|
||||
|----|------|-----------|
|
||||
| RNF-01 | Latência KDS | Novo pedido visível na tela ≤ **500 ms** (LAN saudável) |
|
||||
| RNF-02 | Disponibilidade | MVP single-node OK; alvo produção 99.5% |
|
||||
| RNF-03 | Concorrência | Suportar pico de **60 pedidos/hora** no piloto |
|
||||
| RNF-04 | UX cozinha | Touch-first, botões grandes, alto contraste, legível a 1–2 m |
|
||||
| RNF-05 | Segurança | Auth JWT/session; senhas hashed; HTTPS; RBAC |
|
||||
| RNF-06 | Auditoria | Log de mudanças de status com user + timestamp |
|
||||
| RNF-07 | Observabilidade | Logs estruturados + healthcheck |
|
||||
| RNF-08 | Idioma | PT-BR nativo |
|
||||
| RNF-09 | Deploy | Docker Compose local; caminho claro para VPS/Proxmox |
|
||||
| RNF-10 | LGPD | Dados de cliente mínimos; retenção configurável |
|
||||
|
||||
---
|
||||
|
||||
## 9. Arquitetura alvo
|
||||
|
||||
### 9.1 Princípios
|
||||
|
||||
1. **Order bus / schema único** — todo canal vira o mesmo `Order`
|
||||
2. **Event-driven para cozinha** — WebSocket fan-out por loja/estação
|
||||
3. **Station routing** — regras no servidor, não na UI
|
||||
4. **PWA** — KDS e entregador rodam no browser/tablet
|
||||
5. **Começar simples** — 1 processo API + Postgres + Redis; extrair microsserviços só se necessário
|
||||
|
||||
### 9.2 Stack sugerida (do zero)
|
||||
|
||||
| Camada | Tecnologia | Motivo |
|
||||
|--------|------------|--------|
|
||||
| Frontend | React + Vite + TypeScript (PWA) | KDS/PDV touch, ecossistema forte |
|
||||
| Realtime | Socket.io ou native WS | Padrão dos KDS de referência |
|
||||
| API | NestJS ou Fastify (Node/TS) | Tipagem compartilhada, rapidez |
|
||||
| DB | PostgreSQL | Pedidos, auditoria, relatórios |
|
||||
| Cache / pubsub | Redis | Tickets abertos + fan-out |
|
||||
| Filas (fase 2+) | BullMQ | Webhooks, impressão, retries |
|
||||
| Print | ESC/POS (node) + PDF fallback | Etiquetas térmicas |
|
||||
| Infra local | Docker Compose | Postgres + Redis + API + Web |
|
||||
|
||||
Referências de forma (não copy-paste cego):
|
||||
|
||||
- **kitchenOS** → ingestão + KDS + Redis stream
|
||||
- **Tasty-Station** → Socket.io POS↔KDS
|
||||
- **rusterando** → **referência oficial** pizzaria + rotas / ORS / driver tour (modelo, não fork AGPL)
|
||||
- **URY Mosaic** → multi-kitchen KDS
|
||||
|
||||
### 9.3 Apps da plataforma
|
||||
|
||||
| App | URL mental | Usuário |
|
||||
|-----|------------|---------|
|
||||
| `web-pdv` | `/pdv` | Caixa |
|
||||
| `web-kds` | `/kds/:station` | Cozinha |
|
||||
| `web-dispatch` | `/despacho` | Despachante |
|
||||
| `web-admin` | `/admin` | Gestor |
|
||||
| `web-driver` | `/entregador` | Motoboy (fase 2) |
|
||||
| `web-menu` | `/m/:slug` | Cliente (fase 3) |
|
||||
|
||||
Monorepo sugerido:
|
||||
|
||||
```text
|
||||
pizza-digital/
|
||||
├── SPEC.md # este documento
|
||||
├── docs/
|
||||
│ ├── domain.md # glossário e regras
|
||||
│ ├── api.md # contratos HTTP/WS
|
||||
│ └── ui-kds.md # wireframes textuais
|
||||
├── apps/
|
||||
│ ├── web/ # PDV + KDS + Admin (rotas)
|
||||
│ └── api/
|
||||
├── packages/
|
||||
│ ├── domain/ # types Order, Station, events
|
||||
│ └── escpos/ # impressão
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Modelo de domínio (MVP)
|
||||
|
||||
### 10.1 Entidades
|
||||
|
||||
- **Store** — loja
|
||||
- **User** — usuário + role
|
||||
- **Station** — estação KDS
|
||||
- **Category / Product / Modifier** — cardápio ([SPEC-CARDAPIO](docs/SPEC-CARDAPIO.md))
|
||||
- **ProductStationRule** — produto → estações
|
||||
- **Recipe / RecipeLine** — ficha técnica BOM ([SPEC-ESTOQUE](docs/SPEC-ESTOQUE.md))
|
||||
- **Ingredient** — insumo com saldo
|
||||
- **StockMovement** — entrada/saída/ajuste
|
||||
- **Order** — pedido
|
||||
- **OrderItem** — item do pedido (+ modifiers, notes)
|
||||
- **StationTicket** — fatia do item/pedido na estação
|
||||
- **LabelPrintJob** — job de etiqueta
|
||||
- **Courier** (fase 2) — entregador
|
||||
- **DispatchBatch** (fase 2) — lote de rota
|
||||
|
||||
### 10.2 Campos críticos de `Order`
|
||||
|
||||
```text
|
||||
id, store_id, code, channel, type (delivery|pickup|dine_in),
|
||||
status, customer_name, customer_phone, address,
|
||||
notes, priority, promised_at,
|
||||
accepted_at, production_started_at, ready_at,
|
||||
dispatched_at, delivered_at, canceled_at,
|
||||
created_at, updated_at
|
||||
```
|
||||
|
||||
### 10.3 Eventos (WebSocket)
|
||||
|
||||
```text
|
||||
order.created
|
||||
order.updated
|
||||
order.canceled
|
||||
ticket.upserted
|
||||
ticket.bumped
|
||||
order.ready
|
||||
order.dispatched
|
||||
order.delivered
|
||||
label.printed
|
||||
```
|
||||
|
||||
Rooms: `store:{id}`, `store:{id}:station:{stationId}`
|
||||
|
||||
---
|
||||
|
||||
## 11. Regras de negócio — pizzaria
|
||||
|
||||
1. **Meia-a-meia**: 1 pizza = 2 sabores; preço = maior sabor (configurável); aparece no KDS como um item com 2 metades.
|
||||
2. **Borda / adicionais**: modifiers ligados à montagem (ou massa, se borda recheada).
|
||||
3. **Tempo SLA padrão** (editável): Massa 3 min · Montagem 5 · Forno 8 · Expedição 3 — total alvo ~20 min.
|
||||
4. **Pedido delivery sem endereço** não confirma.
|
||||
5. **Bump na expedição** só liberado se estações anteriores concluídas (ou override gestor).
|
||||
6. **Prioridade urgente** não pula forno fisicamente — só sobe na fila visual e alerta.
|
||||
|
||||
---
|
||||
|
||||
## 12. UX — KDS (requisitos de interface)
|
||||
|
||||
- Cards grandes por pedido; código `#A42` enorme
|
||||
- Observação do cliente em **cor de alerta**
|
||||
- Timer no canto; vira amarelo/vermelho por threshold
|
||||
- Botão único: **PRONTO** (item) / **TUDO PRONTO** (estação)
|
||||
- Badge do canal (PDV / iFood / …)
|
||||
- Sem cards decorativos desnecessários — densidade operacional
|
||||
- Funciona em tablet paisagem e TV 32"+
|
||||
|
||||
---
|
||||
|
||||
## 13. Segurança e permissões (RBAC)
|
||||
|
||||
| Recurso | Operador KDS | Expedição | PDV | Despacho | Gestor |
|
||||
|---------|--------------|-----------|-----|----------|--------|
|
||||
| Ver KDS da sua estação | ✓ | ✓ | — | — | ✓ |
|
||||
| Bump tickets | ✓ | ✓ | — | — | ✓ |
|
||||
| Criar pedido | — | — | ✓ | — | ✓ |
|
||||
| Cancelar pedido | — | — | ✓* | — | ✓ |
|
||||
| Despachar | — | ✓ | — | ✓ | ✓ |
|
||||
| Cardápio / users | — | — | — | — | ✓ |
|
||||
| Métricas | — | — | — | leitura | ✓ |
|
||||
|
||||
\*cancelar só antes de produção, salvo permissão.
|
||||
|
||||
---
|
||||
|
||||
## 14. Critérios de aceite do MVP
|
||||
|
||||
O MVP está **aceito** quando, em ambiente piloto:
|
||||
|
||||
1. Atendente cria pedido no PDV com 2 pizzas (1 meia-a-meia) + bebida + observação
|
||||
2. Tickets aparecem nas estações corretas em < 1 s
|
||||
3. Cada estação conclui seus itens; expedição só libera no fim
|
||||
4. Etiqueta é gerada/impressa
|
||||
5. Gestor reordena/prioritiza um pedido pronto
|
||||
6. Dashboard mostra tempos por estação daquele pedido
|
||||
7. Zero uso de papel na cozinha durante o teste supervisionado
|
||||
|
||||
---
|
||||
|
||||
## 15. Sequência oficial de implementação (S0–S9)
|
||||
|
||||
> **Congelado 2026-07-23 (SPEC 0.2.1).**
|
||||
> Ordem: **KDS por estação antes de delivery/rotas.**
|
||||
> Ciclo inspirado no rusterando: `cart/PDV → kitchen board → driver tour → ORS`.
|
||||
> Não forkamos o binário Rust/AGPL; reutilizamos o **modelo operacional** no stack Nest/React.
|
||||
|
||||
### 15.1 Mapa de sprints
|
||||
|
||||
| Sprint | Bloco (rusterando) | Entrega Pizza Digital | Status |
|
||||
|--------|--------------------|------------------------|--------|
|
||||
| **S0** | Foundation | Repo + Docker Compose + domain types + SPEC | ✅ |
|
||||
| **S1** | Auth + menu admin | Auth JWT + Store + Cardápio + mapa item→estações | ✅ (`pizza.myvexx.com`) |
|
||||
| **S2** | Cart → order | **PDV** + criar pedido + state machine `confirmado→em_producao→pronto` | ✅ |
|
||||
| **S3** | Kitchen board | **KDS multi-estação** realtime (bump, timers, rooms Socket.io) | ✅ |
|
||||
| **S4** | Ready / expedição | Etiquetas + fila `pronto` + priorização + dashboard tempos | pendente |
|
||||
| **S5** | Piloto cozinha | 1 pizzaria estável sem papel (aceite §14) | pendente |
|
||||
| **S6** | Driver tour (manual) | Despacho: fila só `pronto` → agrupar bairro → motoboy → reordenar | pendente |
|
||||
| **S7** | **ORS** | Gestor de Rotas: `DispatchBatch` + mapa permanente + heurística; ORS com `ORS_API_KEY` | ✅ parcial (ORS opcional) |
|
||||
| **S8** | Driver + push | `/motoboy` + rota + GPS ping (+ push staff depois) | ✅ parcial (sem PIN/offline) |
|
||||
| **S9 / Track C1** | Public menu / pay | **Cardápio digital** + checkout (Pix / dinheiro) → quadro `/pedidos` | **PRIORIDADE pós-S5** |
|
||||
| **Track C2** | Agregadores | iFood webhook → Order normalizado | após C1 |
|
||||
|
||||
**Gate entre mundos (obrigatório):**
|
||||
`order.ready` → fila despacho → batch → ORS *(só a partir de S6/S7)*.
|
||||
|
||||
### 15.2 Módulo Rotas (CityFoods → Pizza Digital)
|
||||
|
||||
**Referência oficial de dispatch:** [rusterando](https://github.com/holg/rusterando) (AGPL-3.0).
|
||||
|
||||
| Subfase | Capacidade | Sprint |
|
||||
|---------|------------|--------|
|
||||
| MVP rotas | Fila só pedidos `pronto` | S6 |
|
||||
| MVP rotas | Agrupar por bairro (manual + sugestão) | S6 |
|
||||
| MVP rotas | Atribuir motoboy | S6 |
|
||||
| MVP rotas | Reordenar saída (“2 cliques” CityFoods) | S6 |
|
||||
| Ampliação | OpenRouteService — ordem ótima da tour | S7 |
|
||||
| Ampliação | Mapa no despacho | S7 |
|
||||
| Ampliação | PWA entregador + tracking | S8 |
|
||||
| Ampliação | Pagamento / comissão por entrega | S8–S9 |
|
||||
|
||||
Papéis rusterando → Pizza Digital: Admin≈gestor · Kitchen≈kds/estações · Driver≈entregador · Customer≈canal cardápio (S9).
|
||||
|
||||
### 15.3 Referências por domínio
|
||||
|
||||
| Domínio | Fonte |
|
||||
|---------|--------|
|
||||
| KDS / estações / bump | Este SPEC + kitchenOS / Tasty-Station |
|
||||
| Despacho / ORS / driver tour | **rusterando** (oficial) + CityFoods Gestor de Rotas |
|
||||
| UX “2 cliques” priorização | CityFoods |
|
||||
|
||||
### 15.4 Design UI
|
||||
|
||||
Status: **ainda não congelado.** S1 tem UI funcional (login + admin cardápio).
|
||||
Antes de S3: wireframes PDV → KDS (touch) → Despacho. Design system em documento separado (`docs/DESIGN.md`) quando existir.
|
||||
|
||||
---
|
||||
|
||||
## 16. Riscos
|
||||
|
||||
| Risco | Mitigação |
|
||||
|-------|-----------|
|
||||
| Cozinha rejeita tela (hábito do papel) | UX bump mínima + treinamento 1 turno + impressão híbrida opcional |
|
||||
| Impressora térmica incompatível | Driver ESC/POS + fallback PDF/A4 |
|
||||
| Pico de pedidos derruba WS | Redis pubsub + rooms por loja; load test 60/h |
|
||||
| Escopo inchando (virar iFood) | Freeze: MVP = cozinha digital, não marketplace |
|
||||
| Integração iFood cedo demais | Só após piloto estável |
|
||||
|
||||
---
|
||||
|
||||
## 17. Glossário
|
||||
|
||||
| Termo | Significado |
|
||||
|-------|-------------|
|
||||
| **KDS** | Kitchen Display System — tela digital da cozinha |
|
||||
| **Bump** | Marcar item/pedido como pronto na estação |
|
||||
| **Estação** | Setor de produção (massa, forno, …) |
|
||||
| **Ticket** | Representação do trabalho de um item/pedido numa estação |
|
||||
| **SLA** | Tempo alvo até alerta de atraso |
|
||||
| **Despacho** | Liberação do pedido pronto para entregador |
|
||||
| **Canal** | Origem do pedido (PDV, cardápio, iFood, …) |
|
||||
| **Cozinha Digital** | Operação completa sem comanda de papel + métricas |
|
||||
| **BOM / Ficha técnica** | Lista de insumos por item do cardápio |
|
||||
| **stockMode** | `bom` · `sku` · `none` — como o item consome estoque |
|
||||
| **CMV** | Custo da mercadoria vendida (teórico via BOM) |
|
||||
|
||||
---
|
||||
|
||||
## 18. Decisões em aberto (para fechar com Roger)
|
||||
|
||||
1. Stack final: **NestJS** ✅ (congelado Fase 1)
|
||||
2. Piloto: qual pizzaria / volume médio de pedidos/dia?
|
||||
3. Impressora térmica já definida (marca/modelo)?
|
||||
4. MVP inclui **mesa/comanda salão** ou só delivery + balcão?
|
||||
5. Hospedagem inicial → **Proxmox BIG VM125** (`10.10.10.125`) ✅
|
||||
6. Domínio → **`https://pizza.myvexx.com`** ✅
|
||||
7. Sequência S0–S9 + rusterando = ref. rotas → **congelado §15** ✅
|
||||
8. Design UI/UX (wireframes KDS/PDV/despacho) — em aberto
|
||||
|
||||
---
|
||||
|
||||
## 19. Referências
|
||||
|
||||
- CityFoods Cozinha Digital — https://abre.ai/cityfoods / https://cityfoods.com.br/
|
||||
- CityFoods Instagram — https://www.instagram.com/cityfoods_app/
|
||||
- kitchenOS — https://github.com/rajeshselvam02/kitchenOS
|
||||
- rusterando (**ref. oficial dispatch/ORS**) — https://github.com/holg/rusterando
|
||||
- URY / Mosaic — https://github.com/ury-erp/ury
|
||||
- MaSoVa — https://github.com/SVamseekar/masova-platform
|
||||
- Tasty-Station POS — https://github.com/hey-Zayn/Tasty-Station-POS
|
||||
|
||||
---
|
||||
|
||||
## 20. Próximo passo imediato
|
||||
|
||||
1. **S4** — Etiquetas + priorização + dashboard tempos (fechar cozinha)
|
||||
2. **S5** — Piloto pizzaria (cozinha estável)
|
||||
3. **Track C1 — entrada digital** — cardápio público → `/pedidos` (§5.5) ← **foco de produto**
|
||||
4. **Track E1 / M1** — Estoque + ficha técnica ligados ao cardápio Joinville
|
||||
5. Em paralelo ou depois: S6–S8 (rotas) · Track C2 (iFood)
|
||||
|
||||
---
|
||||
|
||||
*Documento vivo. Mudanças de escopo devem atualizar versão (ex.: 0.2.0 → 0.2.1) e registrar changelog em `docs/CHANGELOG-SPEC.md`.*
|
||||
40
SYNC-PIZZA-DIGITAL.md
Normal file
40
SYNC-PIZZA-DIGITAL.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Sync — Pizza Digital ↔ Obsidian
|
||||
|
||||
**Última sync:** 2026-11-09T19:44:42Z
|
||||
**Direção padrão:** VM125 → Vault (pull) · Vault → VM125 (push)
|
||||
|
||||
## Mapa
|
||||
|
||||
| Origem (VM125) | Destino (CT130 vault) |
|
||||
|----------------|------------------------|
|
||||
| `/home/mini/pizza-digital/SPEC.md` | `Pizza-Digital/SPEC.md` |
|
||||
| `/home/mini/pizza-digital/README.md` | `Pizza-Digital/README.md` |
|
||||
| `/home/mini/pizza-digital/docs/**` | `Pizza-Digital/docs/**` |
|
||||
|
||||
## Comandos
|
||||
|
||||
No **CT130** (hub):
|
||||
|
||||
```bash
|
||||
# VM → Vault
|
||||
/opt/ligbox-spec-hub/scripts/sync-pizza-digital.sh pull
|
||||
|
||||
# Vault → VM
|
||||
/opt/ligbox-spec-hub/scripts/sync-pizza-digital.sh push
|
||||
```
|
||||
|
||||
Na **VM125**:
|
||||
|
||||
```bash
|
||||
# Vault → VM (atualiza docs locais a partir do Obsidian)
|
||||
~/pizza-digital/scripts/sync_obsidian.sh pull
|
||||
|
||||
# VM → Vault
|
||||
~/pizza-digital/scripts/sync_obsidian.sh push
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- SSH: CT130 `root` → `mini@10.10.10.125` (chave `id_ed25519` spec-hub)
|
||||
- Não sincroniza código (`apps/`, `packages/`, `.env`) — só documentação
|
||||
- Após pull no vault, fazer commit no repo `obsidian-vault` se quiser versionar
|
||||
135
docs/API-WHATSAPP-INBOUND.md
Normal file
135
docs/API-WHATSAPP-INBOUND.md
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# API de entrada — WhatsApp
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| Versão | 0.1 |
|
||||
| Data | 2026-10-19 |
|
||||
| Origin no pedido | `whatsapp` |
|
||||
| Badge na Central | **WHATSAPP** |
|
||||
|
||||
Primeira API de canal digital: qualquer bot / n8n / Make / Evolution API envia JSON autenticado e o pedido cai em `/pedidos` com a mesma fila do PDV.
|
||||
|
||||
---
|
||||
|
||||
## 1. Auth
|
||||
|
||||
Header obrigatório:
|
||||
|
||||
```http
|
||||
X-Api-Key: <INBOUND_API_KEY>
|
||||
```
|
||||
|
||||
(ou `Authorization: Bearer <INBOUND_API_KEY>`)
|
||||
|
||||
Variável no servidor: `INBOUND_API_KEY` (ver `.env.example`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Criar pedido (recomendado)
|
||||
|
||||
```http
|
||||
POST /api/inbound/whatsapp
|
||||
Content-Type: application/json
|
||||
X-Api-Key: pizza-inbound-805353
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"storeSlug": "pizza-digital-piloto",
|
||||
"externalId": "wamid.HBgLNTU0Nzk5ODg3NzY2FQIAEhggEXAMPLE",
|
||||
"customerName": "Ana Silva Santos",
|
||||
"customerPhone": "5547999887766",
|
||||
"fulfillment": "entrega",
|
||||
"bairro": "Centro",
|
||||
"address": "Rua das Palmeiras, 120",
|
||||
"notes": "sem cebola",
|
||||
"items": [
|
||||
{ "name": "Calabresa", "quantity": 1, "sizeCode": "grande" },
|
||||
{ "name": "Coca-Cola 2L", "quantity": 1 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Obrigatório | Notas |
|
||||
|-------|-------------|--------|
|
||||
| `storeSlug` | sim | slug da loja |
|
||||
| `externalId` | recomendado | idempotência (`wa:<id>`) — reenvio não duplica |
|
||||
| `customerName` | sim | nome completo |
|
||||
| `customerPhone` | sim | WhatsApp do cliente |
|
||||
| `fulfillment` | não | `entrega` (default) · `retirada` · `mesa` |
|
||||
| `bairro` / `address` | entrega | ao menos um |
|
||||
| `items[].menuItemId` | um dos dois | id do menu |
|
||||
| `items[].name` | um dos dois | match fuzzy no cardápio |
|
||||
| `items[].sizeCode` | não | `broto`…`familia` (preço × fator) |
|
||||
|
||||
Resposta: mesmo payload de `POST /api/orders` (code, items, origin=`whatsapp`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Webhook Meta Cloud API (opcional)
|
||||
|
||||
| Método | Rota | Uso |
|
||||
|--------|------|-----|
|
||||
| `GET` | `/api/inbound/whatsapp/meta` | Verificação (`WHATSAPP_VERIFY_TOKEN`) |
|
||||
| `POST` | `/api/inbound/whatsapp/meta` | Eventos de mensagem |
|
||||
|
||||
Configure no Meta:
|
||||
- Callback URL: `https://<host>/api/inbound/whatsapp/meta`
|
||||
- Verify token: valor de `WHATSAPP_VERIFY_TOKEN`
|
||||
- Loja default: `WHATSAPP_STORE_SLUG`
|
||||
|
||||
### Formato de texto aceito no chat
|
||||
|
||||
```text
|
||||
PEDIDO
|
||||
Nome: Ana Silva
|
||||
Telefone: 47999887766
|
||||
Entrega
|
||||
Bairro: Centro
|
||||
Endereço: Rua das Palmeiras, 120
|
||||
1x Calabresa
|
||||
1x Coca-Cola 2L
|
||||
Obs: sem cebola
|
||||
```
|
||||
|
||||
Mensagens que não começam com `PEDIDO` / `#PEDIDO` são ignoradas (200 OK, sem criar pedido).
|
||||
|
||||
---
|
||||
|
||||
## 4. Fluxo típico com n8n
|
||||
|
||||
```text
|
||||
WhatsApp (Evolution / Meta)
|
||||
→ n8n parseia intenção / menu
|
||||
→ POST /api/inbound/whatsapp (JSON estruturado)
|
||||
→ Central /pedidos (badge WHATSAPP + som)
|
||||
→ KDS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Smoke test
|
||||
|
||||
```bash
|
||||
curl -sS -X POST https://pizza.myvexx.com/api/inbound/whatsapp \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-Api-Key: pizza-inbound-805353" \
|
||||
-d '{
|
||||
"storeSlug":"pizza-digital-piloto",
|
||||
"externalId":"test-wa-001",
|
||||
"customerName":"Ana Silva Santos",
|
||||
"customerPhone":"47999887766",
|
||||
"fulfillment":"entrega",
|
||||
"bairro":"Centro",
|
||||
"address":"Rua Teste, 1",
|
||||
"items":[{"name":"Calabresa","quantity":1,"sizeCode":"grande"}]
|
||||
}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Próximos passos
|
||||
|
||||
- Validar `X-Hub-Signature-256` do Meta
|
||||
- Resposta automática ao cliente (pedido #code + tracking)
|
||||
- Catálogo rápido via botões WhatsApp (interactive messages)
|
||||
67
docs/CARDAPIO-JOINVILLE.md
Normal file
67
docs/CARDAPIO-JOINVILLE.md
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# Cardápio Joinville — pesquisa e modelo
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| Região | Joinville / SC |
|
||||
| Data | 2026-10-19 |
|
||||
| Fontes | Super Pizza, Tempero Napolitano, Positano, Casa di Francisca, Gullas, A Brasileira, Lisboa |
|
||||
| **SPEC Cardápio** | [`SPEC-CARDAPIO.md`](SPEC-CARDAPIO.md) |
|
||||
| **SPEC Estoque** | [`SPEC-ESTOQUE.md`](SPEC-ESTOQUE.md) (BOM / baixas) |
|
||||
|
||||
## Tamanhos Pizza Digital (piloto)
|
||||
|
||||
| Tamanho | Diâmetro | Fatias | ≈ pratos | Sabores máx. | Papel comercial |
|
||||
|---------|----------|--------|----------|--------------|-----------------|
|
||||
| **Broto** | 25 cm | 4 | 1 | **1** | Individual |
|
||||
| **Pequena** | 30 cm | 6 | 1,5 | **1** | 1–2 pessoas |
|
||||
| **Média** | 35 cm | 8 | 2 | **2** | Casal / split |
|
||||
| **Grande** | 40 cm | 12 | 2,5 | **3** | Mais vendido |
|
||||
| **Gigante** | 45 cm | 16 | 3 | **4** | Família grande |
|
||||
| **Família** | 50 cm | 20 | 3,5 | **4** | Máximo |
|
||||
|
||||
**Split:** preço = maior sabor × fator do tamanho. Escolha no PDV ao montar o pedido.
|
||||
|
||||
### Referência de mercado Joinville (pesquisa)
|
||||
|
||||
| Tamanho | Diâmetro | Fatias | Sabores típicos | Papel comercial |
|
||||
|---------|----------|--------|-----------------|-----------------|
|
||||
| **Broto** | 25 cm | 4–6 | 1 | Individual / doce |
|
||||
| Pequena / Média | 30 cm | 8 | 1–2 | Casal |
|
||||
| Grande / Família* | 35–40 cm | 10–12 | 2–3 | **Mais vendido** |
|
||||
| Gigante | 45 cm | 16 | 3–4 | Família grande |
|
||||
|
||||
\*Nomenclatura varia: em algumas casas “Grande” = 35 cm e “Família” = 40 cm; em outras “Grande” = 40 cm.
|
||||
|
||||
**Padrão Pizza Digital (piloto):** preço base = **Grande 40 cm / 12 fatias**. Tamanho e split escolhidos no pedido (não há SKU Broto separado no cardápio ativo).
|
||||
|
||||
## Sabores mais comercializados (frequência nos cardápios)
|
||||
|
||||
### Tradicionais (alta rotação)
|
||||
Calabresa · Mussarela · Frango Catupiry · Portuguesa · 4 Queijos · Marguerita · Bacon · Pepperoni · Lombo Catupiry · Baiana · Napolitana · Presunto · Palmito · Brócolis · Toscana · Moda da Casa · Atum · Crocante · Estrogonofe de Frango · Vegetariana
|
||||
|
||||
### Regionais / SC (diferencial Joinville)
|
||||
- **Joinville** — mussarela, alho, parmesão, pimentão
|
||||
- **Linguiça Blumenau** — mussarela, linguiça Blumenau, catupiry
|
||||
- **Aipim com Bacon** — purê de aipim, bacon
|
||||
- **Catarina** — calabresa ralada, ovos, cebola
|
||||
|
||||
### Doces (pedido frequente pós-salgada / broto)
|
||||
Chocolate preto · Chocolate branco · Banana c/ canela · Prestígio · Romeu e Julieta · Morango c/ chocolate
|
||||
|
||||
## Faixa de preço observada (2024–2026)
|
||||
|
||||
- Broto: ~R$ 39–56
|
||||
- Grande/Família 40 cm: ~R$ 66–96 (tradicionais)
|
||||
- Gigante 45 cm: ~R$ 90–102+
|
||||
- Borda Catupiry/Cheddar: ~R$ 12–20
|
||||
|
||||
Preços no seed = média competitiva Joinville (não cópia de um cardápio).
|
||||
|
||||
## Acompanhamentos — bebidas
|
||||
|
||||
| Categoria | Itens |
|
||||
|-----------|--------|
|
||||
| **Refrigerantes** | Coca / Zero, Guaraná / Zero, Pepsi, Fanta, Sprite, Sukita · 2L, 1,5L, 600ml, lata · água c/ e s/ gás |
|
||||
| **Sucos** | Del Valle (laranja, uva, pêssego, abacaxi, maracujá, manga, goiaba) + naturais feitos na hora |
|
||||
| **Energéticos** | Red Bull, Monster, TNT, Fusion, Burn, Flying Horse |
|
||||
| **Vitaminas** | Leite + gelo + base de frutas (banana, morango, abacate, mamão, manga, açaí, maracujá, coco, mistas, c/ ovo, c/ aveia) |
|
||||
113
docs/CHANGELOG-SPEC.md
Normal file
113
docs/CHANGELOG-SPEC.md
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
# Changelog — SPEC Pizza Digital
|
||||
|
||||
## 0.6.0 — 2026-10-22
|
||||
|
||||
- **Demo Operação:** [`SPEC-SIM-OPS-DEMO.md`](SPEC-SIM-OPS-DEMO.md) · UI `/sim` · API `/api/sim/*`
|
||||
- Motor: entrada Aplicativo/WhatsApp/Telefone · KDS pizzaiolos · estufa · rotas · ETA entrega
|
||||
- 15 motoboys SIM · até 2000 ops/dia · prep cap 165 min · Joinville · Limpar dados teste
|
||||
|
||||
## 0.5.0 — 2026-10-22
|
||||
|
||||
- **Paridade 3 vídeos CityFoods:** [`SPEC-CITYFOODS-VIDEO-PARITY.md`](SPEC-CITYFOODS-VIDEO-PARITY.md) **v0.1.0**
|
||||
- Gap: mapa full-bleed, CTA **Ir para entrega**, rastreio multi-motoboy, pin some ao entregar, filtro forno por sabor
|
||||
- UI manda sobre painel lateral do Gestor de Rotas (dados/API `/api/routes/*` permanecem)
|
||||
- P0 desta release: rebuild `/despacho` mapa-first (RF-VID-01…08)
|
||||
|
||||
## 0.4.0 — 2026-10-22
|
||||
|
||||
- **Gestor de Rotas (CityFoods):** [`SPEC-DESPACHO-CITYFOODS-PARITY.md`](SPEC-DESPACHO-CITYFOODS-PARITY.md) **v0.2.0**
|
||||
- Fluxo: Montar rota → Despachar → mapa permanente → Motoboy (a caminho/entregue) → fechar
|
||||
- Dados: `DispatchBatch` / `DispatchStop` · `Order.batchId` · ping `Driver.lastLat/Lng`
|
||||
- API `/api/routes/*` · UI `/despacho` + `/motoboy` · SPEC mãe §15 S7/S8 → ✅ parcial
|
||||
- P0–P2 despacho/estufa/frota/zonas/ops documentados no baseline; próximo = iFood (P3)
|
||||
|
||||
## 0.3.4 — 2026-10-19
|
||||
|
||||
- Origem **Cardápio** renomeada na UI para **Aplicativo** (enum interno `cardapio`)
|
||||
- **API WhatsApp inbound v0.1:** `POST /api/inbound/whatsapp` + webhook Meta `GET/POST …/meta`
|
||||
- Auth `X-Api-Key` (`INBOUND_API_KEY`) · idempotência `Order.inboundKey`
|
||||
- Doc: [`API-WHATSAPP-INBOUND.md`](API-WHATSAPP-INBOUND.md)
|
||||
|
||||
## 0.3.3 — 2026-10-19
|
||||
|
||||
- **Tamanhos + split no pedido:** Broto / Pequena / Média / Grande / Gigante / Família
|
||||
- Regras: Broto+Pequena = 1 sabor · Média ≤2 · Grande ≤3 · Gigante+Família ≤4
|
||||
- cm / fatias / referência em pratos de mesa · `GET /api/menu/sizes` · sheet PDV
|
||||
- Preço = maior sabor × fator do tamanho (base = Grande 40cm)
|
||||
- Seed: sabores sem sufixo G; SKUs Broto legados desativados
|
||||
|
||||
## 0.3.2 — 2026-10-19
|
||||
|
||||
- **Matriz de Acesso MVP:** [`SPEC-MATRIZ-ACESSO.md`](SPEC-MATRIZ-ACESSO.md)
|
||||
- UI `/acessos` · API `/api/access/*` · templates ≠ vínculos efetivos
|
||||
- Caps: `menu.*` · `staff.*` · `permissions.manage` · `owners.manage` · `finance.view`
|
||||
- Auditoria proposta `AccessAuditLog` · migração `20261019190000_access_matrix`
|
||||
|
||||
## 0.3.1 — 2026-10-19
|
||||
|
||||
- SPECs irmãs: [`SPEC-CARDAPIO.md`](SPEC-CARDAPIO.md) ↔ [`SPEC-ESTOQUE.md`](SPEC-ESTOQUE.md)
|
||||
- Ponte obrigatória: `stockMode` (`bom`/`sku`/`none`) + ficha técnica (BOM)
|
||||
- RF-MEN-08…12 · **RF-EST-01…08** (Track M/E)
|
||||
- Domínio: Recipe, Ingredient, StockMovement
|
||||
- Pesquisa regional mantida em [`CARDAPIO-JOINVILLE.md`](CARDAPIO-JOINVILLE.md)
|
||||
- **Quantização por peso:** 38 pizzas com BOM em gramas — [`FICHAS-TECNICAS-PESO.md`](FICHAS-TECNICAS-PESO.md) + [`data/fichas-pizzas-peso.json`](data/fichas-pizzas-peso.json) · RF-EST-13…15 · RF-MEN-15
|
||||
|
||||
## 0.3.0 — 2026-10-19
|
||||
|
||||
- **Prioridade de produto:** entrada por canais digitais (pós-piloto S5)
|
||||
- Novo capítulo **§5.5** — origin × fulfillment, quadro único `/pedidos`, Track C1/C2
|
||||
- Documento satélite [`docs/SPEC-CANAIS-DIGITAIS.md`](SPEC-CANAIS-DIGITAIS.md)
|
||||
- RFs: RF-CAN-01…08 · RF-PED-08/09 · RF-MEN-04/06/07 · RF-INT-01/03/05
|
||||
- Fase 3 e S9 alinhados a Track C (cardápio → `/pedidos`; iFood = C2)
|
||||
- Fluxo §6.1: canais digitais + PDV → Order Ingest → `/pedidos` → KDS
|
||||
- **Implementado (Central):** `Order.origin`/`fulfillment`, badges + filtros + som no `/pedidos`, `POST /api/orders/demo-digital`
|
||||
|
||||
## 0.2.4 — 2026-07-23
|
||||
|
||||
- **KDS claim exclusivo (CityFoods):** fila comum → PEGAR → outras estações bloqueadas
|
||||
- API: `POST /api/kds/:station/claim/:orderId`, `POST /api/kds/:station/orders/:orderId/bump`
|
||||
- Campos: `Order.claimedStationId` / `claimedAt`
|
||||
- UI: secções Fila / Comigo / Bloqueados
|
||||
|
||||
## 0.2.3 — 2026-07-23
|
||||
|
||||
- **S3 entregue:** KDS multi-estação realtime
|
||||
- API: `GET /api/kds/:station`, `POST /api/kds/tickets/:id/bump`
|
||||
- Expedição bloqueada até outras estações concluírem
|
||||
- Auto `order.ready` quando todos os tickets fecham
|
||||
- UI `/kds/:station` + Socket.io (`ticket.bumped`, timers SLA)
|
||||
|
||||
## 0.2.2 — 2026-07-23
|
||||
|
||||
- **S2 entregue:** PDV + Order/OrderItem/OrderTicket + state machine
|
||||
- API: `POST/GET /api/orders`, `PATCH .../status`, `PATCH .../urgent`
|
||||
- WS: `order.created` / `order.updated` / `order.ready` / `order.canceled`
|
||||
- Gate: `em_producao → pronto` gera `order.ready` (ponte despacho)
|
||||
- UI PDV em `https://pizza.myvexx.com/pdv`
|
||||
|
||||
## 0.2.1 — 2026-07-23
|
||||
|
||||
- Sequência oficial **S0–S9** congelada (§15)
|
||||
- Prioridade: **KDS multi-estação antes** de delivery/rotas
|
||||
- **rusterando** marcado como referência oficial de dispatch / ORS / driver tour
|
||||
- Capítulo §15.2 Módulo Rotas (CityFoods → Pizza Digital)
|
||||
- Gate: `order.ready` → fila despacho → batch → ORS (S6+)
|
||||
- Design UI: explícito como ainda não congelado (§15.4)
|
||||
- S1 marcado ✅; próximo = S2 (PDV)
|
||||
|
||||
## 0.2.0 — 2026-07-23
|
||||
|
||||
- Ampliação Fase 1 (`docs/SPEC-FASE1.md`)
|
||||
- Infra: VM 125 `pizza-digital` no Proxmox BIG (`10.10.10.125`)
|
||||
- DNS: `pizza.myvexx.com` → CNAME tunnel Cloudflare (proxied)
|
||||
- Traefik CT114: `pizza.myvexx.com` → `http://10.10.10.125:3000`
|
||||
- Stack Fase 1 congelada (NestJS + React + Postgres + Redis + Socket.io)
|
||||
- Host de desenvolvimento documentado no SPEC principal
|
||||
|
||||
## 0.1.0 — 2026-07-23
|
||||
|
||||
- Draft inicial do zero
|
||||
- Baseado na Cozinha Digital CityFoods (`@cityfoods_app`)
|
||||
- Referências: kitchenOS, rusterando, URY Mosaic, MaSoVa, Tasty-Station
|
||||
- Escopo MVP: PDV + KDS multi-estação (pizzaria) + etiquetas + métricas
|
||||
- Fases 2–3: rotas, cardápio público, iFood, multi-tenant
|
||||
111
docs/FICHAS-TECNICAS-PESO.md
Normal file
111
docs/FICHAS-TECNICAS-PESO.md
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
# Fichas técnicas — quantização por **peso (g)**
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Fonte máquina** | [`data/fichas-pizzas-peso.json`](data/fichas-pizzas-peso.json) |
|
||||
| **SPEC** | [`SPEC-ESTOQUE.md`](SPEC-ESTOQUE.md) §4 · [`SPEC-CARDAPIO.md`](SPEC-CARDAPIO.md) |
|
||||
| **Versão** | 0.1.0 · 2026-10-19 |
|
||||
| **Pizzas cobertas** | **38** (20 trad. G + 5 broto + 6 especiais + 7 doces) |
|
||||
|
||||
---
|
||||
|
||||
## 1. Regra de quantização
|
||||
|
||||
```text
|
||||
baixa_insumo_g = RecipeLine.qty_g × OrderItem.quantity
|
||||
meia-a-meia = 0,5 × BOM_sabor_A + 0,5 × BOM_sabor_B
|
||||
borda = BOM própria (catupiry/cheddar) somada à pizza
|
||||
```
|
||||
|
||||
- Unidade canônica dos toppings/massa: **gramas (g)**
|
||||
- Ovo: **un** (exceção)
|
||||
- Bebidas continuam `stockMode=sku` (não entram neste catálogo)
|
||||
|
||||
---
|
||||
|
||||
## 2. Base por tamanho
|
||||
|
||||
| Tamanho | Massa | Molho | Mussarela base | Fator vs G |
|
||||
|---------|------:|------:|---------------:|-----------:|
|
||||
| **Grande 40 cm** | 480 g | 90 g | 200 g | 1,00 |
|
||||
| **Broto 25 cm** | 200 g | 40 g | 85 g | ≈ 0,42 |
|
||||
|
||||
Peso total aproximado da pizza montada (antes do forno): **~770–1050 g (G)** · **~320–430 g (Broto)**.
|
||||
|
||||
---
|
||||
|
||||
## 3. Resumo — Tradicionais Grande (20)
|
||||
|
||||
| # | Pizza | Peso ≈ | Massa | Mussarela | Topping principal |
|
||||
|---|-------|-------:|------:|----------:|-------------------|
|
||||
| 1 | Calabresa G | 930 g | 480 | 200 | calabresa 120 g + cebola 40 g |
|
||||
| 2 | Mussarela G | 772 g | 480 | 200 | — |
|
||||
| 3 | Frango Catupiry G | 992 g | 480 | 180 | frango 150 + catupiry 90 |
|
||||
| 4 | Portuguesa G | 1012 g | 480 | 180 | presunto 100 + 2 ovos + cebola/azeitona |
|
||||
| 5 | 4 Queijos G | 912 g | 480 | 120 | provolone 80 + gorgonzola 60 + catupiry 100 |
|
||||
| 6 | Marguerita G | 852 g | 480 | 180 | tomate 80 + parmesão 20 + manjericão 8 |
|
||||
| 7 | Bacon G | 892 g | 480 | 200 | bacon 120 |
|
||||
| 8 | Pepperoni G | 922 g | 480 | 200 | pepperoni 120 + pimentão 30 |
|
||||
| 9 | Lombo Catupiry G | 962 g | 480 | 180 | lombo 120 + catupiry 90 |
|
||||
| 10 | Baiana G | 902 g | 480 | 180 | calabresa moída 130 + pimenta 20 |
|
||||
| 11 | Napolitana G | 842 g | 480 | 180 | tomate 70 + parmesão 20 |
|
||||
| 12 | Presunto G | 872 g | 480 | 200 | presunto 100 |
|
||||
| 13 | Palmito G | 892 g | 480 | 200 | palmito 120 |
|
||||
| 14 | Brócolis G | 942 g | 480 | 180 | brócolis 120 + bacon 70 |
|
||||
| 15 | Toscana G | 922 g | 480 | 180 | calabresa moída 130 + cebola 40 |
|
||||
| 16 | Moda da Casa G | 1002 g | 480 | 180 | presunto+tomate+milho+ervilha |
|
||||
| 17 | Atum G | 932 g | 480 | 180 | atum 140 + cebola 40 |
|
||||
| 18 | Crocante G | 922 g | 480 | 200 | bacon 100 + batata palha 50 |
|
||||
| 19 | Estrogonofe Frango G | 1002 g | 480 | 160 | estrogonofe 250 + batata palha 50 |
|
||||
| 20 | Vegetariana G | 972 g | 480 | 180 | brócolis+palmito+milho+ervilha |
|
||||
|
||||
## 4. Broto (5) · Especiais SC (6) · Doces (7)
|
||||
|
||||
Detalhe linha a linha no JSON. Destaques especiais Joinville:
|
||||
|
||||
| Pizza | Peso ≈ | Destaque (g) |
|
||||
|-------|-------:|--------------|
|
||||
| Joinville G | 852 | alho 20 · parmesão 30 · pimentão 40 |
|
||||
| Linguiça Blumenau G | 982 | linguiça 140 · catupiry 90 |
|
||||
| Aipim c/ Bacon G | 1012 | aipim 200 · bacon 120 |
|
||||
| Catarina G | 942 | calabresa moída 120 · 2 ovos |
|
||||
| Aipim Blumenau G | 1052 | aipim 200 · linguiça 140 |
|
||||
| Coração G | 972 | coração 140 · catupiry 80 |
|
||||
|
||||
---
|
||||
|
||||
## 5. Exemplo de baixa no pedido
|
||||
|
||||
Pedido: **1× Calabresa G + 1× Coca 2L**
|
||||
|
||||
| Insumo | Movimento |
|
||||
|--------|-----------|
|
||||
| Massa | −480 g |
|
||||
| Molho | −90 g |
|
||||
| Mussarela | −200 g |
|
||||
| Calabresa | −120 g |
|
||||
| Cebola | −40 g |
|
||||
| Orégano | −2 g |
|
||||
| Coca 2L (sku) | −1 un |
|
||||
|
||||
Meia-a-meia **Calabresa / Frango Catupiry** (1 pizza G):
|
||||
|
||||
| Insumo | Cálculo |
|
||||
|--------|---------|
|
||||
| Massa / molho | 100% da base G (1 disco) |
|
||||
| Mussarela | 0,5×200 + 0,5×180 = **190 g** |
|
||||
| Calabresa | 0,5×120 = **60 g** |
|
||||
| Frango | 0,5×150 = **75 g** |
|
||||
| Catupiry | 0,5×90 = **45 g** |
|
||||
| Cebola | 0,5×40 = **20 g** |
|
||||
|
||||
---
|
||||
|
||||
## 6. Próximo passo E1
|
||||
|
||||
1. Importar `ingredientsCatalog` → tabela `Ingredient`
|
||||
2. Importar `recipes[]` → `Recipe` + `RecipeLine`
|
||||
3. Ligar `menuItemId` do seed
|
||||
4. Baixa automática em `em_producao`
|
||||
|
||||
Pesos são **padrão piloto Joinville** — ajustáveis por loja na UI de ficha técnica.
|
||||
38
docs/MIGRATION-ACCESS-MATRIX.md
Normal file
38
docs/MIGRATION-ACCESS-MATRIX.md
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Migração — Matriz de Acesso
|
||||
|
||||
**Não executar em produção sem revisão.**
|
||||
|
||||
## Lab / VM125
|
||||
|
||||
```bash
|
||||
cd ~/pizza-digital
|
||||
# 1. Atualizar código (já inclui prisma/migrations/20261019190000_access_matrix)
|
||||
docker compose build api web
|
||||
docker compose up -d api web
|
||||
# entrypoint já roda: prisma migrate deploy + seed
|
||||
docker logs pizza-api --tail 40
|
||||
```
|
||||
|
||||
## Verificação
|
||||
|
||||
```bash
|
||||
# login gestor
|
||||
curl -s -X POST http://127.0.0.1:3000/api/auth/login \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"email":"gestor@pizza.local","password":"805353","storeSlug":"pizza-digital-piloto"}'
|
||||
|
||||
# matriz (Bearer token)
|
||||
curl -s http://127.0.0.1:3000/api/access/matrix -H "Authorization: Bearer $TOKEN"
|
||||
|
||||
# pdv não deve salvar matriz (403)
|
||||
```
|
||||
|
||||
## Rollback (lab)
|
||||
|
||||
```sql
|
||||
DROP TABLE IF EXISTS "AccessAuditLog";
|
||||
DROP TABLE IF EXISTS "AccessProfileTemplate";
|
||||
DROP TABLE IF EXISTS "UserCapability";
|
||||
ALTER TABLE "User" DROP COLUMN IF EXISTS "profileTemplateKey";
|
||||
DELETE FROM "_prisma_migrations" WHERE migration_name = '20261019190000_access_matrix';
|
||||
```
|
||||
161
docs/PROMPT-CENTRAL-PEDIDOS.md
Normal file
161
docs/PROMPT-CENTRAL-PEDIDOS.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# Context Prompt — Central de Pedidos (Cursor)
|
||||
|
||||
> Cole o bloco **PROMPT** abaixo num chat Cursor (Agent) no repo `pizza-digital`.
|
||||
> Spec: `SPEC.md` §5.5 · `docs/SPEC-CANAIS-DIGITAIS.md` · RF-PED / RF-CAN
|
||||
> Código base: `apps/web/src/PedidosPage.tsx` · rota `/pedidos`
|
||||
|
||||
---
|
||||
|
||||
## PROMPT (copiar daqui)
|
||||
|
||||
```text
|
||||
# Missão
|
||||
Construir / evoluir o módulo **Central de Pedidos** do produto **Pizza Digital**
|
||||
(rota `/pedidos`) — o quadro operacional único da loja, estilo CityFoods.
|
||||
|
||||
PT-BR em toda UI e comentários de produto. Código TS em inglês.
|
||||
|
||||
# Contexto do produto
|
||||
Pizza Digital = SO da cozinha + delivery próprio (não é marketplace).
|
||||
Referência UX: CityFoods — Cozinha Digital + quadro Pedidos (`@cityfoods_app`).
|
||||
|
||||
A Central de Pedidos é o **hub pós-login**:
|
||||
- Staff entra → `/pedidos` (não /pdv)
|
||||
- CTA **+ Novo** → PDV (`/pdv`) para digitar pedido
|
||||
- Pedidos de **qualquer canal** (PDV, cardápio digital, WhatsApp, iFood, telefone)
|
||||
caem no **mesmo Order** e aparecem neste quadro
|
||||
- Daqui a loja vai para KDS, etiqueta e despacho
|
||||
|
||||
Regra de ouro (SPEC §5.5):
|
||||
> Nenhum canal tem UI de cozinha própria — tudo passa pelo Order bus e pelo `/pedidos`.
|
||||
|
||||
# Dimensões de pedido (alvo — migrar do `channel` legado)
|
||||
|
||||
origin: "pdv" | "cardapio" | "ifood" | "whatsapp" | "telefone"
|
||||
fulfillment: "entrega" | "retirada" | "mesa"
|
||||
|
||||
Legado atual (Prisma `OrderChannel`):
|
||||
delivery → origin=pdv, fulfillment=entrega
|
||||
balcao → origin=pdv, fulfillment=retirada
|
||||
pdv → origin=pdv, fulfillment=mesa
|
||||
|
||||
UI do card:
|
||||
- Faixa colorida = fulfillment (ENTREGA / RETIRADA / MESA) + código curto
|
||||
- Badge secundário = origin (CARDÁPIO / IFOOD / WHATSAPP / PDV / TELEFONE)
|
||||
|
||||
# Stack (não trocar)
|
||||
- Monorepo: apps/api (NestJS + Prisma + Socket.io) · apps/web (React + Vite + TS)
|
||||
- DB: PostgreSQL · Redis · Docker na VM 125
|
||||
- Auth: JWT via `useAuth` / `authFetch`
|
||||
- Realtime: Socket.io eventos `order.created` | `order.updated` | `order.ready` | `ticket.bumped`
|
||||
- Host: https://pizza.itecnologys.com · path /pedidos
|
||||
|
||||
# Arquivos-chave (ler antes de editar)
|
||||
- SPEC.md (§5.5, §6.1, §7.1 RF-PED, §7.8 RF-CAN, §10)
|
||||
- docs/SPEC-CANAIS-DIGITAIS.md
|
||||
- apps/web/src/PedidosPage.tsx ← tela Central
|
||||
- apps/web/src/PdvPage.tsx ← + Novo
|
||||
- apps/web/src/App.tsx ← login → /pedidos
|
||||
- apps/web/src/styles.css ← classes .pedidos-*
|
||||
- apps/web/src/channelLabel.ts
|
||||
- apps/api/prisma/schema.prisma ← Order ainda tem `channel`
|
||||
- apps/api (módulo orders + gateway WS)
|
||||
|
||||
# Estado atual (já no ar)
|
||||
- Skin CityFoods: header Pedidos / + Novo, chips de tags, grid de cards, aside Histórico
|
||||
- WS refresh no board
|
||||
- Filtro ACTIVE = confirmado | em_producao | pronto
|
||||
- channelTag mapeia delivery/balcao/pdv → ENTREGA/RETIRADA/MESA (ainda via `channel`)
|
||||
- Marcar pronto, link etiqueta, link KDS
|
||||
- Ainda NÃO há: origin/fulfillment no schema, badge de origem, som de novo pedido digital,
|
||||
filtros por fulfillment/origin, cancelar/editar no card, som + highlight de pedido novo
|
||||
|
||||
# O que construir nesta sessão (Central de Pedidos — pacote board)
|
||||
|
||||
## P0 — modelo + API
|
||||
1. Migrar Order: adicionar `origin` + `fulfillment` (manter `channel` deprecated ou mapear na leitura)
|
||||
2. Create/list orders aceitam e devolvem origin/fulfillment
|
||||
3. Compat: PDV continua mandando channel legado → API normaliza para origin+fulfillment
|
||||
4. WS payload inclui origin + fulfillment
|
||||
|
||||
## P1 — UI Central `/pedidos`
|
||||
1. Card: faixa = fulfillment; badge = origin (CARDÁPIO / IFOOD / …)
|
||||
2. Som + flash no card quando `order.created` (especialmente origin digital)
|
||||
3. Filtros: Todos | Entrega | Retirada | Mesa + opcional por origin
|
||||
4. Ordenação: horário (default) | nº pedido | urgente primeiro
|
||||
5. Histórico lateral: últimos prontos/em_rota/entregue com origin
|
||||
6. Ações no card: ✓ pronto (se em_producao) · etiqueta · abrir KDS · (depois) cancelar
|
||||
7. Empty state: “Nenhum pedido” + CTA + Novo
|
||||
8. Touch-first, legível a 1 m; pt-BR; R$ via money.ts
|
||||
|
||||
## P2 — ponte canais (não implementar cardápio inteiro aqui)
|
||||
- Garantir que POST /api/orders com origin=cardapio|ifood|whatsapp aparece no board ≤ 500 ms
|
||||
- Badge correto; fulfillment escolhido pelo cliente
|
||||
- Stub: endpoint/fixture que simula pedido digital para testar o board sem PWA
|
||||
|
||||
# UX CityFoods a preservar
|
||||
- Faixa ENTREGA/RETIRADA no topo do card
|
||||
- Tags de sabor/components sob o item
|
||||
- “Preparo tardio” se em_producao > 20 min
|
||||
- Histórico com check à direita
|
||||
- + Novo em destaque no header
|
||||
- Sem dashboard genérico; um quadro operacional
|
||||
|
||||
# Critérios de aceite
|
||||
1. Login → /pedidos abre o quadro
|
||||
2. + Novo → /pdv → criar pedido → volta a aparecer no quadro via WS sem F5
|
||||
3. Pedido simulado origin=cardapio mostra badge CARDÁPIO + faixa ENTREGA/RETIRADA
|
||||
4. Latência visual ≤ 500 ms após order.created
|
||||
5. KDS e etiqueta continuam funcionando a partir do mesmo Order
|
||||
6. Mobile/tablet: cards usáveis com dedo; sem regressão no PDV
|
||||
|
||||
# Fora de escopo nesta sessão
|
||||
- PWA cardápio /c/:slug completo (Track C1.1–C1.3 — sessão separada)
|
||||
- Webhook iFood real (C2)
|
||||
- Rotas / ORS / motoboy
|
||||
- Redesign do KDS
|
||||
- Multi-tenant SaaS
|
||||
|
||||
# Como trabalhar
|
||||
1. Ler SPEC §5.5 + PedidosPage + schema Order
|
||||
2. Mudar uma fatia por vez (schema → API → UI → fixture digital)
|
||||
3. Não inventar libs novas; seguir padrões Nest/React do repo
|
||||
4. Depois de UI: rebuild web se deploy Docker; validar /pedidos
|
||||
5. Atualizar docs/SPEC-CANAIS-DIGITAIS.md se fechar um critério C1.x
|
||||
|
||||
Comece pelo gap origin/fulfillment (API + badges no PedidosPage). Mostre o plano curto e implemente.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Prompt curto (follow-up / hillclimb)
|
||||
|
||||
```text
|
||||
Continua a Central de Pedidos (/pedidos) no Pizza Digital.
|
||||
SPEC §5.5 · origin×fulfillment · badge origem · WS ≤500ms · CityFoods skin.
|
||||
Não faça o cardápio PWA agora — só o board + normalização do Order.
|
||||
Arquivo principal: apps/web/src/PedidosPage.tsx + orders API + prisma Order.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Mapa mental (1 tela)
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────┐
|
||||
Canais → │ CENTRAL DE PEDIDOS /pedidos │ → KDS
|
||||
│ + Novo → PDV │ → Etiqueta
|
||||
│ cards: fulfillment + badge origin │ → Despacho
|
||||
└─────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Checklist de sessão
|
||||
|
||||
| # | Item | Done |
|
||||
|---|------|------|
|
||||
| 1 | `origin` + `fulfillment` no Prisma/API | ☐ |
|
||||
| 2 | Badges no card PedidosPage | ☐ |
|
||||
| 3 | Som/flash `order.created` | ☐ |
|
||||
| 4 | Filtros fulfillment | ☐ |
|
||||
| 5 | Fixture pedido digital | ☐ |
|
||||
| 6 | Aceite WS ≤ 500 ms | ☐ |
|
||||
28
docs/RELEASE-v0.0.0.md
Normal file
28
docs/RELEASE-v0.0.0.md
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# Pizza Digital v0.0.0
|
||||
|
||||
**Data:** 2026-07-23
|
||||
**Produto:** Cozinha Digital / PDV / KDS / Expedição / Despacho (piloto)
|
||||
**URL:** https://pizza.myvexx.com
|
||||
**VM runtime:** 125 (`pizza-digital` · `10.10.10.125`)
|
||||
**Vault Obsidian (BIG1 VM123):** `/root/obsidian-infra/ligbox-ops-platform/docs/pizza-digital/`
|
||||
|
||||
## Remotes GitHub (v0.0.0)
|
||||
|
||||
| Remote | Repo | Nota |
|
||||
|--------|------|------|
|
||||
| Itecnologys | `itecnologys-tech/pizza-digital` | repo oficial org |
|
||||
| MyVexx mirror | `itecnologys/pizza-digital-myvexx` | mirror produto myvexx.com (sem org myvexx no token) |
|
||||
|
||||
Tag: **`v0.0.0`**
|
||||
|
||||
## Módulos nesta versão
|
||||
|
||||
- PDV — canais Entrega / Balcão / Mesa · IP origem
|
||||
- KDS — Massa / Montagem / Forno / Bebidas · PEGAR · BUMP · IMPRIMIR
|
||||
- Expedição — só pedidos `pronto` (sem PEGAR) · SAIR
|
||||
- Despacho — fila `pronto` → `em_ruta`
|
||||
- Etiqueta — código · hash · componentes da pizza · IP
|
||||
|
||||
## Spec
|
||||
|
||||
Ver `SPEC.md` §15 (S0–S3 ✅ · S4 parcial · S6+ rotas pendente).
|
||||
114
docs/SPEC-CANAIS-DIGITAIS.md
Normal file
114
docs/SPEC-CANAIS-DIGITAIS.md
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
# SPEC — Entrada por Canais Digitais
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Capítulo** | SPEC §5.5 · RF-CAN · Track C |
|
||||
| **Versão** | 0.3.0 |
|
||||
| **Data** | 2026-10-19 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **Referência UX** | CityFoods Cardápio Digital + quadro Pedidos |
|
||||
| **Cardápio (catálogo)** | [`SPEC-CARDAPIO.md`](SPEC-CARDAPIO.md) |
|
||||
| **Estoque** | [`SPEC-ESTOQUE.md`](SPEC-ESTOQUE.md) — disponibilidade no PWA |
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Fazer o cliente pedir **sem passar pelo balcão**, e a loja ver o pedido no mesmo quadro operacional (`/pedidos`) que já usa para PDV/KDS.
|
||||
|
||||
**Promessa:** menos telefone · menos erro de digitação · mais ticket digital · mesma cozinha.
|
||||
|
||||
---
|
||||
|
||||
## 2. Modelo de dados (alvo)
|
||||
|
||||
### 2.1 Dimensões
|
||||
|
||||
```ts
|
||||
origin: "pdv" | "cardapio" | "ifood" | "whatsapp" | "telefone"
|
||||
fulfillment: "entrega" | "retirada" | "mesa"
|
||||
```
|
||||
|
||||
### 2.2 Migração do `channel` atual
|
||||
|
||||
| Legado `channel` | `origin` | `fulfillment` |
|
||||
|------------------|----------|---------------|
|
||||
| `delivery` | `pdv` | `entrega` |
|
||||
| `balcao` | `pdv` | `retirada` |
|
||||
| `pdv` | `pdv` | `mesa` |
|
||||
|
||||
Novos pedidos digitais: `origin=cardapio` (ou `ifood`) + fulfillment escolhido pelo cliente.
|
||||
|
||||
---
|
||||
|
||||
## 3. Track C — sprints
|
||||
|
||||
| ID | Entrega | Dependência |
|
||||
|----|---------|-------------|
|
||||
| **C1.1** | Rotas públicas `/c/:slug` (cardápio PWA) | Menu API + loja aberta/fechada |
|
||||
| **C1.2** | Carrinho + checkout (entrega/retirada, bairro, taxa) | Áreas de entrega |
|
||||
| **C1.3** | Pagamento: Pix (stub/real) + “pagar na entrega” | Gateway Pix |
|
||||
| **C1.4** | Create Order `origin=cardapio` → WS → `/pedidos` | Schema origin/fulfillment |
|
||||
| **C1.5** | Badge origem + som no board | UI `/pedidos` |
|
||||
| **C1.6** | Deep-link WhatsApp (`wa.me` → `/c/:slug`) | C1.1 |
|
||||
| **C1.7** | API inbound WhatsApp (`POST /api/inbound/whatsapp`) | ✅ v0.1 — ver [`API-WHATSAPP-INBOUND.md`](API-WHATSAPP-INBOUND.md) |
|
||||
| **C2.1** | Adaptador iFood (webhook + assinatura) | Conta loja iFood |
|
||||
| **C2.2** | Normalizador iFood → Order + idempotência | C1.4 |
|
||||
|
||||
**Gate:** C1 só começa com S3 estável; idealmente após S5 (piloto). Pode prototipar C1.1 em paralelo.
|
||||
|
||||
---
|
||||
|
||||
## 4. Fluxo C1 — cardápio digital
|
||||
|
||||
```text
|
||||
Cliente abre /c/pizza-digital-piloto
|
||||
→ vê cardápio (categorias / pizzas / bebidas)
|
||||
→ monta carrinho (meia-a-meia / borda / obs)
|
||||
→ escolhe Entrega ou Retirada
|
||||
→ se Entrega: bairro + endereço + taxa
|
||||
→ pagamento (Pix ou na entrega)
|
||||
→ POST /api/orders { origin: "cardapio", fulfillment, items, ... }
|
||||
→ loja: card novo em /pedidos (ENTREGA/RETIRADA + badge CARDÁPIO)
|
||||
→ KDS recebe como qualquer pedido
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. UX quadro `/pedidos` (já no ar)
|
||||
|
||||
| Elemento CityFoods | Nosso |
|
||||
|--------------------|--------|
|
||||
| Faixa ENTREGA / RETIRADA | `fulfillment` |
|
||||
| Tags de sabor | components + notes |
|
||||
| Preparo tardio | SLA > 20 min |
|
||||
| Histórico | pedidos recentes |
|
||||
| **Novo:** badge origem | APLICATIVO / IFOOD / PDV / WHATSAPP |
|
||||
|
||||
---
|
||||
|
||||
## 6. Fora de escopo (C1)
|
||||
|
||||
- App nativo iOS/Android consumidor
|
||||
- Marketplace multi-loja Pizza Digital
|
||||
- Fidelidade / cashback (fase posterior)
|
||||
- Chat in-app com a loja
|
||||
|
||||
---
|
||||
|
||||
## 7. Critérios de aceite C1
|
||||
|
||||
1. Cliente finaliza pedido no cardápio sem login de staff.
|
||||
2. Pedido aparece no `/pedidos` em ≤ 500 ms (WS) com badge **CARDÁPIO**.
|
||||
3. KDS processa igual a pedido PDV.
|
||||
4. Etiqueta mostra origem + fulfillment.
|
||||
5. Loja fechada → cardápio bloqueia checkout com mensagem clara.
|
||||
|
||||
---
|
||||
|
||||
## 8. Próximo passo de implementação
|
||||
|
||||
1. ~~Migrar modelo `origin` + `fulfillment` (API + UI badges).~~ ✅ Central `/pedidos` + `POST /api/orders/demo-digital`
|
||||
2. Scaffold PWA `/c/:slug`.
|
||||
3. Ligar create order cardápio público → board (já validável via fixture).
|
||||
197
docs/SPEC-CARDAPIO.md
Normal file
197
docs/SPEC-CARDAPIO.md
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
# SPEC — Cardápio (Menu)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Capítulo** | SPEC §7.5 RF-MEN · domínio Menu* |
|
||||
| **Versão** | 0.1.0 |
|
||||
| **Data** | 2026-10-19 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **Pesquisa regional** | [`CARDAPIO-JOINVILLE.md`](CARDAPIO-JOINVILLE.md) |
|
||||
| **SPEC irmã (obrigatória)** | [`SPEC-ESTOQUE.md`](SPEC-ESTOQUE.md) — ficha técnica / BOM |
|
||||
| **Canais** | [`SPEC-CANAIS-DIGITAIS.md`](SPEC-CANAIS-DIGITAIS.md) — PWA `/c/:slug` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Ser a **fonte única da verdade** do que a loja vende: categorias, itens, preços, tamanhos, modifiers (borda/meia-a-meia) e mapeamento para KDS.
|
||||
|
||||
O cardápio **não guarda saldo**. Saldo, compra e baixa ficam no **Estoque**. A ponte é a **ficha técnica (BOM)**: cada item do menu consome 1+ insumos.
|
||||
|
||||
```text
|
||||
Cardápio (o que vende) ──BOM──► Estoque (o que tem)
|
||||
│ │
|
||||
▼ ▼
|
||||
PDV / PWA / iFood alerta / bloqueio / CMV
|
||||
│
|
||||
▼
|
||||
Order → KDS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Escopo
|
||||
|
||||
### Inclui
|
||||
- Categorias e itens (CRUD gestor)
|
||||
- Preços por tamanho (Broto / G / Gigante) ou SKU por tamanho
|
||||
- Meia-a-meia, bordas, adicionais
|
||||
- Map item → estações KDS + componentes (tags)
|
||||
- Disponibilidade: ativo / pausado / esgotado (via Estoque)
|
||||
- Cardápio Joinville piloto (tradicionais, especiais SC, doces, bebidas)
|
||||
- Exposição ao PDV, Central, PWA público e (futuro) iFood
|
||||
|
||||
### Não inclui (Estoque)
|
||||
- Quantidade em prateleira, NF de compra, inventário físico → [`SPEC-ESTOQUE.md`](SPEC-ESTOQUE.md)
|
||||
|
||||
---
|
||||
|
||||
## 3. Modelo de dados (alvo)
|
||||
|
||||
```text
|
||||
MenuCategory
|
||||
id, storeId, name, sortOrder, active, channelVisibility[]
|
||||
|
||||
MenuItem ← já existe (Prisma)
|
||||
id, storeId, categoryId, name, description
|
||||
priceCents ← preço base (ou ver Size)
|
||||
halfHalf, active, sortOrder
|
||||
stations[] · components[]
|
||||
|
||||
MenuItemSize (novo — Track M1)
|
||||
id, menuItemId, sizeCode ← broto|media|grande|gigante
|
||||
diameterCm, slices, maxFlavors
|
||||
priceCents, active
|
||||
|
||||
MenuModifier (borda / adicional)
|
||||
id, storeId, name, priceCents, kind ← borda|extra|bebida_combo
|
||||
stationCodes[]
|
||||
|
||||
Recipe (ficha técnica) ← vive na SPEC-ESTOQUE, FK menuItemId
|
||||
id, menuItemId, sizeCode?
|
||||
lines[] → Ingredient + qty + unit
|
||||
```
|
||||
|
||||
### Tamanhos padrão Joinville (congelar no piloto)
|
||||
|
||||
Referência de prato de mesa (~25 cm) + diâmetro / fatias comerciais SC:
|
||||
|
||||
| sizeCode | Nome UI | cm | Fatias | ≈ pratos | Sabores máx. (split) | Fator preço (base = Grande) |
|
||||
|----------|---------|----|--------|----------|----------------------|------------------------------|
|
||||
| `broto` | Broto | 25 | 4 | 1 | **1** | 0,55 |
|
||||
| `pequena` | Pequena | 30 | 6 | 1,5 | **1** | 0,70 |
|
||||
| `media` | Média | 35 | 8 | 2 | **2** | 0,85 |
|
||||
| `grande` | Grande | 40 | 12 | 2,5 | **3** | 1,00 |
|
||||
| `gigante` | Gigante | 45 | 16 | 3 | **4** | 1,20 |
|
||||
| `familia` | Família | 50 | 20 | 3,5 | **4** | 1,35 |
|
||||
|
||||
**Regras de split:** Broto e Pequena = inteira (1 sabor). Média até 2. Grande até 3. Gigante e Família até 4. Preço = **maior sabor** × fator do tamanho (+ borda).
|
||||
|
||||
**Default comercial:** `grande` (40 cm). Catálogo em `GET /api/menu/sizes` e no sheet do PDV.
|
||||
|
||||
---
|
||||
|
||||
## 4. Categorias piloto (já no seed)
|
||||
|
||||
| # | Categoria | Papel |
|
||||
|---|-----------|--------|
|
||||
| 1 | Pizzas Tradicionais | Sabores · preço base Grande; tamanho no pedido |
|
||||
| 2 | Especiais Joinville | Diferencial SC |
|
||||
| 3 | Pizzas Doces | Pós-salgada |
|
||||
| 4 | Refrigerantes | Acompanhamento |
|
||||
| 5 | Sucos | Natural + caixa |
|
||||
| 6 | Energéticos | Acompanhamento |
|
||||
| 7 | Vitaminas · frutas c/ leite | Feitas na hora |
|
||||
| 8 | Bordas e extras | Modifiers |
|
||||
|
||||
---
|
||||
|
||||
## 5. Requisitos (RF-MEN + extensão)
|
||||
|
||||
| ID | Requisito | Fase |
|
||||
|----|-----------|------|
|
||||
| RF-MEN-01 | CRUD categorias, itens, preços, adicionais | ✅ parcial |
|
||||
| RF-MEN-02 | Mapear item → estações KDS | ✅ |
|
||||
| RF-MEN-03 | SLA por categoria/item | Fase 1 |
|
||||
| RF-MEN-04 | Cardápio digital público PWA | Track C1 |
|
||||
| RF-MEN-05 | Meia-a-meia / combos | ✅ split multi-sabor por tamanho |
|
||||
| RF-MEN-06 | Horário loja aberta/fechada | Track C1 |
|
||||
| RF-MEN-07 | Áreas entrega + taxa | Track C1 |
|
||||
| RF-MEN-08 | Tamanhos Broto→Família + preço por fator | ✅ PDV + `/menu/sizes` |
|
||||
| RF-MEN-09 | Pausar item (manual ou por estoque baixo) | **M1** |
|
||||
| RF-MEN-10 | Ficha técnica obrigatória p/ item produzido | **M1** → Estoque |
|
||||
| RF-MEN-11 | Bebida 1:1 → 1 unidade de SKU estoque | **M1** |
|
||||
| RF-MEN-12 | Vitamina/suco natural → BOM (fruta + leite + gelo) | **M1** |
|
||||
| RF-MEN-13 | Sync disponibilidade → PWA/PDV ≤ 2 s | Track C1+M1 |
|
||||
| RF-MEN-14 | Preço meia-a-meia = maior sabor (config) | ✅ regra |
|
||||
| RF-MEN-15 | Cada pizza com BOM em **gramas** (catálogo Joinville 38) | **M1** ↔ EST-13 |
|
||||
|
||||
---
|
||||
|
||||
## 6. Ponte Cardápio ↔ Estoque
|
||||
|
||||
| Evento cardápio | Efeito estoque |
|
||||
|-----------------|----------------|
|
||||
| Criar/editar `MenuItem` produzido | Exigir `Recipe` (ou marcar `stockMode=none`) |
|
||||
| Pedido `confirmado` / `em_producao` | Reserva ou baixa (política EST) |
|
||||
| Pedido `cancelado` antes da produção | Estorna reserva |
|
||||
| Saldo < `minStock` de insumo crítico | Item → `paused` ou badge “esgotado” |
|
||||
| Compra / ajuste no Estoque | Recalcula disponibilidade do menu |
|
||||
|
||||
Detalhe de baixas, **pesos em g por tipo de pizza**, unidades e alertas:
|
||||
**[`SPEC-ESTOQUE.md`](SPEC-ESTOQUE.md) §4–4.1** · [`FICHAS-TECNICAS-PESO.md`](FICHAS-TECNICAS-PESO.md) · [`data/fichas-pizzas-peso.json`](data/fichas-pizzas-peso.json).
|
||||
|
||||
### `stockMode` por item
|
||||
|
||||
| Modo | Uso | Exemplo |
|
||||
|------|-----|---------|
|
||||
| `bom` | Consome receita | Pizza Calabresa G |
|
||||
| `sku` | 1 item = 1 unidade SKU | Coca 2L, Red Bull |
|
||||
| `none` | Sem controle | Observação / taxa |
|
||||
|
||||
---
|
||||
|
||||
## 7. Regras de negócio
|
||||
|
||||
1. Item `active=false` ou `paused` **não aparece** no PDV/PWA (gestor ainda vê no admin).
|
||||
2. Meia-a-meia: ambos sabores precisam estar disponíveis; baixa BOM proporcional (½ + ½) — ver Estoque.
|
||||
3. Borda é `MenuModifier` com BOM própria (catupiry/cheddar).
|
||||
4. Vitamina: sempre `stockMode=bom` (leite + fruta + gelo); se faltar fruta → pausa só aquele sabor.
|
||||
5. Preços em **centavos BRL**; UI pt-BR (`formatBRL`).
|
||||
|
||||
---
|
||||
|
||||
## 8. Track M — implementação
|
||||
|
||||
| ID | Entrega | Dependência |
|
||||
|----|---------|-------------|
|
||||
| **M0** | Seed Joinville (pizzas + bebidas) no ar | ✅ |
|
||||
| **M1** | `Recipe` + `Ingredient` + baixa no pedido | SPEC-ESTOQUE E1 |
|
||||
| **M2** | Admin UI ficha técnica + pausa auto | M1 |
|
||||
| **M3** | `MenuItemSize` multi-preço | M0 |
|
||||
| **M4** | Cardápio PWA lê só itens disponíveis | C1 + M1 |
|
||||
|
||||
---
|
||||
|
||||
## 9. Critérios de aceite
|
||||
|
||||
1. Gestor lista/edita categorias e itens no admin (ou seed + API).
|
||||
2. PDV mostra categorias Joinville com preços BRL.
|
||||
3. Todo item `stockMode=bom|sku` tem ficha ou SKU ligado (M1).
|
||||
4. Coca 2L sem saldo → some/pausada no PDV em ≤ 2 s (M1).
|
||||
5. Pizza sem mussarela no estoque → Calabresa e Mussarela pausam (M1).
|
||||
6. SPEC-ESTOQUE referencia este doc e vice-versa (sem requisitos órfãos).
|
||||
|
||||
---
|
||||
|
||||
## 10. Fora de escopo (agora)
|
||||
|
||||
- Cardápio multi-idioma
|
||||
- Fotos HD / CMS marketing
|
||||
- Precificação dinâmica por horário
|
||||
- Marketplace multi-loja
|
||||
|
||||
---
|
||||
|
||||
*SPEC satélite. Versão do produto pai: SPEC.md. Cruzamento obrigatório com SPEC-ESTOQUE.*
|
||||
142
docs/SPEC-CITYFOODS-VIDEO-PARITY.md
Normal file
142
docs/SPEC-CITYFOODS-VIDEO-PARITY.md
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
# SPEC — Paridade UI/ops com 3 vídeos CityFoods
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Documento** | Gap analysis + requisitos de UI idêntica em operação |
|
||||
| **Versão** | 0.1.0 |
|
||||
| **Data** | 2026-10-22 |
|
||||
| **Alinha com** | SPEC-DESPACHO-CITYFOODS-PARITY v0.2 · SPEC.md §15 |
|
||||
| **Vídeos** | [Overview yVtc](https://www.youtube.com/watch?v=yVtcgJt0L_Q) · [Forno Jxau](https://www.youtube.com/watch?v=JxauDKXTJr0) · [Despacho+mapa KDcD](https://www.youtube.com/watch?v=KDcDTGEw11A) |
|
||||
|
||||
---
|
||||
|
||||
## 0. Objetivo
|
||||
|
||||
Reconstruir a superfície operacional para **a mesma interface e operacionalidade** mostrada nos três vídeos — não só “capacidade de backend”.
|
||||
Prioridade absoluta do produto: **mapa como bloco inteiro na tela**, capaz de rastrear todas as entregas/motoboys ao vivo.
|
||||
|
||||
---
|
||||
|
||||
## 1. Narrativa por vídeo (transcrição)
|
||||
|
||||
### V1 — Overview Siciliana / Mangue Hills (`yVtcgJt0L_Q`)
|
||||
|
||||
- Despacho = “coração operacional”: frota casa/rua, canal, nº pedido, tempos, reordenação → KDS.
|
||||
- Cores de estágio: vermelho (novo) · laranja (produção) · amarelo (forno).
|
||||
- Rotas inteligentes: pedidos próximos saem juntos.
|
||||
- Cotação iFood vs frota própria (fora de escopo imediato — P3).
|
||||
- Conferência/estufa com etiqueta → aparece no despacho.
|
||||
- Ranking ao vivo (pizzas/noite, tempo médio) + funil (produção/forno/estufa/rua).
|
||||
|
||||
### V2 — Forno / filtro por sabor (`JxauDKXTJr0`)
|
||||
|
||||
- KDS forno touch: filtrar por **sabor** (ex.: calabresa → Napoli) para achar a comanda na noite cheia.
|
||||
- Confirmar item encontrado → bump; quando todos os itens do pedido estão ok → pedido **pronto** para despacho.
|
||||
- Impressão opcional por etapa.
|
||||
|
||||
### V3 — Despacho + rastreio (`KDcDTGEw11A`) — **âncora do mapa**
|
||||
|
||||
1. Tela de despacho: pedidos **verdes** = prontos aguardando entregador (tempo / atraso).
|
||||
2. Selecionar N pizzas que formam uma “roda” (rota).
|
||||
3. Escolher entregador do dia.
|
||||
4. Botão **Ir para entrega** → pedidos saem da visualização de prontos e vão para o app do entregador.
|
||||
5. Mapa de rastreio: **todas as entregas de todos os entregadores** em tempo real + pin do motoboy.
|
||||
6. Filtro por entregador (ex.: Steve) → só as paradas dele.
|
||||
7. Entregue no app → pin **some** do mapa; última entrega → motoboy apto a voltar à casa.
|
||||
8. Relatório noite: entregas, km, ranking pizzaiolo (já coberto em `/ops`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Gap vs Pizza Digital (baseline pós-Gestor de Rotas)
|
||||
|
||||
| Capacidade vídeo | Baseline | Gap |
|
||||
|------------------|----------|-----|
|
||||
| Pedidos verdes prontos no despacho | Estufa/pronto com cards | ⚠️ Visual não “verde pronto” unificado |
|
||||
| Seleção + “Ir para entrega” (1 clique) | Montar rota → Despachar (2 passos) | ❌ CTA e fluxo do vídeo |
|
||||
| Mapa **bloco inteiro** / viewport dominante | Painel lateral sticky ~320px | ❌ Layout |
|
||||
| Rastreio **todos** motoboys + paradas | Mapa foca 1 rota ativa | ❌ Multi-rota / multi-driver |
|
||||
| Filtro mapa por entregador | Tabs de rota | ⚠️ Parcial |
|
||||
| Pin some ao entregar | Stop `entregue` ainda desenhado | ❌ Deve omitir pin |
|
||||
| App motoboy navegar Maps/Waze | Deep-link Google | ✅ parcial |
|
||||
| Forno filtro por sabor | KDS claim/bump sem filter sabor | ❌ V2 (próximo) |
|
||||
| Ranking / funil | `/ops` | ✅ |
|
||||
| iFood cotação | — | ❌ P3 |
|
||||
|
||||
---
|
||||
|
||||
## 3. Requisitos novos (UI parity)
|
||||
|
||||
### Tela D2 — Despacho mapa-first (`/despacho`)
|
||||
|
||||
| ID | Requisito | Pri |
|
||||
|----|-----------|-----|
|
||||
| RF-VID-01 | Layout: **mapa = bloco dominante full-bleed** (≥ ~55–70% do viewport útil); lista de prontos + frota em dock lateral/inferior, sem esconder o mapa | P0 |
|
||||
| RF-VID-02 | Lista unificada de **prontos verdes** (estufa + pronto sem rota); tempo e atraso visíveis | P0 |
|
||||
| RF-VID-03 | Seleção múltipla + entregador + CTA primário **Ir para entrega** (= montar batch + despachar na rua numa ação) | P0 |
|
||||
| RF-VID-04 | Ao Ir para entrega: pedidos saem da lista; entram no app motoboy; pins aparecem no mapa | P0 |
|
||||
| RF-VID-05 | Mapa mostra **todas** as rotas `na_rua` (e preview/montada) + pins de **todos** os motoboys com GPS | P0 |
|
||||
| RF-VID-06 | Chip/filtro por entregador: “todos” ou 1 motoboy — atualiza pins | P0 |
|
||||
| RF-VID-07 | Parada `entregue` **não** desenha pin (some do rastreio) | P0 |
|
||||
| RF-VID-08 | Socket: `route.*` / `driver.ping` / `order.delivered` redesenham o mapa sem reload manual | P0 |
|
||||
| RF-VID-09 | Manter atalhos avançados (Preview / Montar sem despachar) em menu secundário | P1 |
|
||||
| RF-VID-10 | KDS forno: filtro por sabor (chips) + confirmar item — ver RF-FOR-* | P1 |
|
||||
|
||||
### Tela KDS Forno (V2)
|
||||
|
||||
| ID | Requisito | Pri |
|
||||
|----|-----------|-----|
|
||||
| RF-FOR-01 | Em `/kds/forno` (ou estação forno): chips de sabores presentes na fila | P1 |
|
||||
| RF-FOR-02 | Selecionar 1–N sabores filtra tickets que contenham esses itens | P1 |
|
||||
| RF-FOR-03 | Confirmar item encontrado (bump parcial) até pedido completo → pronto/estufa | P1 |
|
||||
|
||||
---
|
||||
|
||||
## 4. Layout alvo (D2)
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Nav · Despacho · ao vivo · Motoboy · Ops │
|
||||
├──────────────┬──────────────────────────────────────────────┤
|
||||
│ Prontos ● N │ │
|
||||
│ [✓] #003 … │ MAPA FULL-BLEED │
|
||||
│ [✓] #013 … │ (todas entregas + motoboys) │
|
||||
│ [✓] #004 … │ │
|
||||
│ │ │
|
||||
│ Motoboy ▾ │ │
|
||||
│ [Ir p/ entrega] │
|
||||
│ Frota chips │ Filtro mapa: Todos | Steve | … │
|
||||
└──────────────┴──────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Mobile: mapa no topo (min-height ~45vh); dock de prontos abaixo.
|
||||
|
||||
---
|
||||
|
||||
## 5. Aceite (definição de pronto desta SPEC)
|
||||
|
||||
1. Abrir `/despacho` → mapa ocupa o bloco principal da tela (não coluna estreita).
|
||||
2. Selecionar 2+ pedidos verdes + motoboy → **Ir para entrega** → somem da lista e aparecem no mapa + `/motoboy`.
|
||||
3. Com 2 motoboys na rua, o mapa mostra paradas/pins de ambos; filtrar um esconde o outro.
|
||||
4. Marcar entregue no motoboy → pin correspondente some imediatamente (socket).
|
||||
5. SPEC-DESPACHO v0.2 continua válida para modelo de dados; este doc manda na **UI**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Fora de escopo imediato
|
||||
|
||||
- Cotação iFood / marketplace (V1 / P3)
|
||||
- Cópia pixel-perfect de assets proprietários CityFoods
|
||||
- Offline motoboy / PIN
|
||||
|
||||
---
|
||||
|
||||
## 7. Sequência de construção
|
||||
|
||||
1. **Agora:** RF-VID-01…08 — rebuild `/despacho` mapa-first + Ir para entrega
|
||||
2. Em seguida: RF-FOR-01…03 — filtro forno
|
||||
3. Depois: P3 iFood
|
||||
|
||||
---
|
||||
|
||||
*Cruzamento das 3 transcrições × SPEC-DESPACHO v0.2 × pizza.itecnologys.com — 2026-10-22.*
|
||||
397
docs/SPEC-DESPACHO-CITYFOODS-PARITY.md
Normal file
397
docs/SPEC-DESPACHO-CITYFOODS-PARITY.md
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
# SPEC — Paridade Despacho / Delivery (referência CityFoods)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Documento** | Paridade operacional com CityFoods (expedição + delivery + admin) |
|
||||
| **Versão** | 0.2.1 |
|
||||
| **Data** | 2026-10-22 |
|
||||
| **Status impl.** | P0–P2 + Gestor · UI mapa-first em [`SPEC-CITYFOODS-VIDEO-PARITY.md`](SPEC-CITYFOODS-VIDEO-PARITY.md) |
|
||||
| **Vídeo referência** | [Siciliana yVtc](https://www.youtube.com/watch?v=yVtcgJt0L_Q) · [Forno Jxau](https://www.youtube.com/watch?v=JxauDKXTJr0) · [Despacho KDcD](https://www.youtube.com/watch?v=KDcDTGEw11A) |
|
||||
| **Case** | ~R$ 1M/mês · ~70% iFood · hub multi-marca · entrega < 30 min |
|
||||
| **Alinha com** | SPEC §15 S4→S8 · SPEC-FASE1 §6 · rusterando (rotas) |
|
||||
| **Fora deste doc** | Cardápio público PWA (C1), fidelidade, caixa completo |
|
||||
|
||||
---
|
||||
|
||||
## 0. Objetivo
|
||||
|
||||
Elevar o Pizza Digital do estado atual (**lista de prontos + SAIR**) ao nível das telas CityFoods do vídeo:
|
||||
|
||||
1. **Despacho** como cockpit (não só fila)
|
||||
2. **Conferência / estufa** como etapa formal
|
||||
3. **Frota** (casa / rua) + atribuição
|
||||
4. **Rotas** e, depois, marketplace / app entregador
|
||||
5. **Admin** de zonas, ranking e relatórios operacionais
|
||||
|
||||
**Princípio de sequência (já na SPEC mãe):**
|
||||
KDS estável → despacho rico → frota → ORS → app motoboy → iFood.
|
||||
|
||||
---
|
||||
|
||||
## 1. Estado atual (baseline Pizza Digital)
|
||||
|
||||
| Tela / capacidade | Rota / artefato | Status |
|
||||
|-------------------|-----------------|--------|
|
||||
| Central de pedidos | `/pedidos` | ✅ |
|
||||
| KDS multi-estação | `/kds/:station` | ✅ |
|
||||
| Expedição (KDS) | `/kds/expedicao` · → ESTUFA | ✅ |
|
||||
| Etiqueta | `/etiqueta/:id` | ✅ |
|
||||
| Despacho cockpit | `/despacho` | ✅ P0–P2 |
|
||||
| Frota / motoboy | `/despacho` frota + `/api/drivers` | ✅ |
|
||||
| Conferência / estufa | `Order.estufaAt` + Expedição | ✅ |
|
||||
| **Gestor de Rotas** | `DispatchBatch` · mapa permanente | ✅ (heurística; ORS com `ORS_API_KEY`) |
|
||||
| App entregador | `/motoboy` | ✅ parcial (sem PIN próprio / offline) |
|
||||
| Zonas / taxas | `/admin/zonas` | ✅ CRUD (PDV ainda não aplica taxa auto) |
|
||||
| Ranking / funil / relatório | `/ops` | ✅ |
|
||||
| iFood no fluxo | — | ❌ P3 / C2 |
|
||||
|
||||
---
|
||||
|
||||
## 2. Narrativa das telas necessárias (SPEC por tela)
|
||||
|
||||
Cada subsecção = **uma tela** (ou superfície). Inclui: papel, o que o CityFoods mostra no vídeo, requisitos RF, dados, eventos, aceite.
|
||||
|
||||
---
|
||||
|
||||
### Tela A — Cockpit de Despacho (`/despacho`)
|
||||
|
||||
**Papel:** operador de despacho / gestor de turno.
|
||||
**No vídeo:** “coração operacional” — lista de pedidos + frota + tempos + cores + reordenação.
|
||||
|
||||
#### A.1 O que deve mostrar
|
||||
|
||||
| Coluna / bloco | Descrição |
|
||||
|----------------|-----------|
|
||||
| Frota (topo ou lateral) | Motoboys em **casa** / **rua**, nº de entregas ativas |
|
||||
| Pedido | Código, canal (PDV / App / iFood / WhatsApp), fulfillment |
|
||||
| Tempos | Hora do pedido · tempo na loja · tempo desde `pronto` / estufa |
|
||||
| Estágio visual | Cor: novo · em produção · forno · estufa · em rota |
|
||||
| Destino | Bairro + endereço curto |
|
||||
| Itens resumidos | Qtd × sabor (sem ficha completa) |
|
||||
| Ações | Selecionar · atribuir motoboy · SAIR / batch |
|
||||
|
||||
#### A.2 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint |
|
||||
|----|-----------|--------|
|
||||
| RF-DSP-01 | Listar pedidos em `pronto` e `na_estufa` em tempo real (Socket.io) | S4 / S6 |
|
||||
| RF-DSP-02 | Exibir canal, código, tempos (pedido / loja / estufa) | S4 |
|
||||
| RF-DSP-03 | Faixa de cor por estágio de produção espelhado do KDS | S4 |
|
||||
| RF-DSP-04 | Seleção múltipla de pedidos para batch de saída | S6 |
|
||||
| RF-DSP-05 | Atribuir entregador antes ou no SAIR | S6 |
|
||||
| RF-DSP-06 | SAIR → status `em_rota` + `dispatchedAt` + entregadorId | S6 |
|
||||
| RF-DSP-07 | Reordenar fila de prioridade; persistir; refletir no KDS | S6 |
|
||||
|
||||
#### A.3 Aceite
|
||||
|
||||
- Despachante vê, sem sair da tela, **quem está livre**, **o que está na estufa** e **há quanto tempo**.
|
||||
- Um clique em SAIR com motoboy atribuído remove o pedido da fila de prontos.
|
||||
|
||||
---
|
||||
|
||||
### Tela B — Conferência / Estufa
|
||||
|
||||
**Papel:** conferente (login próprio).
|
||||
**No vídeo:** identifica sabor → imprime etiqueta → pedido vai para estufa no despacho.
|
||||
|
||||
#### B.1 Fluxo
|
||||
|
||||
```
|
||||
KDS BUMP (forno) → fila Conferência
|
||||
→ conferente confirma sabores / qtds
|
||||
→ imprime etiqueta (código + sabores)
|
||||
→ status na_estufa
|
||||
→ aparece no Cockpit Despacho como “estufa”
|
||||
```
|
||||
|
||||
#### B.2 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint |
|
||||
|----|-----------|--------|
|
||||
| RF-CNF-01 | Tela `/conferencia` ou modo dedicado em `/kds/expedicao` | S4 |
|
||||
| RF-CNF-02 | Login com papel/área `conferente` ou `despacho` | S4 |
|
||||
| RF-CNF-03 | Checklist de itens/sabores antes de confirmar | S4 |
|
||||
| RF-CNF-04 | Confirmar → print etiqueta + `na_estufa` + evento `order.ready` enriquecido | S4 |
|
||||
| RF-CNF-05 | Pedidos na estufa ordenáveis por tempo parado (alerta visual) | S4 |
|
||||
|
||||
#### B.3 Modelo de estado (ampliação)
|
||||
|
||||
```
|
||||
em_producao → pronto_cozinha → na_estufa → em_rota → entregue
|
||||
```
|
||||
|
||||
> Compatibilidade: enquanto `na_estufa` não existir no enum, usar `pronto` + flag `holdingStation=estufa` **ou** migrar enum (preferível).
|
||||
|
||||
#### B.4 Aceite
|
||||
|
||||
- Sem etiqueta confirmada, pedido **não** entra na fila “estufa” do despacho.
|
||||
- Tempo parado na estufa é visível e alarmante após N minutos (config loja).
|
||||
|
||||
---
|
||||
|
||||
### Tela C — Frota / Motoboys
|
||||
|
||||
**Papel:** despacho + gestor.
|
||||
**No vídeo:** quem está na casa, quem está na rua, quantas entregas cada um carrega.
|
||||
|
||||
#### C.1 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint |
|
||||
|----|-----------|--------|
|
||||
| RF-FRT-01 | Cadastro de entregador (nome, telefone, ativo, loja) | S6 |
|
||||
| RF-FRT-02 | Status operacional: `casa` \| `rua` \| `offline` | S6 |
|
||||
| RF-FRT-03 | Contagem de entregas abertas (`em_rota`) por entregador | S6 |
|
||||
| RF-FRT-04 | Painel no cockpit: lista viva atualizada por socket | S6 |
|
||||
| RF-FRT-05 | Histórico simples: entregas do turno / canceladas | S6–S8 |
|
||||
|
||||
#### C.2 Aceite
|
||||
|
||||
- Ao despachar, só aparecem entregadores `casa` (ou regra configurável).
|
||||
- Quando o app marcar entrega, status volta a `casa` se fila zerada.
|
||||
|
||||
---
|
||||
|
||||
### Tela D — Gestor de Rotas (mapa + batch)
|
||||
|
||||
**Papel:** despacho.
|
||||
**No vídeo / City Pro:** montar rota → despachar → acompanhar no mapa (não só preview).
|
||||
|
||||
#### D.0 Fluxo canónico (implementado)
|
||||
|
||||
```
|
||||
Estufa (seleção ± bairro)
|
||||
→ Preview mapa (opcional)
|
||||
→ Montar rota → DispatchBatch status=montada
|
||||
→ Atribuir motoboy
|
||||
→ Despachar rota ✈ → status=na_rua · orders em_rota · 1ª parada a_caminho
|
||||
→ Motoboy: a caminho / entregue por parada (+ ping GPS)
|
||||
→ Última entrega → status=fechada · motoboy casa
|
||||
```
|
||||
|
||||
Mapa **full-bleed** (bloco dominante) em `/despacho` — ver RF-VID-01…08 em SPEC-CITYFOODS-VIDEO-PARITY: todas as rotas/motoboys, filtro por entregador, pin some ao entregar, CTA **Ir para entrega**.
|
||||
|
||||
#### D.1 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint | Status |
|
||||
|----|-----------|--------|--------|
|
||||
| RF-ROT-01 | Agrupar seleção por bairro (manual + sugestão) | S6 | ✅ |
|
||||
| RF-ROT-02 | Sugerir ordem de visita (heurística bairro/CEP) | S6 | ✅ |
|
||||
| RF-ROT-03 | Otimizar tour com ORS (OpenRouteService) | S7 | ✅ opcional (`ORS_API_KEY`; senão heurística) |
|
||||
| RF-ROT-04 | Mapa no despacho com pontos + polyline da rota | S7 | ✅ |
|
||||
| RF-ROT-04b | Entidade rota persistida `montada` \| `na_rua` \| `fechada` | S7 | ✅ `DispatchBatch` + `DispatchStop` |
|
||||
| RF-ROT-04c | Despachar rota (não só SAIR solto) | S7 | ✅ `POST /api/routes/:id/dispatch` |
|
||||
| RF-ROT-04d | Tracking motoboy no mapa (ping) | S7–S8 | ✅ `POST /api/routes/:id/ping` + `driver.ping` |
|
||||
| RF-ROT-05 | Comparar custo: frota própria vs marketplace | S7–C2 | ❌ (após iFood) |
|
||||
|
||||
#### D.2 Aceite
|
||||
|
||||
- Batch de 2+ pedidos gera rota com código (`Rdd-nn`), ordem e motoboy.
|
||||
- Mapa continua visível enquanto a rota está `montada` / `na_rua`.
|
||||
- ORS só quando `ORS_API_KEY` + pedidos com geo/bairro resolvível (Joinville centroides MVP).
|
||||
|
||||
**API:** `/api/routes/*` · **UI:** `/despacho` · **Ref.:** rusterando (modelo, sem fork AGPL).
|
||||
|
||||
---
|
||||
|
||||
### Tela E — App / PWA Entregador
|
||||
|
||||
**Papel:** motoboy.
|
||||
**No vídeo / Play Store CityFoods:** mapa, detalhes, iniciar e concluir entrega.
|
||||
|
||||
#### E.1 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint | Status |
|
||||
|----|-----------|--------|--------|
|
||||
| RF-MOT-01 | Login entregador (role/área) | S8 | ⚠️ parcial (escolhe motoboy na UI; sem PIN) |
|
||||
| RF-MOT-02 | Lista / rota ativa do motoboy | S8 | ✅ `/motoboy` + `GET /api/routes/driver/:id` |
|
||||
| RF-MOT-03 | Mapa da rota + deep-link GPS | S8 | ✅ |
|
||||
| RF-MOT-04 | Marcar “a caminho” / “entregue” | S8 | ✅ por parada da rota |
|
||||
| RF-MOT-05 | Ping de posição (opt-in geolocation) | S8 | ✅ |
|
||||
| RF-MOT-06 | Offline tolerante (fila local → sync) | S8+ | ❌ |
|
||||
|
||||
#### E.2 Aceite
|
||||
|
||||
- `entregue` via parada da rota; cockpit atualiza progresso e frota.
|
||||
- Última parada fecha a rota e devolve motoboy a `casa` se sem fila.
|
||||
|
||||
---
|
||||
|
||||
### Tela F — Ranking de produção (ao vivo)
|
||||
|
||||
**Papel:** gestor / pizzaiolo.
|
||||
**No vídeo:** Cláudio 95, Gil 86… pizzas/noite + tempo médio; remanejo por atraso.
|
||||
|
||||
#### F.1 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint |
|
||||
|----|-----------|--------|
|
||||
| RF-RNK-01 | Bumps KDS creditados ao usuário logado na estação | S4–S5 |
|
||||
| RF-RNK-02 | Ranking do turno: pizzas, tempo médio, atrasos | S5 |
|
||||
| RF-RNK-03 | Tela TV/gestor só leitura (opcional) | S5 |
|
||||
| RF-RNK-04 | Alerta “operador em zona de atraso” (N pedidos late) | S5 |
|
||||
|
||||
#### F.2 Aceite
|
||||
|
||||
- Dois logins na mesma estação não misturam contagem.
|
||||
- Ranking zera/rotaciona por turno (config).
|
||||
|
||||
---
|
||||
|
||||
### Tela G — Funil operacional (métricas)
|
||||
|
||||
**Papel:** gestor.
|
||||
**No vídeo:** tempos produção / forno / estufa / rua; identificar gargalo.
|
||||
|
||||
#### G.1 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint |
|
||||
|----|-----------|--------|
|
||||
| RF-FUN-01 | Timestamps: created, claimed, bumped por estação, estufa, dispatched, delivered | S4 |
|
||||
| RF-FUN-02 | Dashboard: p50/p90 por etapa do funil (dia/turno) | S5 |
|
||||
| RF-FUN-03 | Contagem de cancelamentos do período | S5 |
|
||||
| RF-FUN-04 | Distinto do Pooling comercial (faturado/ticket) — ou abas no mesmo painel gestor | S5 |
|
||||
|
||||
#### G.2 Aceite
|
||||
|
||||
- Gestor responde em < 10 s: “o gargalo agora é forno ou estufa ou rua?”.
|
||||
|
||||
---
|
||||
|
||||
### Tela H — Admin: zonas de entrega
|
||||
|
||||
**Papel:** gestor (Setup).
|
||||
**No CityFoods (site):** áreas/bairros + taxas diferenciadas.
|
||||
|
||||
#### H.1 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint |
|
||||
|----|-----------|--------|
|
||||
| RF-ZON-01 | CRUD zona: nome, polígono ou lista de bairros, taxa cents, tempo estimado | S6–S9 |
|
||||
| RF-ZON-02 | PDV/cardápio aplica taxa pelo bairro/CEP | S9 / C1 |
|
||||
| RF-ZON-03 | Despacho usa zona para agrupamento | S6 |
|
||||
| RF-ZON-04 | Fora de zona → bloqueio ou taxa especial | S9 |
|
||||
|
||||
---
|
||||
|
||||
### Tela I — Admin: relatórios de entrega / equipe
|
||||
|
||||
**Papel:** gestor.
|
||||
**No CityFoods City Pro / Cozinha Digital:** relatórios entregas + entregadores + operação cozinha.
|
||||
|
||||
#### I.1 Requisitos
|
||||
|
||||
| ID | Requisito | Sprint |
|
||||
|----|-----------|--------|
|
||||
| RF-REL-01 | Relatório entregas: qtd, tempo médio, cancelados, por canal | S8 |
|
||||
| RF-REL-02 | Relatório entregadores: entregas, tempo rua, ranking | S8 |
|
||||
| RF-REL-03 | Relatório cozinha: pizza/hora, atrasos por estação | S5 |
|
||||
| RF-REL-04 | Export CSV (padrão estoque) | S5–S8 |
|
||||
|
||||
---
|
||||
|
||||
### Tela J — Integração marketplace (iFood) no despacho
|
||||
|
||||
**Papel:** sistema + despacho.
|
||||
**No vídeo:** cotação API iFood vs própria; ~70% do fluxo.
|
||||
|
||||
#### J.1 Requisitos
|
||||
|
||||
| ID | Requisito | Track |
|
||||
|----|-----------|-------|
|
||||
| RF-IFO-01 | Webhook iFood → Order normalizado (idempotente) | C2 |
|
||||
| RF-IFO-02 | Pedido aparece na Central + KDS + Despacho com canal iFood | C2 |
|
||||
| RF-IFO-03 | Cotação frete marketplace vs frota (UI no batch) | C2 + S7 |
|
||||
| RF-IFO-04 | Sync status pronto/despachado quando política da loja exigir | C2 |
|
||||
|
||||
> Só após Central + despacho interno estáveis (gate SPEC mãe).
|
||||
|
||||
---
|
||||
|
||||
## 3. Mapa tela → sprint (resumo executivo)
|
||||
|
||||
| Tela | Nome | Sprint / track | Prioridade | Status |
|
||||
|------|------|----------------|------------|--------|
|
||||
| **A** | Cockpit Despacho | S4 → S6 | P0 | ✅ |
|
||||
| **B** | Conferência / Estufa | S4 | P0 | ✅ |
|
||||
| **C** | Frota | S6 | P1 | ✅ |
|
||||
| **D** | Gestor de Rotas / mapa | S6 → S7 | P1–P2 | ✅ (ORS opcional) |
|
||||
| **E** | App entregador | S8 | P2 | ✅ parcial |
|
||||
| **F** | Ranking produção | S5 | P1 | ✅ `/ops` |
|
||||
| **G** | Funil operacional | S4–S5 | P1 | ✅ `/ops` |
|
||||
| **H** | Zonas / taxas | S6–S9 | P2 | ✅ CRUD · ❌ taxa no PDV |
|
||||
| **I** | Relatórios | S5–S8 | P2 | ✅ básico |
|
||||
| **J** | iFood no fluxo | C2 | P3 | ❌ |
|
||||
|
||||
---
|
||||
|
||||
## 4. Dados (implementados)
|
||||
|
||||
```
|
||||
Driver { id, storeId, name, phone, active, opsStatus, lastLat?, lastLng?, lastPingAt? }
|
||||
Order + estufaAt, driverId, lat?, lng?, batchId?, dispatchedAt, deliveredAt, priority
|
||||
DeliveryZone { storeId, name, neighborhoods[], feeCents, etaMinutes, active }
|
||||
DispatchBatch {
|
||||
id, storeId, code, driverId?, status: montada|na_rua|fechada,
|
||||
method, distanceKm?, depotLat, depotLng, polyline?,
|
||||
plannedAt, dispatchedAt?, closedAt?
|
||||
}
|
||||
DispatchStop {
|
||||
id, batchId, orderId, seq, lat, lng,
|
||||
status: pendente|a_caminho|entregue, arrivedAt?, deliveredAt?
|
||||
}
|
||||
```
|
||||
|
||||
Ranking usa `PrepTask` (claimedBy + completedAt). Funil usa timestamps do `Order`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Eventos Socket
|
||||
|
||||
| Evento | Quando | Quem escuta |
|
||||
|--------|--------|-------------|
|
||||
| `order.ready` | Estufa / pronto | Despacho |
|
||||
| `order.priority` | Reordenação | KDS + Despacho |
|
||||
| `order.dispatched` | Despacho de rota / SAIR | Despacho + Frota |
|
||||
| `order.delivered` | Entregue | Despacho + Relatórios |
|
||||
| `driver.updated` | casa/rua | Cockpit frota |
|
||||
| `driver.ping` | GPS motoboy | Mapa despacho |
|
||||
| `route.updated` | Montar / parada / fecho | Despacho + Motoboy |
|
||||
| `route.dispatched` | Despachar rota | Despacho + Motoboy |
|
||||
| `route.closed` | Rota fechada | Despacho + Frota |
|
||||
|
||||
---
|
||||
|
||||
## 6. Critério de “mesmo nível” (definição de pronto)
|
||||
|
||||
Paridade operacional com o vídeo **atingida** quando:
|
||||
|
||||
1. ✅ Cockpit A em `/despacho`
|
||||
2. ✅ Conferência B + estufa alimentam o cockpit
|
||||
3. ✅ Frota C no SAIR / despacho de rota
|
||||
4. ✅ Gestor de Rotas D: montar → despachar → mapa permanente
|
||||
5. ✅ Funil G + ranking F em `/ops`
|
||||
6. ✅ App motoboy E consome a **mesma rota**
|
||||
|
||||
**Ainda pode faltar** para paridade comercial City Pro: ORS com chave em produção, PIN/offline motoboy, taxa zona no PDV, iFood cotação, multi-loja — P2+/P3.
|
||||
|
||||
---
|
||||
|
||||
## 7. Fora de escopo (neste SPEC)
|
||||
|
||||
- Programa de fidelidade CityFoods
|
||||
- Pop-ups / destaques cardápio
|
||||
- Caixa completo / fechamento de turno financeiro
|
||||
- Multi-tenant white-label
|
||||
- Fork ou cópia de UI proprietária CityFoods (só modelo operacional)
|
||||
|
||||
---
|
||||
|
||||
## 8. Próximo passo
|
||||
|
||||
1. **P3 / C2 — iFood** no fluxo (webhook → Central/KDS/Despacho)
|
||||
2. Polimento: `ORS_API_KEY` em prod · taxa zona no PDV · PIN motoboy · CSV relatórios
|
||||
|
||||
---
|
||||
|
||||
*Documento derivado do cruzamento vídeo yVtcgJt0L_Q × pizza.itecnologys.com × SPEC §15. v0.2.0 — Gestor de Rotas documentado pós-implementação.*
|
||||
262
docs/SPEC-ESTOQUE.md
Normal file
262
docs/SPEC-ESTOQUE.md
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
# SPEC — Estoque (Insumos & CMV)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Capítulo** | SPEC §7.9 RF-EST (novo) |
|
||||
| **Versão** | 0.1.0 |
|
||||
| **Data** | 2026-10-19 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **SPEC irmã (obrigatória)** | [`SPEC-CARDAPIO.md`](SPEC-CARDAPIO.md) — menu + ficha técnica |
|
||||
| **Dispara baixa** | Order `confirmado` → `em_producao` (Central / PDV / canais) |
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Controlar **o que a cozinha tem** (insumos e embalagens), amarrado ao **o que a loja vende** (cardápio).
|
||||
|
||||
Sem estoque ligado ao cardápio: pedido vende o que não existe → ruptura, CMV cego, pausa manual.
|
||||
|
||||
```text
|
||||
Compra / ajuste ──► Ingredient.stockQty
|
||||
│
|
||||
Pedido (OrderItem) ──BOM──┤
|
||||
▼
|
||||
StockMovement (saída)
|
||||
│
|
||||
▼
|
||||
se qty < min → MenuItem paused
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Escopo
|
||||
|
||||
### Inclui
|
||||
- Cadastro de insumos (`Ingredient`) com unidade e saldo
|
||||
- Ficha técnica (`Recipe`) por `MenuItem` (+ tamanho opcional)
|
||||
- Movimentos: entrada (compra), saída (pedido), ajuste, perda
|
||||
- Alerta mínimo / pausa automática no cardápio
|
||||
- Relatório CMV simples (custo teórico por pedido)
|
||||
- SKU 1:1 para bebidas prontas (refri, energético)
|
||||
|
||||
### Não inclui (fases posteriores)
|
||||
- NFe XML automática / SPED
|
||||
- Multi-depósito / transferência entre lojas
|
||||
- WMS / código de barras avançado
|
||||
- Produção de massa com OP industrial
|
||||
|
||||
---
|
||||
|
||||
## 3. Modelo de dados
|
||||
|
||||
```text
|
||||
Ingredient
|
||||
id, storeId, name, sku?
|
||||
unit ← g | ml | un | kg | L
|
||||
stockQty ← Decimal
|
||||
minQty ← alerta
|
||||
costCents ← custo médio unitário (na unidade base)
|
||||
active
|
||||
|
||||
Recipe
|
||||
id, storeId, menuItemId
|
||||
sizeCode? ← broto|media|grande|gigante|null (= preço base)
|
||||
yieldNote? ← "1 pizza G"
|
||||
lines: RecipeLine[]
|
||||
|
||||
RecipeLine
|
||||
id, recipeId, ingredientId
|
||||
qty ← quantidade na unidade do Ingredient
|
||||
wasteFactor? ← 1.05 = 5% perda
|
||||
|
||||
StockMovement
|
||||
id, storeId, ingredientId
|
||||
kind ← purchase|sale|adjust|waste|cancel_restore
|
||||
qty ← sinal: + entrada / − saída
|
||||
unitCostCents?
|
||||
refType ← order|orderItem|manual|purchase
|
||||
refId?
|
||||
note?, userId?, createdAt
|
||||
|
||||
MenuItem.stockMode ← bom | sku | none (campo no Cardápio)
|
||||
MenuItem.skuIngredientId? ← quando stockMode=sku
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Ponte com Cardápio
|
||||
|
||||
> Contrato: todo item vendável com impacto de estoque **declara** `stockMode` na SPEC-CARDAPIO.
|
||||
|
||||
| `stockMode` | Baixa |
|
||||
|-------------|--------|
|
||||
| `bom` | Soma `RecipeLine.qty × OrderItem.quantity` (meia-a-meia: 0,5 + 0,5) |
|
||||
| `sku` | −1 un do `skuIngredientId` por unidade vendida |
|
||||
| `none` | Sem movimento |
|
||||
|
||||
### Momento da baixa (política piloto)
|
||||
|
||||
| Momento | Ação |
|
||||
|---------|------|
|
||||
| `order` → `em_producao` | **Baixa** (sale) — padrão piloto |
|
||||
| `order` → `cancelado` e ainda não saiu da cozinha | **Estorno** (cancel_restore) |
|
||||
| Após `pronto` | Cancelamento **não** devolve (perda / waste opcional) |
|
||||
|
||||
Alternativa futura: reserva no `confirmado` + baixa no `pronto`.
|
||||
|
||||
### Exemplos Joinville
|
||||
|
||||
**Calabresa G** (`bom`) — pesos em **g** (fonte: [`FICHAS-TECNICAS-PESO.md`](FICHAS-TECNICAS-PESO.md)):
|
||||
| Insumo | Qty |
|
||||
|--------|-----|
|
||||
| Massa (disco) | **480 g** |
|
||||
| Molho tomate | **90 g** |
|
||||
| Mussarela | **200 g** |
|
||||
| Calabresa fatiada | **120 g** |
|
||||
| Cebola | **40 g** |
|
||||
| Orégano | **2 g** |
|
||||
| **Peso montado ≈** | **930 g** |
|
||||
|
||||
**Coca-Cola 2L** (`sku`): −1 un `ingredient:coca-2l`
|
||||
|
||||
**Vitamina Morango** (`bom`):
|
||||
| Insumo | Qty |
|
||||
|--------|-----|
|
||||
| Leite integral | 300 ml |
|
||||
| Morango | 100 g |
|
||||
| Açúcar | 20 g |
|
||||
| Gelo | 100 g |
|
||||
|
||||
### 4.1 Quantização por peso — pizzas (obrigatório)
|
||||
|
||||
> Catálogo completo (38 tipos): [`data/fichas-pizzas-peso.json`](data/fichas-pizzas-peso.json) · resumo [`FICHAS-TECNICAS-PESO.md`](FICHAS-TECNICAS-PESO.md)
|
||||
|
||||
| Regra | Valor |
|
||||
|-------|--------|
|
||||
| Unidade canônica toppings/massa | **gramas (g)** |
|
||||
| Base Grande 40 cm | massa 480 · molho 90 · mussarela 200 |
|
||||
| Base Broto 25 cm | massa 200 · molho 40 · mussarela 85 (≈ 0,42× G) |
|
||||
| Baixa | `Σ (line.qty_g × quantity)` |
|
||||
| Meia-a-meia | massa/molho 100% · toppings 50% de cada sabor |
|
||||
| Cobertura piloto | **38 pizzas** (20 trad. + 5 broto + 6 especiais SC + 7 doces) |
|
||||
|
||||
Cada tipo de pizza **tem ficha própria** — não compartilhar um único “peso médio”. Ajustes por loja na UI sem quebrar o JSON seed.
|
||||
|
||||
---
|
||||
|
||||
## 5. Requisitos (RF-EST)
|
||||
|
||||
| ID | Requisito | Track |
|
||||
|----|-----------|-------|
|
||||
| RF-EST-01 | CRUD insumos (nome, unidade, min, custo) | **E1** |
|
||||
| RF-EST-02 | Ficha técnica por MenuItem (+ size) | **E1** |
|
||||
| RF-EST-03 | Baixa automática no pedido (política §4) | **E1** |
|
||||
| RF-EST-04 | Entrada por compra / ajuste manual | **E1** |
|
||||
| RF-EST-05 | Alerta `stockQty ≤ minQty` (WS + UI gestor) | **E1** |
|
||||
| RF-EST-06 | Pausa automática MenuItem se insumo crítico zerar | **E2** |
|
||||
| RF-EST-07 | Estorno em cancelamento elegível | **E1** |
|
||||
| RF-EST-08 | Relatório CMV teórico por dia / item | **E2** |
|
||||
| RF-EST-09 | Inventário físico (contagem → ajuste) | **E2** |
|
||||
| RF-EST-10 | Perda / desperdício com motivo | **E2** |
|
||||
| RF-EST-11 | Bloquear venda se `strictStock=true` e saldo insuficiente | **E2** |
|
||||
| RF-EST-12 | Auditoria: todo movimento com user + ref | **E1** |
|
||||
| RF-EST-13 | Ficha por tipo de pizza com **qty em gramas** (38+ SKUs menu) | **E1** |
|
||||
| RF-EST-14 | Importar/versionar catálogo `fichas-pizzas-peso.json` | **E1** |
|
||||
| RF-EST-15 | Escala Broto/Grande/Gigante por fator ou ficha dedicada | **E1** |
|
||||
|
||||
---
|
||||
|
||||
## 6. Eventos
|
||||
|
||||
```text
|
||||
stock.low → store:{id} (ingredientId, qty, minQty)
|
||||
stock.updated → store:{id}
|
||||
menu.item.paused → store:{id} (menuItemId, reason=stock)
|
||||
menu.item.resumed → store:{id}
|
||||
```
|
||||
|
||||
PDV / PWA escutam `menu.item.paused|resumed` (SPEC-CARDAPIO RF-MEN-13).
|
||||
|
||||
---
|
||||
|
||||
## 7. Regras de negócio
|
||||
|
||||
1. Unidade canônica por insumo; conversões (kg↔g) só na UI de entrada.
|
||||
2. Custo médio móvel em compras: atualiza `Ingredient.costCents`.
|
||||
3. Meia-a-meia: duas half-recipes; se faltar um sabor, cardápio bloqueia o par.
|
||||
4. Bebidas `sku`: não passam por montagem/forno; só estação `bebidas` + expedição.
|
||||
5. Vitaminas/sucos naturais: baixa no mesmo momento do pedido; se fruta acabou → pausa só o sabor.
|
||||
6. `strictStock` (config loja): default **false** no piloto (alerta sem bloquear); true em produção madura.
|
||||
|
||||
---
|
||||
|
||||
## 8. Track E — implementação
|
||||
|
||||
| ID | Entrega | Dependência |
|
||||
|----|---------|-------------|
|
||||
| **E0** | Este SPEC + link no SPEC.md / Cardápio | ✅ agora |
|
||||
| **E1** | Prisma Ingredient/Recipe/Movement + baixa no create/transition | **feito 2026-10-20** |
|
||||
| **E2** | Pausa auto + CMV + inventário UI | E1 · Central estável |
|
||||
| **E3** | Compras com custo médio + multi-loja | Fase 3 |
|
||||
|
||||
**Ordem sugerida com Cardápio:** M0 (menu Joinville) → **E1** → M1/M2 (admin ficha) → E2.
|
||||
|
||||
---
|
||||
|
||||
## 9. Critérios de aceite
|
||||
|
||||
1. Cadastro de mussarela / calabresa / Coca 2L com saldo e mínimo.
|
||||
2. Ficha Calabresa G salva e listável a partir do MenuItem.
|
||||
3. Criar pedido com 1× Calabresa G reduz mussarela em 200 g (ou qty da ficha).
|
||||
4. Criar pedido com 1× Coca 2L reduz 1 un do SKU.
|
||||
5. Cancelar pedido em produção devolve saldo.
|
||||
6. Mussarela em 0 → itens BOM que usam mussarela pausam (E2) ou alerta (E1).
|
||||
7. Movimentos auditáveis com `refType=order`.
|
||||
|
||||
---
|
||||
|
||||
## 10. UI alvo (gestor)
|
||||
|
||||
| Tela | Rota (proposta) | Função |
|
||||
|------|-----------------|--------|
|
||||
| Insumos | `/estoque` | Lista + saldo + min |
|
||||
| Ficha técnica | `/cardapio/:itemId/ficha` | BOM editor |
|
||||
| Movimentos | `/estoque/movimentos` | Extrato |
|
||||
| Inventário | `/estoque/inventario` | Contagem |
|
||||
|
||||
Papel: `gestor` (escrita) · `pdv` (leitura alertas).
|
||||
|
||||
---
|
||||
|
||||
## 11. Fora de escopo
|
||||
|
||||
- Integração contábil completa
|
||||
- Validade / lote FEFO (fase futura)
|
||||
- App de compras do fornecedor
|
||||
|
||||
---
|
||||
|
||||
## 12. Glossário cruzado
|
||||
|
||||
| Termo | Onde |
|
||||
|-------|------|
|
||||
| **MenuItem** | SPEC-CARDAPIO — o que vende |
|
||||
| **Recipe / BOM** | Esta SPEC — como consome |
|
||||
| **Ingredient** | Esta SPEC — o que tem |
|
||||
| **stockMode** | Cardápio declara · Estoque executa |
|
||||
| **Order** | SPEC principal / Central — dispara baixa |
|
||||
|
||||
---
|
||||
|
||||
*SPEC satélite. Versão do produto pai: SPEC.md. Cruzamento obrigatório com SPEC-CARDAPIO.*
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 13. Implementação E1 (2026-10-20)
|
||||
|
||||
Entregue: `stockQty` · `StockMovement` · `strictStock` · `/admin/estoque` · baixa em produção · estorno no cancelamento · RF-EST-11.
|
||||
|
||||
`strictStock` default **false** (piloto). Gestor liga em `/admin/estoque` para bloquear pedido sem saldo.
|
||||
232
docs/SPEC-FASE1.md
Normal file
232
docs/SPEC-FASE1.md
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
# SPEC Fase 1 — MVP Cozinha Digital (detalhamento)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Fase** | 1 — MVP Cozinha Digital |
|
||||
| **Versão** | 0.2.0 (ampliação da SPEC 0.1) |
|
||||
| **Data** | 2026-07-23 |
|
||||
| **Host de desenvolvimento** | Proxmox BIG · VM **125** · `pizza-digital` · `10.10.10.125` |
|
||||
| **SO** | Debian 12 |
|
||||
| **Users** | `mini` (sudo) · `root` |
|
||||
| **Referência rotas (fase 2)** | **rusterando** (oficial) + CityFoods Gestor de Rotas / ORS |
|
||||
| **Referência KDS** | CityFoods Cozinha Digital + kitchenOS / restaurant-pos |
|
||||
| **Sequência** | SPEC §15 — S0…S9 · KDS antes de rotas · S1 ✅ · próximo S2 |
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo da Fase 1
|
||||
|
||||
Entregar uma **Cozinha Digital operacional** para pizzaria piloto:
|
||||
|
||||
1. PDV web cria pedido
|
||||
2. KDS multi-estação (Massa → Montagem → Forno → Expedição)
|
||||
3. Timers / priorização
|
||||
4. Etiqueta (ESC/POS ou PDF)
|
||||
5. Dashboard de tempos
|
||||
6. Gate para despacho (`order.ready`) — **sem** otimização de rotas ainda (Fase 2)
|
||||
|
||||
**Fora da Fase 1:** app motoboy, ORS, iFood, cardápio público, multi-loja.
|
||||
|
||||
---
|
||||
|
||||
## 2. Infraestrutura (BIG)
|
||||
|
||||
| Item | Valor |
|
||||
|------|--------|
|
||||
| Hypervisor | Proxmox BIG (`big1-proxmox` / `95.216.14.162`) |
|
||||
| VMID | **125** |
|
||||
| Nome | `pizza-digital` |
|
||||
| Bridge | `vmbr4000` |
|
||||
| IP LAN | `10.10.10.125/24` |
|
||||
| Gateway | `10.10.10.1` (pfSense) |
|
||||
| DNS | `8.8.8.8` / `8.8.4.4` |
|
||||
| vCPU | 4 |
|
||||
| RAM | 8 GB |
|
||||
| Disco | 80 GB |
|
||||
| Cloud-init | `local:snippets/vm125-pizza-digital-init.yaml` |
|
||||
| Hardening | fail2ban (sshd) · PasswordAuthentication yes · PermitRootLogin yes |
|
||||
| Stack base | Docker + Compose · Node 22 · pnpm · Git · build-essential · nginx · redis-tools · psql client |
|
||||
|
||||
### 2.1 Acesso
|
||||
|
||||
```text
|
||||
# Via Proxmox BIG (LAN)
|
||||
ssh mini@10.10.10.125
|
||||
|
||||
# Ou jump pelo host
|
||||
ssh big1-proxmox
|
||||
ssh mini@10.10.10.125
|
||||
```
|
||||
|
||||
SSH externo (porta NAT tipo 2525) **não** está nesta fase — exige regra no pfSense + FORWARD no Proxmox (**não** alterar `/etc/network/interfaces` sem confirmação ×3).
|
||||
|
||||
### 2.2 Workspace na VM
|
||||
|
||||
```text
|
||||
/home/mini/pizza-digital/ # monorepo
|
||||
/opt/pizza-digital/ # dados / runtime
|
||||
/opt/pizza-digital/data/ # volumes locais
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Entregáveis Fase 1 (Definition of Done)
|
||||
|
||||
| # | Entregável | Critério de aceite |
|
||||
|---|------------|--------------------|
|
||||
| 1 | Monorepo bootstrap | `apps/api`, `apps/web`, `packages/domain`, `docker-compose.yml` |
|
||||
| 2 | Auth + RBAC mínimo | login gestor / pdv / kds |
|
||||
| 3 | Cardápio | CRUD item + modifiers + meia-a-meia + mapa → estações |
|
||||
| 4 | PDV | criar pedido delivery/balcão com observação |
|
||||
| 5 | Order state machine | confirmado → em_producao → pronto |
|
||||
| 6 | KDS multi-estação | 4 telas; WebSocket ≤ 500 ms |
|
||||
| 7 | Timers SLA | verde/amarelo/vermelho |
|
||||
| 8 | Priorização | reordenar / marcar urgente |
|
||||
| 9 | Etiquetas | imprimir ou PDF com `#CODE` + obs |
|
||||
| 10 | Dashboard | tempo médio por estação + fluxo do pedido |
|
||||
| 11 | Seed pizzaria | dados demo (sabores, bordas, bebidas) |
|
||||
|
||||
---
|
||||
|
||||
## 4. Estações (template Pizzaria) — freeze Fase 1
|
||||
|
||||
| Código | Nome | SLA default | Recebe |
|
||||
|--------|------|-------------|--------|
|
||||
| `massa` | Massa | 3 min | base / borda |
|
||||
| `montagem` | Montagem | 5 min | molho, queijo, toppings, meia-a-meia |
|
||||
| `forno` | Forno | 8 min | pizza assando |
|
||||
| `expedicao` | Expedição | 3 min | embalagem + etiqueta + conferência |
|
||||
| `bebidas` | Bebidas (opc.) | 2 min | refrigerante / água |
|
||||
|
||||
Regra: pedido só `pronto` quando **todas** as estações obrigatórias do pedido concluírem; expedição é a última.
|
||||
|
||||
---
|
||||
|
||||
## 5. Stack Fase 1 (congelada para implementação)
|
||||
|
||||
| Camada | Escolha |
|
||||
|--------|---------|
|
||||
| API | **NestJS** + TypeScript |
|
||||
| Web | **React** + Vite + TypeScript (PWA) |
|
||||
| Realtime | **Socket.io** |
|
||||
| DB | **PostgreSQL 16** (Docker) |
|
||||
| Cache/pubsub | **Redis 7** (Docker) |
|
||||
| ORM | Prisma |
|
||||
| Print | ESC/POS node + PDF fallback |
|
||||
| Deploy local | Docker Compose na VM 125 |
|
||||
|
||||
Referências de implementação:
|
||||
|
||||
- KDS realtime → kitchenOS / Tasty-Station
|
||||
- Estações → restaurant-pos
|
||||
- Rotas (só na Fase 2) → rusterando + OpenRouteService
|
||||
|
||||
---
|
||||
|
||||
## 6. Apps / rotas web (Fase 1)
|
||||
|
||||
| App | Path | Papel |
|
||||
|-----|------|-------|
|
||||
| PDV | `/pdv` | Caixa |
|
||||
| KDS | `/kds/:station` | Cozinha |
|
||||
| Despacho (lista) | `/despacho` | Fila `pronto` (sem ORS) |
|
||||
| Admin | `/admin` | Cardápio, users, métricas |
|
||||
|
||||
---
|
||||
|
||||
## 7. Eventos WebSocket (Fase 1)
|
||||
|
||||
```text
|
||||
order.created
|
||||
order.updated
|
||||
order.canceled
|
||||
ticket.upserted
|
||||
ticket.bumped
|
||||
order.ready
|
||||
label.printed
|
||||
```
|
||||
|
||||
Rooms: `store:{id}` · `store:{id}:station:{stationId}`
|
||||
|
||||
---
|
||||
|
||||
## 8. Módulo Rotas — ponte para Fase 2 (não implementar agora)
|
||||
|
||||
Contrato mínimo que a Fase 1 **já deve expor**:
|
||||
|
||||
```text
|
||||
order.status === 'pronto'
|
||||
→ aparece em /despacho
|
||||
→ payload inclui: address, bairro, lat/lng (se houver), priority
|
||||
```
|
||||
|
||||
Na Fase 2 (CityFoods-like):
|
||||
|
||||
1. Agrupar por bairro
|
||||
2. Atribuir motoboy
|
||||
3. ORS tour (rusterando)
|
||||
4. PWA entregador + tracking
|
||||
5. Comissão entregador
|
||||
|
||||
---
|
||||
|
||||
## 9. Sprints Fase 1 (na VM 125)
|
||||
|
||||
| Sprint | Dias úteis | Entrega |
|
||||
|--------|------------|---------|
|
||||
| **S0** | 1 | Repo + Docker Compose (Postgres/Redis) + domain types |
|
||||
| **S1** | 2 | Auth + Store + Cardápio + estações |
|
||||
| **S2** | 2 | Pedidos PDV + state machine |
|
||||
| **S3** | 3 | KDS realtime multi-estação + timers |
|
||||
| **S4** | 2 | Etiquetas + priorização + dashboard |
|
||||
| **S5** | 2 | Seed + teste piloto + hardening |
|
||||
|
||||
---
|
||||
|
||||
## 10. Segurança na VM (Fase 1)
|
||||
|
||||
- [x] fail2ban sshd
|
||||
- [x] PasswordAuthentication + root (padrão iTecnologys / cloud-init)
|
||||
- [ ] UFW: allow 22/tcp, 80/tcp, 443/tcp, 3000/tcp (dev) — aplicar após bootstrap
|
||||
- [ ] Não expor Postgres/Redis na WAN
|
||||
- [ ] Secrets em `.env` (não commit)
|
||||
|
||||
---
|
||||
|
||||
## 11. Checklist de bootstrap VM 125
|
||||
|
||||
- [x] VM criada e running (VMID 125, Debian 12, 4 vCPU / 8 GB / 80 GB)
|
||||
- [x] Ping `10.10.10.125` a partir do BIG (`vmbr4000`)
|
||||
- [x] SSH `root@` / `mini@10.10.10.125` OK (senha `805353`)
|
||||
- [x] Docker Compose S0: `postgres` + `redis` + `api` + `web` + `gateway` (:3000) UP
|
||||
- [x] `GET /api/health` → redis up · `GET /` → 200
|
||||
- [x] `fail2ban` sshd ativo
|
||||
- [x] Docker + Compose + Node 22 + pnpm + TypeScript/tsx
|
||||
- [x] `sshd -T` → `passwordauthentication yes` · `permitrootlogin yes`
|
||||
- [x] Workspace `/home/mini/pizza-digital` com SPEC
|
||||
- [ ] Monorepo código (S0) — próximo passo
|
||||
- [ ] SSH externo NAT (porta dedicada) — opcional; **não** altera `/etc/network/interfaces` sem 3 confirmações
|
||||
|
||||
### Acesso rápido
|
||||
|
||||
```bash
|
||||
# Do Windows (ProxyJump)
|
||||
ssh pizza-digital
|
||||
|
||||
# Do BIG Proxmox (necessário route helper se dual-bridge)
|
||||
/usr/local/bin/pizza-digital-route.sh
|
||||
ssh mini@10.10.10.125
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. Changelog desta ampliação
|
||||
|
||||
### 0.2.0 — 2026-07-23
|
||||
|
||||
- Detalhamento completo da **Fase 1**
|
||||
- Infra VM 125 no Proxmox BIG documentada
|
||||
- Stack congelada (NestJS + React + Postgres + Redis + Socket.io)
|
||||
- Definition of Done e sprints S0–S5
|
||||
- Ponte explícita para Gestor de Rotas (Fase 2 / CityFoods + rusterando)
|
||||
79
docs/SPEC-KDS-CLAIM.md
Normal file
79
docs/SPEC-KDS-CLAIM.md
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
# SPEC — KDS PEGAR (claim) + pooling
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Capítulo** | RF-KDS claim exclusivo |
|
||||
| **Versão** | 0.2.0 |
|
||||
| **Data** | 2026-10-20 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **UI** | `/kds/:station` |
|
||||
| **API** | `POST /api/kds/:station/claim/:orderId` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Na fila comum, o operador **não puxa o ticket no primeiro clique**. PEGAR abre confirmação com:
|
||||
|
||||
- **nome do operador** logado
|
||||
- **nome da dona da pizza** (cliente)
|
||||
- pedido `#código`, estação, itens
|
||||
|
||||
Só `Confirmar — é minha` dispara o claim exclusivo.
|
||||
|
||||
---
|
||||
|
||||
## 2. Regras
|
||||
|
||||
1. Fila comum: todos veem tickets `view=pool` (`canClaim`).
|
||||
2. Clique PEGAR → modal; **não** chama a API ainda.
|
||||
3. Confirmar → `POST …/claim/:orderId` (uma estação ganha; demais veem bloqueado).
|
||||
4. Cancelar / clique fora fecha o modal.
|
||||
5. Ticket mostra pill **Pizza de {primeiro nome}**.
|
||||
6. Contadores: fila comum / comigo / outras estações.
|
||||
|
||||
---
|
||||
|
||||
## 3. Pooling (tempo real + backup)
|
||||
|
||||
| Fonte | Comportamento |
|
||||
|-------|----------------|
|
||||
| Socket.io | `order.*` / `ticket.bumped` com debounce 180 ms |
|
||||
| Poll backup | 10 s se socket ao vivo; **3 s** se offline |
|
||||
| Aba oculta | não polla |
|
||||
| Foco / visível | recarrega na hora |
|
||||
|
||||
RF-KDS-02 (WS sem polling) continua o caminho feliz; o poll é **rede de segurança** para TV/tablet com socket instável.
|
||||
|
||||
---
|
||||
|
||||
## 4. Copy da confirmação
|
||||
|
||||
```text
|
||||
Confirmar PEGAR
|
||||
Pedido #A12
|
||||
{Operador} vai pegar a pizza de {Dona}?
|
||||
|
||||
Operador {user.name}
|
||||
Dona da pizza {shortCustomerName}
|
||||
Estação Massa | Montagem | …
|
||||
Itens 1× Calabresa G, …
|
||||
|
||||
[Cancelar] [Confirmar — é minha]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Requisitos
|
||||
|
||||
| ID | Requisito | Status |
|
||||
|----|-----------|--------|
|
||||
| RF-KDS-C1 | Claim exclusivo por estação (0.2.4) | **feito** |
|
||||
| RF-KDS-C2 | Modal PEGAR com operador + dona da pizza | **feito** |
|
||||
| RF-KDS-C3 | Poll backup + debounce + visibility | **feito** |
|
||||
| RF-KDS-C4 | Contadores de pool / mine / locked | **feito** |
|
||||
|
||||
---
|
||||
|
||||
*Satélite. Claim original: CHANGELOG 0.2.4. Produto pai: SPEC.md v0.3.5.*
|
||||
221
docs/SPEC-KDS-PIZZAIOLO-MODELO-B.md
Normal file
221
docs/SPEC-KDS-PIZZAIOLO-MODELO-B.md
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
# SPEC — KDS Operacional do Pizzaiolo (Modelo B)
|
||||
|
||||
**Status:** aprovado para implementação
|
||||
**Produto:** Pizzaza / Pizza Digital
|
||||
**Data:** 2026-10-25
|
||||
**Referência visual:** mock operacional (cards por pizza, áreas coloridas, botões grandes)
|
||||
**Arquitetura de fluxo:** Modelo B — montagem e forno com responsáveis independentes
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Separar completamente:
|
||||
|
||||
| Camada | Público | Natureza |
|
||||
|--------|---------|----------|
|
||||
| **KDS operacional (pizzaiolo)** | Role `kds` (e futuros papéis montagem/forno) | Estação de produção touch, unidade = **pizza** |
|
||||
| **KDS / Ops administrativo** | `gestor` / admin | Kanban de pedidos, Pooling, faturamento, metas |
|
||||
|
||||
O pizzaiolo **não** vê Pedidos, PDV, Pooling financeiro, relatórios nem configurações.
|
||||
Esconder menu não basta: rotas e APIs administrativas devem rejeitar role `kds`.
|
||||
|
||||
---
|
||||
|
||||
## 2. Unidade de trabalho
|
||||
|
||||
- **Unidade:** `PrepTask` (uma pizza / unidade de preparo).
|
||||
- Um pedido com N pizzas pode ter pizzas em estágios e com pizzaiolos diferentes.
|
||||
- Marcar pizza pronta **não** marca o pedido pronto.
|
||||
- Pedido só segue para liberação quando **todas** as pizzas e itens obrigatórios (bebidas, sobremesas, adicionais) forem conferidos na expedição.
|
||||
|
||||
Identificação no card: `#PEDIDO · Pizza seq/total` (ex.: `#N16 · Pizza 1/2`).
|
||||
|
||||
---
|
||||
|
||||
## 3. Estados da pizza (máquina oficial)
|
||||
|
||||
```
|
||||
disponivel
|
||||
│ PEGAR
|
||||
▼
|
||||
em_montagem (responsável montagem)
|
||||
│ FINALIZAR MONTAGEM
|
||||
▼
|
||||
aguardando_forno (sem forneiro ainda)
|
||||
│ ASSUMIR FORNO (+ INICIAR FORNO)
|
||||
▼
|
||||
no_forno (responsável forno)
|
||||
│ MARCAR PRONTA
|
||||
▼
|
||||
pronta → fila de expedição (automático)
|
||||
```
|
||||
|
||||
### Transições e botões
|
||||
|
||||
| Botão | De → Para | Efeito |
|
||||
|-------|-----------|--------|
|
||||
| **PEGAR** | disponivel → em_montagem | Atribui montagem ao usuário; inicia cronômetro da etapa; atômico no servidor |
|
||||
| **DEVOLVER À FILA** | em_montagem → disponivel | Remove responsável; preserva tempo já consumido; motivo opcional/obrigatório conforme config |
|
||||
| **FINALIZAR MONTAGEM** / **ENVIAR AO FORNO** | em_montagem → aguardando_forno | Encerra tempo de montagem; **não** atribui forneiro |
|
||||
| **ASSUMIR FORNO** | aguardando_forno → no_forno (aguardando início) | Identifica forneiro |
|
||||
| **INICIAR FORNO** | (opcional explícito) marca `fornoStartedAt` | Se omitido na v1, ASSUMIR FORNO também inicia cocção |
|
||||
| **MARCAR PRONTA** | no_forno → pronta | Encerra cocção; envia à expedição automaticamente |
|
||||
| **TRANSFERIR** | qualquer etapa atribuída | Troca responsável com motivo; histórico preservado |
|
||||
| **REPORTAR PROBLEMA** | qualquer | Registra ocorrência sem apagar histórico |
|
||||
|
||||
### Dupla atribuição
|
||||
|
||||
Claim/assume é **atômico** (`UPDATE … WHERE status = X AND claimedById IS NULL`).
|
||||
Se dois usuários competirem, só um vence; o outro recebe conflito 409.
|
||||
|
||||
---
|
||||
|
||||
## 4. Layout do KDS pizzaiolo (Kanban operacional)
|
||||
|
||||
Quatro colunas Kanban (cards grandes, touch), unidade = pizza:
|
||||
|
||||
1. **Disponíveis** — sem responsável → **PEGAR**
|
||||
2. **Montagem** — em preparo → **ENVIAR AO FORNO** (+ Devolver)
|
||||
3. **Forno** — fila (`aguardando_forno`) + cocção (`no_forno`) → **PRONTA → CHECAGEM**
|
||||
Qualquer pizzaiolo/forneiro autorizado pode marcar pronta (não só quem assumiu).
|
||||
4. **Checagem** — pizzas já prontas na 1ª checagem; daí seguem para **expedição** (etiqueta/embalagem/liberação)
|
||||
|
||||
### Fluxo Forno → Checagem → Expedição
|
||||
|
||||
`PRONTA` na coluna Forno encerra a cocção e coloca a pizza na **primeira checagem**.
|
||||
Quando todas as pizzas do pedido estiverem prontas, o pedido fica `pronto` (pronto cozinha) para a expedição conferir, etiquetar e liberar.
|
||||
|
||||
### O que NÃO aparece para o pizzaiolo
|
||||
|
||||
- Pooling / faturamento / ticket médio
|
||||
- Coluna Expedição administrativa
|
||||
- Links Pedidos, PDV, Setup, Demo, Relatórios
|
||||
- Indicadores financeiros
|
||||
|
||||
### Header operacional
|
||||
|
||||
`Pizzaza` · badge `KDS` · `Cozinha` · AO VIVO · hora · nome do usuário
|
||||
Sem menu administrativo completo.
|
||||
|
||||
### Rodapé
|
||||
|
||||
“Pizzas concluídas recentemente” — thumbs + código + horário + nome (não pedidos financeiros).
|
||||
|
||||
---
|
||||
|
||||
## 5. Conteúdo do card (pizza)
|
||||
|
||||
- Código do pedido + fração `seq/total`
|
||||
- Badge do estágio + tempo total (vermelho se atraso)
|
||||
- Tempo da etapa atual (quando atribuída)
|
||||
- Foto realista do produto
|
||||
- Nome / tamanho / componentes (metades, bordas)
|
||||
- Canal / cliente / bairro (compacto)
|
||||
- Responsável montagem e/ou forno (iniciais + nome), sem sobrescrever o outro
|
||||
- Botão primário full-width da etapa + secundário (Devolver / Transferir / Ver pedido)
|
||||
- Trilho lateral grosso na cor da **etapa** (não do canal financeiro): azul disponível, laranja montagem, âmbar fila forno, vermelho forno, verde pronta
|
||||
|
||||
Ordenação: prioridade operacional (atraso, tempo de espera, horário prometido) — não só `createdAt`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Cronômetros
|
||||
|
||||
- **Tempo total:** `now - order.arrivedAt|queuedAt` (servidor)
|
||||
- **Tempo da etapa:** `now - stageStartedAt` da etapa atual
|
||||
- Cálculo sempre a partir de timestamps do servidor (sobrevive a refresh/offline)
|
||||
|
||||
Metas por etapa (config gestor, defaults):
|
||||
|
||||
| Etapa | Meta sugerida |
|
||||
|-------|----------------|
|
||||
| Montagem | 6–8 min |
|
||||
| Espera forno | 3 min |
|
||||
| Forno | 8–12 min |
|
||||
| Total produção | 15–20 min |
|
||||
|
||||
Atraso: destaque visual automático ao ultrapassar meta.
|
||||
|
||||
---
|
||||
|
||||
## 7. Perfis e permissões
|
||||
|
||||
| Ação | Pizzaiolo montagem | Forneiro | Expedição | Gestor |
|
||||
|------|--------------------|----------|-----------|--------|
|
||||
| Ver disponíveis / pegar | Sim | Não* | Não | Sim |
|
||||
| Finalizar montagem | Sim (suas) | Não | Não | Sim |
|
||||
| Assumir / marcar forno | Não* | Sim | Não | Sim |
|
||||
| Ver responsável | Sim | Sim | Sim | Sim |
|
||||
| Transferir | Restrito | Restrito | — | Sim |
|
||||
| Pooling / Pedidos / PDV | Não | Não | Não | Sim |
|
||||
| Conferir / etiqueta / liberar | Não | Não | Sim | Sim |
|
||||
|
||||
\* Mesmo usuário pode ter as duas caps; registros de montagem e forno permanecem separados.
|
||||
|
||||
### Backend
|
||||
|
||||
- `GET/POST` administrativos (`/api/orders/stats/today`, etc.) → 403 para `kds`
|
||||
- `GET /pedidos` (web) → redirect `/kds/producao` para role `kds`
|
||||
- Nav: pizzaiolo só vê KDS (+ logout)
|
||||
|
||||
---
|
||||
|
||||
## 8. Expedição (fora da tela do pizzaiolo)
|
||||
|
||||
Ao **MARCAR PRONTA**, a pizza entra na fila de expedição automaticamente.
|
||||
Expedição: conferência, etiqueta, embalagem, liberação do **pedido completo** (todas as pizzas + itens).
|
||||
O pizzaiolo não usa botão “Enviada p/ Expedição” manual.
|
||||
|
||||
---
|
||||
|
||||
## 9. API proposta (v1)
|
||||
|
||||
Prefixo: `/api/kds/pizzas`
|
||||
|
||||
| Método | Rota | Descrição |
|
||||
|--------|------|-----------|
|
||||
| GET | `/board` | Board pizzaiolo: disponíveis, montagem, aguardando_forno, no_forno, recentes |
|
||||
| POST | `/:taskId/pegar` | disponivel → em_montagem |
|
||||
| POST | `/:taskId/devolver` | em_montagem → disponivel (+ motivo) |
|
||||
| POST | `/:taskId/enviar-forno` | em_montagem → aguardando_forno |
|
||||
| POST | `/:taskId/assumir-forno` | aguardando_forno → no_forno |
|
||||
| POST | `/:taskId/marcar-pronta` | no_forno → pronta (+ evento expedição) |
|
||||
| POST | `/:taskId/transferir` | troca responsável (+ motivo) |
|
||||
|
||||
Eventos socket: `pizza.updated`, `pizza.claimed`, `pizza.ready` (payload por PrepTask).
|
||||
|
||||
### Modelo de dados (evolução)
|
||||
|
||||
`PrepTaskStatus`: adicionar `aguardando_forno`, `no_forno` (manter `em_preparo` = em_montagem).
|
||||
Campos: `montagemById`, `montagemStartedAt`, `montagemDoneAt`, `fornoById`, `fornoStartedAt`, `fornoDoneAt`.
|
||||
Histórico: tabela `PrepTaskEvent` (opcional v1.1) ou log append-only.
|
||||
|
||||
---
|
||||
|
||||
## 10. Critérios de aceitação
|
||||
|
||||
1. Role `kds` não vê menus/admin/Pooling/Pedidos; URL `/pedidos` redireciona; API stats 403.
|
||||
2. Tela abre direto na produção autorizada.
|
||||
3. Cards são **pizzas**, com foto, 2 cronômetros e responsável(is).
|
||||
4. PEGAR / ENVIAR AO FORNO / ASSUMIR FORNO / MARCAR PRONTA seguem a máquina de estados.
|
||||
5. Fila aguardando forno ≠ cocção iniciada.
|
||||
6. Dupla atribuição impossível (409).
|
||||
7. MARCAR PRONTA encaminha à expedição sem liberar pedido incompleto.
|
||||
8. Touch: botões grandes, contraste alto, atualização realtime.
|
||||
9. Modelo B: montagem e forno com responsáveis e tempos independentes.
|
||||
|
||||
---
|
||||
|
||||
## 11. Fora de escopo desta etapa
|
||||
|
||||
- QR/etiqueta individual por pizza (recomendado v2)
|
||||
- Relatórios de produtividade detalhados (gestor)
|
||||
- App nativo; esta SPEC é web touch KDS
|
||||
|
||||
---
|
||||
|
||||
## 12. Relação com o KDS gestor
|
||||
|
||||
O Kanban de pedidos (Novo / Montagem / Forno / Prontas / Expedição) permanece disponível **apenas** para gestor/admin como visão de supervisão.
|
||||
Não é a home do pizzaiolo.
|
||||
107
docs/SPEC-LACUNAS-VIDEO-SIRIFOOD.md
Normal file
107
docs/SPEC-LACUNAS-VIDEO-SIRIFOOD.md
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
# SPEC-LACUNAS-VIDEO-SIRIFOOD — Pizza Digital vs operação filmada
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **ID** | SPEC-LACUNAS-VIDEO-SIRIFOOD |
|
||||
| **Versão** | 0.3.7 |
|
||||
| **Data** | 2026-10-20 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **Referência** | [Vídeo Siciliana / SiriFood](https://www.youtube.com/watch?v=yVtcgJt0L_Q) |
|
||||
| **Ambiente comparado** | `https://pizza.itecnologys.com` · VM 125 · piloto Joinville |
|
||||
| **Status** | Diagnóstico · **não implementado** (exceto o que já está no ar, marcado abaixo) |
|
||||
|
||||
## 0. Leitura do vídeo
|
||||
|
||||
Operação **Siciliana Palhoça** (SiriFood): ~R$ 1 mi/mês, ~70% iFood, três lojas no mesmo sistema. O **despacho** é o coração: produção, forno, conferência, estufa, motoboy e cotação iFood giram em torno dessa tela.
|
||||
|
||||
Case citado: ~13 cancelamentos/mês e entrega abaixo de 30 min.
|
||||
|
||||
## 1. O que o piloto já cobre
|
||||
|
||||
- KDS multi-estação (massa, montagem, forno, expedição, bebidas)
|
||||
- PEGAR com nome do operador e do cliente — **no pedido inteiro**, não na pizza
|
||||
- PDV + filtros de origem (aplicativo, iFood, WhatsApp, telefone, PDV)
|
||||
- Etiqueta **por pedido** e impressão no BUMP
|
||||
- Despacho = lista ao vivo de prontos + botão despachar
|
||||
- Estoque com baixa pela ficha técnica
|
||||
- 20 funcionários com nome composto e 8 perfis (gestor, atendente, administrativo, pizzaiolo, conferente, expedição, estoque, motoboy)
|
||||
- Navegação SAGE em todas as páginas
|
||||
|
||||
Isso **não** equivale à operação do vídeo. Cargo no login ≠ recurso ao vivo.
|
||||
|
||||
## 2. Lacunas (RF-GAP)
|
||||
|
||||
Cada linha é trabalho a construir. P0 = sem isso o vídeo continua irreconhecível na nossa UI.
|
||||
|
||||
| ID | No vídeo | No Pizza Digital hoje | Prioridade |
|
||||
|----|----------|------------------------|------------|
|
||||
| **RF-GAP-01** | 3 lojas no mesmo sistema | 1 loja piloto | P2 |
|
||||
| **RF-GAP-02** | Despacho centro de comando (canal, nº, tempo na casa, estatísticas) | Lista de prontos + despachar | P0 |
|
||||
| **RF-GAP-03** | Motoboy ao vivo: casa / rua / nº de entregas | 3 logins, sem status | P0 |
|
||||
| **RF-GAP-04** | Reordenar fila no despacho e o KDS segue na hora | FIFO + PEGAR; gestor não reordena | P0 |
|
||||
| **RF-GAP-05** | Semáforo: vermelho chegou · laranja fazendo · amarelo no forno | Status de pedido + estações, sem esse semáforo no despacho | P0 |
|
||||
| **RF-GAP-06** | Agrupar pedidos da mesma região para saírem do forno juntos | Não existe (rotas = S6 / rusterando) | P1 |
|
||||
| **RF-GAP-07** | Cotação iFood API: iFood × própria × sob demanda × frota | Canal iFood só como filtro | P1 |
|
||||
| **RF-GAP-08** | Conferência com login próprio: identifica sabor, imprime etiqueta, marca estufa | Perfil Conferente cadastrado; fluxo inexistente | P0 |
|
||||
| **RF-GAP-09** | Estufa com pedidos etiquetados à espera do motoboy | Sem estado `na_estufa` | P0 |
|
||||
| **RF-GAP-10** | Ranking ao vivo: pizzas/noite, tempo/pizza, média do pizzaiolo | Sem produtividade por operador | P1 |
|
||||
| **RF-GAP-11** | Gráfico de atraso + remanejar gente para o gargalo | Pooling = faturamento, não gargalo | P1 |
|
||||
| **RF-GAP-12** | Mapa do pedido: chegou → produção → forno → estufa → rua → entregue | Sem linha do tempo por estágio | P1 |
|
||||
| **RF-GAP-13** | Tempos: produção, forno, parado na estufa, na rua | SLA por estação no KDS; sem dwell estufa/rua | P1 |
|
||||
| **RF-GAP-14** | Preparo item a item (sabor conferido) | PEGAR trava o **pedido inteiro** | P0 |
|
||||
| **RF-GAP-15** | Etiqueta com nº + sabores na conferência | 1 etiqueta/pedido; falta 01/N por pizza + etiqueta-mãe | P0 |
|
||||
| **RF-GAP-16** | Despacho híbrido (frota, casa, iFood Entrega) | Não iniciado | P2 |
|
||||
| **RF-GAP-17** | KPI: cancelamentos, entrega < 30 min | Só faturado hoje | P1 |
|
||||
|
||||
## 3. P0 — o que construir primeiro ( Specs de trabalho )
|
||||
|
||||
### 3.1 Preparo colaborativo — RF-GAP-14 / RF-GAP-15
|
||||
|
||||
Níveis: **Pedido** (prazo) · **Tarefa de preparo** (pizza física + responsável) · **Expedição** (conjunto).
|
||||
|
||||
- Modal PEGAR lista pizzas (metades, observações). Operador marca as que assume.
|
||||
- Ação: `Assumir N pizzas`. Aceite comercial ≠ atribuição de preparo.
|
||||
- Trava atômica **da tarefa**, não do pedido. Dois PEGAR na mesma pizza: um ganha.
|
||||
- Transferência explícita + histórico. Fechar modal não libera pizza em preparo.
|
||||
- Pedido só `pronto` quando todas as tarefas obrigatórias concluem.
|
||||
- Etiqueta por pizza `PEDIDO #024 · PIZZA 02/04` + etiqueta-mãe de conferência.
|
||||
- Reimprimir não cria tarefa nem baixa estoque de novo.
|
||||
- A partir de 3 pizzas: sugerir “Dividir preparo com a equipe”.
|
||||
- Forno conjunto (janela de entrada) = evolução, não MVP.
|
||||
|
||||
### 3.2 Conferência + estufa — RF-GAP-08 / RF-GAP-09
|
||||
|
||||
- Login conferente (já existe Patrícia Nunes / Rafael Costa).
|
||||
- Conferir sabor → imprimir etiqueta da tarefa → marcar **na estufa**.
|
||||
- Despacho só oferece motoboy o que está na estufa.
|
||||
|
||||
### 3.3 Despacho comando — RF-GAP-02 / RF-GAP-03 / RF-GAP-04 / RF-GAP-05
|
||||
|
||||
- Colunas: canal, nº, tempo na casa, estado (vermelho/laranja/amarelo), motoboy.
|
||||
- Lista motoboys: em casa / na rua / carga.
|
||||
- Arrastar/reordenar fila **propaga no KDS**.
|
||||
|
||||
## 4. Fora do P0 (não misturar)
|
||||
|
||||
P1: rotas por região, ranking, gargalo, KPIs, cotação iFood.
|
||||
P2: multi-loja, frota híbrida.
|
||||
|
||||
Programação automática do forno: depois do P0.
|
||||
|
||||
## 5. Relação com a sequência S0–S9
|
||||
|
||||
| Sequência atual | Impacto desta SPEC |
|
||||
|-----------------|-------------------|
|
||||
| S3 KDS estações | Estender tickets para **tarefa por pizza** |
|
||||
| S4 etiquetas | Etiqueta por tarefa + conferência |
|
||||
| S5 despacho | Vira centro de comando (hoje é fila) |
|
||||
| S6 rotas / rusterando | RF-GAP-06 / 16 |
|
||||
| Canais digitais / iFood | RF-GAP-07 deixa de ser só filtro |
|
||||
|
||||
## 6. Critério de “estamos no nível do vídeo”
|
||||
|
||||
1. Dois pizzaiolos assumem pizzas do mesmo pedido sem um ficar com tudo.
|
||||
2. Conferente imprime 01/N e o despacho vê o pedido na estufa.
|
||||
3. Gestor reordena a fila e a cozinha vê a nova ordem.
|
||||
4. Motoboy aparece como casa/rua, não só como usuário.
|
||||
5. iFood na hora do despacho é cotação, não só um selo vermelho.
|
||||
169
docs/SPEC-MATRIZ-ACESSO.md
Normal file
169
docs/SPEC-MATRIZ-ACESSO.md
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
# SPEC — Matriz de Acesso (MVP)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Versão** | 0.1.0 |
|
||||
| **Data** | 2026-10-19 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **UI** | `/acessos` · Admin |
|
||||
| **API** | `/api/access/*` |
|
||||
| **Protótipo visual** | `acessos.html` (Codex 2026-10-19) — referência UX, **não** localStorage como auth |
|
||||
| **Regras** | `matriz-de-acesso-mvp.md` + este doc |
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Gestão de **permissões administrativas** do tenant (loja) selecionado: matriz Perfil × Capacidade, com rascunho, dependências, salvamento atômico e auditoria.
|
||||
|
||||
**Fora desta entrega:** Cozinha, Balcão, Despacho, Delivery, Estoque operacional · suporte irrestrito multi-tenant.
|
||||
|
||||
---
|
||||
|
||||
## 2. Modelo no Pizza Digital (estado real do repo)
|
||||
|
||||
| Conceito PRD / proposta | Implementação atual |
|
||||
|-------------------------|---------------------|
|
||||
| Tenant / loja | `Store` |
|
||||
| Identity global | **Proposta futura.** MVP: credencial em `User` (email+hash) por loja |
|
||||
| Vínculo funcionário–tenant | `User` (`storeId` + email único na loja) |
|
||||
| Sessão | JWT com `sub`, `storeId`, `role` operacional — **capabilities são reconsultadas no Core** |
|
||||
| Core = fonte da verdade | API Nest + Postgres |
|
||||
| Admin (borda) | App React `apps/web` |
|
||||
|
||||
`UserRole` (`gestor` \| `pdv` \| `kds` \| `despacho`) permanece para **operação** (PDV/KDS). A matriz MVP **não** define essas permissões operacionais.
|
||||
|
||||
---
|
||||
|
||||
## 3. Capacidades (propostas → contrato MVP)
|
||||
|
||||
| ID | Área | Label PT-BR |
|
||||
|----|------|-------------|
|
||||
| `menu.view` | Cardápio | Visualizar cardápio |
|
||||
| `menu.manage` | Cardápio | Editar cardápio |
|
||||
| `staff.view` | Equipe | Visualizar funcionários |
|
||||
| `staff.manage` | Equipe | Gerenciar funcionários comuns |
|
||||
| `permissions.manage` | Equipe | Gerenciar permissões |
|
||||
| `owners.manage` | Equipe | Gerenciar Donos |
|
||||
| `finance.view` | Financeiro | Visualizar dashboard financeiro |
|
||||
|
||||
**Nota:** “Consultar auditoria do cardápio” **acompanha** `menu.view` — não é capability separada.
|
||||
|
||||
---
|
||||
|
||||
## 4. Perfis = **modelos de preenchimento** (não contrato fechado do PRD)
|
||||
|
||||
O PRD nomeia só **Dono**. Gestor / Cardápio / Financeiro são **propostas** desta implementação.
|
||||
|
||||
| Capacidade | Dono | Gestor | Cardápio | Financeiro |
|
||||
|------------|:----:|:------:|:--------:|:----------:|
|
||||
| `menu.view` | ■ | ■ | ■ | |
|
||||
| `menu.manage` | ■ | ■ | ■ | |
|
||||
| `staff.view` | ■ | ■ | | |
|
||||
| `staff.manage` | ■ | ■ | | |
|
||||
| `permissions.manage` | ■ | | | |
|
||||
| `owners.manage` | ■ | | | |
|
||||
| `finance.view` | ■ | ■ | | ■ |
|
||||
|
||||
### Como o modelo é aplicado ao vínculo
|
||||
|
||||
1. Matriz edita a **definição do modelo** por tenant (`AccessProfileTemplate`).
|
||||
2. Permissões **efetivas** ficam em `UserCapability` (vínculo).
|
||||
3. **Aplicar modelo** a um ou mais usuários é ação **explícita** (`POST /api/access/apply-template`) — nunca silenciosa ao salvar a matriz.
|
||||
4. `User.profileTemplateKey` guarda só o último modelo aplicado (rótulo); mudar o template **não** atualiza quem já o usou até reaplicar.
|
||||
|
||||
---
|
||||
|
||||
## 5. Dependências (Core valida o conjunto completo)
|
||||
|
||||
1. `menu.manage` ⇒ exige `menu.view`
|
||||
2. `staff.manage` \| `permissions.manage` \| `owners.manage` ⇒ exigem `staff.view`
|
||||
3. `staff.manage` **não** implica promover, conceder permissões nem gerir Donos
|
||||
4. Autor só concede capacidades que **ele próprio** possui (reconsulta Core)
|
||||
5. `permissions.manage` **não** implica `owners.manage`
|
||||
6. Último vínculo com `owners.manage` ativo **não** pode perder essa cap / ser desativado
|
||||
7. Mudança de template/perfil que deixaria o tenant sem Dono → rejeitada **atomicamente**
|
||||
8. Admin de tenant **não** altera email global nem define/redefine senhas (fora deste módulo; documentado)
|
||||
9. `User.active=false` ⇒ sem acesso àquele tenant
|
||||
|
||||
UI explica auto-concessão/remoção de dependentes; servidor revalida no save.
|
||||
|
||||
---
|
||||
|
||||
## 6. API
|
||||
|
||||
| Método | Rota | Auth |
|
||||
|--------|------|------|
|
||||
| `GET` | `/api/access/me` | sessão — caps efetivas (sempre do DB) |
|
||||
| `GET` | `/api/access/matrix` | `permissions.manage` |
|
||||
| `PUT` | `/api/access/matrix` | `permissions.manage` · body + `If-Match` / `version` |
|
||||
| `POST` | `/api/access/apply-template` | `permissions.manage` (+ caps do autor) |
|
||||
|
||||
`PUT` salva só **templates**. Efetivos mudam só via apply (ou seed inicial).
|
||||
|
||||
---
|
||||
|
||||
## 7. Persistência
|
||||
|
||||
- `AccessProfileTemplate` — storeId + key + capabilities[]
|
||||
- `UserCapability` — userId + capability (efetivo)
|
||||
- `User.profileTemplateKey` — opcional
|
||||
- `AccessAuditLog` — autor, storeId, target, before/after, createdAt (**proposta** se PRD não fechou auditoria)
|
||||
|
||||
---
|
||||
|
||||
## 8. UI `/acessos`
|
||||
|
||||
- Colunas: perfis · Linhas: permissões agrupadas por área
|
||||
- Busca · filtros área/perfil · célula toggle · contagem pendente
|
||||
- Descartar / Salvar · loading / erro / sucesso / vazio / 403
|
||||
- Responsivo, teclado, toque · PT-BR
|
||||
|
||||
localStorage **não** autoriza.
|
||||
|
||||
---
|
||||
|
||||
## 9. Migração (instruções — não rodar em produção sem revisão)
|
||||
|
||||
```bash
|
||||
# lab / VM125
|
||||
cd pizza-digital
|
||||
docker compose exec api npx prisma migrate deploy
|
||||
docker compose exec api npx prisma db seed # backfill caps do gestor
|
||||
```
|
||||
|
||||
Arquivo: `apps/api/prisma/migrations/20261019190000_access_matrix/`
|
||||
|
||||
---
|
||||
|
||||
## 10. Decisões pendentes
|
||||
|
||||
1. Separar Identity global (email único multi-loja) do vínculo — ainda acoplado em `User`.
|
||||
2. Colunas da matriz: só modelos vs. também funcionários (2ª vista).
|
||||
3. Auditoria de permissões: proposta implementada; confirmar se PRD exige retenção/export.
|
||||
4. Conjunto Dono “protegido” na UI vs. editável no Core — MVP: editável no Core com regra do último Dono; UI marca coluna Dono como sensível.
|
||||
5. Aprovar nomes Gestor/Cardápio/Financeiro no PRD oficial.
|
||||
|
||||
---
|
||||
|
||||
## 11. Critérios de aceite → cobertura
|
||||
|
||||
| Critério | Como |
|
||||
|----------|------|
|
||||
| Autorizado consulta matriz | `GET /matrix` + UI |
|
||||
| Sem permissão: UI + API | Guard `permissions.manage` |
|
||||
| Busca/filtros não perdem rascunho | state local React |
|
||||
| Descartar restaura | reload snapshot |
|
||||
| Salvar só após 2xx | PUT atômico |
|
||||
| Falha preserva rascunho | não limpa draft |
|
||||
| Dependências FE+BE | `normalizeCapabilities` |
|
||||
| Sem escalada via staff | staff.manage ≠ permissions/owners |
|
||||
| Revogação na próxima ação | `/access/me` + guards |
|
||||
| Sem ficar sem Dono | check atômico |
|
||||
| Sem cross-tenant | sempre `storeId` da sessão |
|
||||
| Teclado/tablet/mobile | UI matrix |
|
||||
|
||||
---
|
||||
|
||||
*Documento vivo. Versão produto pai: SPEC.md.*
|
||||
68
docs/SPEC-SIM-OPS-DEMO.md
Normal file
68
docs/SPEC-SIM-OPS-DEMO.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# SPEC — Simulação de Operação (Demo)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Versão** | 0.1.0 |
|
||||
| **Data** | 2026-10-22 |
|
||||
| **Rota UI** | `/sim` (menu · só gestor) |
|
||||
| **API** | `/api/sim/*` |
|
||||
| **Marca pedido** | `inboundKey` prefixo `sim:` · notes `[SIM-OPS]` |
|
||||
|
||||
---
|
||||
|
||||
## 0. Objetivo
|
||||
|
||||
Rotina automática que **demonstra o ciclo completo** da pizzaria em Joinville:
|
||||
|
||||
entrada (Aplicativo / WhatsApp / Telefone) → Central → KDS (pizzaiolos) → estufa → despacho → motoboy → entrega.
|
||||
|
||||
Liga / desliga no menu. Limpa dados de teste em tempo real.
|
||||
|
||||
---
|
||||
|
||||
## 1. Parâmetros
|
||||
|
||||
| Parâmetro | Valor |
|
||||
|-----------|--------|
|
||||
| Cidade | Joinville / SC (bairros do mapa) |
|
||||
| Intervalo entrada | 2–6 min (mantém fila visível; almoço/jantar mais curto) |
|
||||
| Cap ops/dia | 2000 (create/claim/bump/estufa/rota/entrega) |
|
||||
| Prep máximo | **165 min** — só força se travar |
|
||||
| Confecção | **14–18 min** simples · **18–25 min** complexa (média pizzaria) |
|
||||
| Estufa | 4–7 min · Entrega 14–25 min · ciclo ~35–55 min |
|
||||
| Motoboys | 15 com **nomes reais** (Steve Rogers, Ricardo Alves…) |
|
||||
| Pizzaiolos | 8 KDS com nomes (Cláudio, Gil, Cristiane…) |
|
||||
| Pizza | simples / complexa · até 4 sabores · **sempre** com complemento (borda/extra) |
|
||||
| Pedidos | 1–N itens; múltiplos por “onda” |
|
||||
| ETA entrega | 8–18 min após despacho (distância); então `entregue` |
|
||||
|
||||
Faixas busy (America/Sao_Paulo): **11:30–14:00** e **18:00–22:30**.
|
||||
|
||||
---
|
||||
|
||||
## 2. API
|
||||
|
||||
| Método | Path | Efeito |
|
||||
|--------|------|--------|
|
||||
| GET | `/api/sim/status` | ligado?, stats, nextIngressAt |
|
||||
| POST | `/api/sim/start` | seed frota/pizzaiolos · liga tick · burst inicial |
|
||||
| POST | `/api/sim/stop` | para tick (dados ficam) |
|
||||
| POST | `/api/sim/purge` | apaga pedidos/rotas/drivers SIM · zera stats |
|
||||
|
||||
---
|
||||
|
||||
## 3. App Motoboy (decisão)
|
||||
|
||||
- **Agora:** `/motoboy` PWA continua o app do entregador; a simulação alimenta as mesmas rotas/API e conclui entregas com ETA (você vê pins sumirem no despacho).
|
||||
- **Depois (não bloqueia demo):** PIN do motoboy, offline queue, deep-link Waze/Google configurável, push — SPEC-DESPACHO E.
|
||||
|
||||
---
|
||||
|
||||
## 4. Aceite
|
||||
|
||||
1. Gestor liga em `/sim` → pedidos entram sozinhos nos canais.
|
||||
2. KDS mostra pizzaiolos pegando tasks.
|
||||
3. Despacho: verdes → rotas → mapa.
|
||||
4. Entregas fecham sozinhas no ETA.
|
||||
5. Desliga para o tick.
|
||||
6. Limpar remove só dados `sim:` sem derrubar cardápio/seed real.
|
||||
88
docs/SPEC-USUARIOS.md
Normal file
88
docs/SPEC-USUARIOS.md
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
# SPEC — Usuários, senha e sessão
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Capítulo** | Equipe / Auth |
|
||||
| **Versão** | 0.1.0 |
|
||||
| **Data** | 2026-10-20 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **UI** | `/admin/equipe` · `/forgot-password` · `/reset-password` |
|
||||
| **API** | `/api/users` · `/api/auth/*` |
|
||||
| **Irmã** | [`SPEC-MATRIZ-ACESSO.md`](SPEC-MATRIZ-ACESSO.md) |
|
||||
|
||||
---
|
||||
|
||||
## 1. Objetivo
|
||||
|
||||
Cadastrar quem opera a loja (PDV, KDS, despacho, gestor), com senha forte, bloqueio por tentativa, recuperação e vínculo à matriz de acesso.
|
||||
|
||||
---
|
||||
|
||||
## 2. Modelo
|
||||
|
||||
```text
|
||||
User
|
||||
email, name, role (gestor|pdv|kds|despacho)
|
||||
jobTitle, areas[], phone, active
|
||||
passwordHash, mustChangePassword, passwordChangedAt
|
||||
failedLoginAttempts, lockedUntil, lastLoginAt
|
||||
profileTemplateKey ← rótulo do último modelo aplicado
|
||||
|
||||
AuthToken password_reset | invite | email_verify
|
||||
PushDevice token web/app
|
||||
NotifyLog email | push (auditoria de envio)
|
||||
UserCapability fonte da verdade no vínculo (SPEC matriz)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Requisitos
|
||||
|
||||
| ID | Requisito | Status |
|
||||
|----|-----------|--------|
|
||||
| RF-USR-01 | CRUD equipe (`staff.view` / `staff.manage`) | **feito** |
|
||||
| RF-USR-02 | Cargo (`jobTitle`) + áreas (`pdv`, `kds`, `despacho`, `admin`, …) | **feito** |
|
||||
| RF-USR-03 | Política de senha: ≥8, maiúscula, minúscula, dígito; sem o local-part do e-mail | **feito** |
|
||||
| RF-USR-04 | Lockout: 5 falhas → 15 min (`lockedUntil`) | **feito** |
|
||||
| RF-USR-05 | Recuperação por e-mail (link 60 min) + stub push | **feito** (SMTP produção opcional) |
|
||||
| RF-USR-06 | Convite / `mustChangePassword` no primeiro acesso | **feito** |
|
||||
| RF-USR-07 | Aplicar template da matriz no cadastro | **feito** |
|
||||
| RF-USR-08 | Gestor extra exige `owners.manage` | **feito** |
|
||||
| RF-USR-09 | Desbloquear conta (`unlock`) | **feito** |
|
||||
| RF-USR-10 | SMTP/push reais em produção | **stub** |
|
||||
|
||||
---
|
||||
|
||||
## 4. Política de senha (congelada)
|
||||
|
||||
```text
|
||||
minLength 8 · max 128
|
||||
1 maiúscula · 1 minúscula · 1 dígito
|
||||
não contém o e-mail
|
||||
maxFailedAttempts 5 · lockMinutes 15
|
||||
resetTokenTtlMinutes 60
|
||||
```
|
||||
|
||||
Código: `apps/api/src/auth/password-policy.ts`.
|
||||
|
||||
---
|
||||
|
||||
## 5. Fluxos
|
||||
|
||||
1. Gestor cria usuário em `/admin/equipe` → hash bcrypt → opcional convite.
|
||||
2. Login `/api/auth/login` incrementa falhas; 5× bloqueia 15 min.
|
||||
3. Esqueci senha → `AuthToken.password_reset` → e-mail com link (resposta genérica, não vaza existência).
|
||||
4. Reset consome token, valida política, limpa lockout.
|
||||
|
||||
---
|
||||
|
||||
## 6. Fora desta SPEC
|
||||
|
||||
- SSO / SAML
|
||||
- 2FA TOTP (fase futura)
|
||||
- Multi-loja no mesmo login
|
||||
|
||||
---
|
||||
|
||||
*Satélite. Produto pai: SPEC.md v0.3.5.*
|
||||
724
docs/SPEC.md
Normal file
724
docs/SPEC.md
Normal file
|
|
@ -0,0 +1,724 @@
|
|||
# SPEC — Pizza Digital
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| **Produto** | Pizza Digital |
|
||||
| **Versão do SPEC** | **0.3.2** |
|
||||
| **Status** | Fase 1 cozinha no ar · **Prioridade seguinte: entrada por canais digitais** |
|
||||
| **Data** | 2026-07-23 · rev. matriz acesso 2026-10-19 |
|
||||
| **Owner** | Roger / iTecnologys |
|
||||
| **Host Dev** | Proxmox BIG · VMID **125** · `10.10.10.125` · user `mini` |
|
||||
| **URL pública** | `https://pizza.itecnologys.com` (legado SPEC: pizza.myvexx.com) |
|
||||
| **Detalhe Fase 1** | [`docs/SPEC-FASE1.md`](docs/SPEC-FASE1.md) |
|
||||
| **Canais digitais** | [`docs/SPEC-CANAIS-DIGITAIS.md`](docs/SPEC-CANAIS-DIGITAIS.md) · §5.5 |
|
||||
| **Cardápio** | [`docs/SPEC-CARDAPIO.md`](docs/SPEC-CARDAPIO.md) · RF-MEN · Joinville |
|
||||
| **Estoque** | [`docs/SPEC-ESTOQUE.md`](docs/SPEC-ESTOQUE.md) · RF-EST · BOM ↔ menu |
|
||||
| **Matriz de acesso** | [`docs/SPEC-MATRIZ-ACESSO.md`](docs/SPEC-MATRIZ-ACESSO.md) · `/acessos` · RF-ACS |
|
||||
| **Sequência oficial** | §15 — S0…S9 · Track C · Track M/E · Matriz ACS |
|
||||
| **Referência principal** | CityFoods — Cozinha Digital + Cardápio Digital (`@cityfoods_app`) |
|
||||
| **KDS / estações** | kitchenOS + restaurant-pos + este SPEC |
|
||||
| **Rotas / dispatch** | **rusterando** = referência oficial (ORS, driver tour) — AGPL: modelo operacional, não fork do core |
|
||||
| **Repos open-source** | kitchenOS, rusterando, URY Mosaic, MaSoVa, Tasty-Station |
|
||||
|
||||
---
|
||||
|
||||
## 1. Visão
|
||||
|
||||
**Pizza Digital** é uma plataforma de operação de delivery para pizzarias, restaurantes e outras cozinhas, com foco em **Cozinha Digital (KDS)**: eliminar comanda de papel, organizar produção por estações, reduzir erros e acelerar despacho.
|
||||
|
||||
Não é um marketplace. É o **sistema operacional da cozinha + delivery próprio**, no mesmo espírito da CityFoods:
|
||||
|
||||
> Cozinha Digital não é sobre robôs — é inteligência e fluxo à prova de falhas, do pedido à entrega.
|
||||
|
||||
### 1.1 Problema
|
||||
|
||||
Operações de delivery perdem tempo e clientes por:
|
||||
|
||||
- Comandas de papel (perda, rasura, leitura ruim)
|
||||
- Pedidos de vários canais em telas diferentes
|
||||
- Falta de priorização (pedido novo não sai junto de outros prontos)
|
||||
- Erros de observação / montagem
|
||||
- Motoboys mal roteados (ida e volta na mesma região)
|
||||
- Zero métrica de tempo por estação
|
||||
|
||||
### 1.2 Promessa
|
||||
|
||||
Uma plataforma única que:
|
||||
|
||||
1. Recebe pedidos (PDV, cardápio digital, marketplaces)
|
||||
2. Distribui por **estações de cozinha** (KDS)
|
||||
3. Identifica pedidos por **etiquetas**
|
||||
4. Prioriza e agrupa saída
|
||||
5. Despacha com **gestão de rotas**
|
||||
6. Mede **tempo e produtividade** em tempo real
|
||||
|
||||
### 1.3 Posicionamento
|
||||
|
||||
| | CityFoods (referência) | Pizza Digital (nossa) |
|
||||
|--|------------------------|------------------------|
|
||||
| Foco | Delivery multi-tipo | **Pizzaria primeiro**, depois outras cozinhas |
|
||||
| Diferencial citado | “Primeira Cozinha Digital” | Cozinha Digital + templates de estação por tipo de negócio |
|
||||
| Mercado | SC / BR SaaS | Começar local / controlado, evoluir para SaaS multi-tenant |
|
||||
|
||||
---
|
||||
|
||||
## 2. Objetivos e métricas de sucesso
|
||||
|
||||
### 2.1 Objetivos de produto (MVP)
|
||||
|
||||
- Substituir comanda de papel por KDS touch
|
||||
- Operar **1 pizzaria piloto** de ponta a ponta (pedido → pronto → despacho)
|
||||
- Reduzir erros de montagem / observação
|
||||
- Medir tempo médio por estação e por pedido
|
||||
|
||||
### 2.2 KPIs (piloto)
|
||||
|
||||
| KPI | Meta inicial (90 dias) |
|
||||
|-----|------------------------|
|
||||
| Erros de pedido (reclamação / refazer) | −50% vs baseline |
|
||||
| Tempo médio pedido → pronto | −20% |
|
||||
| Pedidos sem papel na cozinha | 100% no piloto |
|
||||
| Atraso de despacho (> SLA) | −30% |
|
||||
| Adoção diária do KDS | ≥ 95% dos pedidos passam pelo KDS |
|
||||
|
||||
### 2.3 Não-objetivos (MVP)
|
||||
|
||||
- App consumidor nativo (iOS/Android) completo
|
||||
- Marketplace próprio
|
||||
- Contabilidade / ERP completo
|
||||
- Multi-país / multi-moeda
|
||||
- IA generativa na cozinha
|
||||
|
||||
---
|
||||
|
||||
## 3. Personas e papéis
|
||||
|
||||
| Papel | Quem | Precisa |
|
||||
|-------|------|---------|
|
||||
| **Operador KDS** | Cozinheiro / pizzaiolo | Tela grande, bump rápido, ver observações |
|
||||
| **Expedição** | Embalador / conferente | Pedido completo, etiqueta, agrupar saída |
|
||||
| **Caixa / PDV** | Atendente | Lançar pedido balcão / telefone |
|
||||
| **Despachante** | Quem libera motoboy | Agrupar por região, priorizar |
|
||||
| **Entregador** | Motoboy | Lista de entregas, status, navegação básica |
|
||||
| **Gestor** | Dono / gerente | Métricas, cardápio, usuários, relatórios |
|
||||
| **Admin SaaS** (fase 2) | iTecnologys | Multi-loja, billing, suporte |
|
||||
|
||||
---
|
||||
|
||||
## 4. Escopo por fases
|
||||
|
||||
### Fase 0 — Fundação (esta SPEC + bootstrap)
|
||||
|
||||
- Repositório, monorepo, CI mínimo
|
||||
- Modelo de domínio e eventos
|
||||
- Ambiente local (Docker)
|
||||
|
||||
### Fase 1 — MVP Cozinha Digital (core) ✅ em andamento
|
||||
|
||||
> Detalhe completo: [`docs/SPEC-FASE1.md`](docs/SPEC-FASE1.md) · Host: VM **125** `10.10.10.125`
|
||||
|
||||
- Pedidos manuais (PDV web)
|
||||
- KDS multi-estação (pizzaria)
|
||||
- Status: `novo → em_preparo → pronto → despachado → entregue`
|
||||
- Priorização / reordenação de fila
|
||||
- Etiquetas (impressão ESC/POS ou PDF fallback)
|
||||
- Painel gestor básico (tempos)
|
||||
- Stack: NestJS + React + Postgres + Redis + Socket.io (Docker na VM 125)
|
||||
|
||||
### Fase 2 — Delivery operacional (após KDS estável)
|
||||
|
||||
> Prioridade de construção: **KDS multi-estação primeiro**; delivery/rotas só depois do piloto de cozinha (S5).
|
||||
> Referência oficial de dispatch: **[rusterando](https://github.com/holg/rusterando)** (kitchen → driver tour → ORS). Detalhe: §15.1 e §15.2.
|
||||
|
||||
- Gate `order.ready` → fila de despacho
|
||||
- Agrupar por bairro (manual + sugestão) + atribuir motoboy + reordenar saída (“2 cliques” CityFoods)
|
||||
- OpenRouteService (padrão rusterando) para ordem da rota
|
||||
- Mapa no despacho · PWA entregador · tracking
|
||||
- Pagamento/comissão por entrega · fechamento de caixa básico
|
||||
|
||||
### Fase 3 — Canais digitais e SaaS (PRIORIDADE pós-S5)
|
||||
|
||||
> Track **C** (canais) começa em paralelo assim que o piloto de cozinha (S5) estiver estável — **não esperar o fim de S6–S8**.
|
||||
> Ver §5.5 e [`docs/SPEC-CANAIS-DIGITAIS.md`](docs/SPEC-CANAIS-DIGITAIS.md).
|
||||
|
||||
- **C1** Cardápio digital público (PWA) + checkout (Pix / dinheiro na entrega)
|
||||
- **C1** Pedidos digitais no quadro `/pedidos` com badge de origem
|
||||
- **C2** Integração iFood (webhook → pedido normalizado)
|
||||
- Multi-tenant / multi-loja
|
||||
- Relatórios operacionais avançados
|
||||
- Templates de cozinha: pizzaria, hamburgueria, sushi, dark kitchen
|
||||
|
||||
---
|
||||
|
||||
## 5. Conceito operacional — Cozinha Digital
|
||||
|
||||
### 5.1 Estações (template Pizzaria)
|
||||
|
||||
| Estação | Responsabilidade | Exemplos de itens |
|
||||
|---------|------------------|-------------------|
|
||||
| **Massa** | Abrir / preparar base | Massa, borda |
|
||||
| **Montagem** | Molho, queijo, toppings | Sabores, adicionais |
|
||||
| **Forno** | Assar | Pizza em forno |
|
||||
| **Expedição** | Embalar, conferir, etiquetar | Pedido completo |
|
||||
| **Bebidas** (opcional) | Separar frios | Refrigerante, água |
|
||||
|
||||
**Princípio KDS (CityFoods — claim exclusivo):**
|
||||
|
||||
1. A **fila chega para todas** as estações.
|
||||
2. Uma estação **escolhe e puxa (PEGAR)** um ou mais pedidos para si.
|
||||
3. Com o claim, **as outras estações ficam bloqueadas** de pegar aquele pedido.
|
||||
4. Só a estação dona dá **BUMP** (pedido pronto).
|
||||
|
||||
Regras de mapeamento (histórico / tickets):
|
||||
|
||||
- Cada item do cardápio mapeia para **1+ estações** (referência de produção)
|
||||
- Claim é ao nível do **pedido completo**, não por item
|
||||
|
||||
### 5.2 Template futuro — outras cozinhas
|
||||
|
||||
| Tipo | Estações padrão |
|
||||
|------|-----------------|
|
||||
| Hamburgueria | Grelha · Fritura · Montagem · Expedição |
|
||||
| Sushi | Corte · Montagem · Expedição |
|
||||
| Dark kitchen genérica | Prep · Quente · Frios · Expedição |
|
||||
|
||||
### 5.3 Etiquetas
|
||||
|
||||
Cada pedido (ou item, configurável) gera etiqueta com:
|
||||
|
||||
- Código curto do pedido (ex: `#A42`)
|
||||
- Canal (PDV / iFood / Cardápio)
|
||||
- Cliente (nome parcial)
|
||||
- Itens resumidos + observações em destaque
|
||||
- Horário / timer SLA
|
||||
- QR opcional para conferência
|
||||
|
||||
Objetivo: eliminar grampear comanda ilegível na embalagem.
|
||||
|
||||
### 5.4 Priorização (inspirado CityFoods “2 cliques”)
|
||||
|
||||
O despachante/gestor pode:
|
||||
|
||||
- Reordenar fila de saída
|
||||
- Agrupar pedidos da mesma região para sair juntos
|
||||
- Marcar pedido como **urgente** (sobresai visualmente no KDS)
|
||||
|
||||
### 5.5 Entrada por canais digitais (PRIORIDADE)
|
||||
|
||||
> Detalhe de produto: [`docs/SPEC-CANAIS-DIGITAIS.md`](docs/SPEC-CANAIS-DIGITAIS.md) · RFs: RF-CAN, RF-PED-08/09, RF-MEN-04/06/07, RF-INT-01/03/05
|
||||
|
||||
**Problema:** pedidos chegam por telefone, WhatsApp e iFood em telas diferentes — a loja digita de novo no PDV e erra.
|
||||
|
||||
**Promessa:** o cliente pede no canal digital; a loja vê **o mesmo Order** no quadro `/pedidos` e a cozinha trata igual a pedido de balcão.
|
||||
|
||||
#### Dimensões (substituem `channel` misturado)
|
||||
|
||||
| Dimensão | Valores | Uso na UI |
|
||||
|----------|---------|-----------|
|
||||
| `origin` | `pdv` · `cardapio` · `ifood` · `whatsapp` · `telefone` | Badge no card (CARDÁPIO / IFOOD / …) |
|
||||
| `fulfillment` | `entrega` · `retirada` · `mesa` | Faixa ENTREGA / RETIRADA (CityFoods) |
|
||||
|
||||
#### Canais de entrada → um quadro
|
||||
|
||||
```text
|
||||
Cardápio PWA /c/:slug ─┐
|
||||
WhatsApp (deep-link) ─┼─► Order Ingest (schema único)
|
||||
iFood webhook ─┤ │
|
||||
PDV / telefone (staff) ─┘ ▼
|
||||
/pedidos (badge origem + fulfillment)
|
||||
│
|
||||
▼
|
||||
KDS · Etiqueta · Despacho
|
||||
```
|
||||
|
||||
#### Track C (após piloto S5; C1 pode prototipar em paralelo)
|
||||
|
||||
| Track | Entrega |
|
||||
|-------|---------|
|
||||
| **C1** | Cardápio público + checkout (Pix / pagar na entrega) → `/pedidos` ≤ 500 ms |
|
||||
| **C2** | Webhook iFood → normalizador + idempotência |
|
||||
|
||||
**Regra de ouro:** nenhum canal tem UI de cozinha própria — tudo passa pelo Order bus e pelo `/pedidos`.
|
||||
|
||||
---
|
||||
|
||||
## 6. Fluxos principais
|
||||
|
||||
### 6.1 Fluxo feliz — delivery pizzaria (canal digital ou PDV)
|
||||
|
||||
```text
|
||||
[Cliente] Cardápio /c/:slug | WhatsApp | iFood
|
||||
[Staff] PDV /pedidos (+ Novo)
|
||||
→ Order Ingest normaliza (origin + fulfillment + itens)
|
||||
→ Aparece em /pedidos (badge origem · faixa ENTREGA/RETIRADA)
|
||||
→ Roteia itens → estações KDS
|
||||
→ Operadores dão claim/bump por estação
|
||||
→ Expedição confere + imprime etiqueta (origem + fulfillment)
|
||||
→ Pedido = PRONTO
|
||||
→ Despacho atribui motoboy / agrupa rota
|
||||
→ Entregador: saiu → entregue
|
||||
→ Métricas gravadas (tempos por etapa)
|
||||
```
|
||||
|
||||
### 6.2 Estados do pedido
|
||||
|
||||
```text
|
||||
rascunho → confirmado → em_producao → pronto → em_rota → entregue
|
||||
↓
|
||||
cancelado
|
||||
```
|
||||
|
||||
Estados de **item na estação**:
|
||||
|
||||
```text
|
||||
na_fila → em_preparo → concluido | cancelado
|
||||
```
|
||||
|
||||
### 6.3 Diagrama lógico
|
||||
|
||||
```text
|
||||
Canais Core Operação
|
||||
───── ──── ────────
|
||||
PDV ──────┐
|
||||
Cardápio ─┼─► Order Ingest ─► Router ─► KDS Stations
|
||||
iFood ────┘ │ │ │
|
||||
│ │ ▼
|
||||
│ └──► Labels / Print
|
||||
▼
|
||||
Order Store ◄── WebSocket fan-out
|
||||
│
|
||||
▼
|
||||
Dispatch / Routes ─► Driver PWA
|
||||
│
|
||||
▼
|
||||
Analytics
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Requisitos funcionais
|
||||
|
||||
### 7.1 Pedidos (RF-PED)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-PED-01 | Criar pedido no PDV (itens, adicionais, obs, tipo: delivery/balcão/mesa) | Sim |
|
||||
| RF-PED-02 | Schema único de pedido independente do canal | Sim |
|
||||
| RF-PED-03 | Editar pedido até entrar em produção (política configurável) | Sim |
|
||||
| RF-PED-04 | Cancelar pedido com motivo | Sim |
|
||||
| RF-PED-05 | Observações em destaque no KDS | Sim |
|
||||
| RF-PED-06 | Número sequencial diário + código curto legível | Sim |
|
||||
| RF-PED-07 | Ingestão via webhook (iFood etc.) | Track C2 |
|
||||
| RF-PED-08 | Campos `origin` + `fulfillment` (em vez de channel misturado) | Track C1 |
|
||||
| RF-PED-09 | Pedido digital aparece no `/pedidos` em tempo real (WS) | Track C1 |
|
||||
|
||||
### 7.2 KDS / Cozinha Digital (RF-KDS)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-KDS-01 | Tela KDS por estação (filtro de área) | Sim |
|
||||
| RF-KDS-02 | Atualização em tempo real (WebSocket, sem polling) | Sim |
|
||||
| RF-KDS-03 | Timer visual (verde / amarelo / vermelho por SLA) | Sim |
|
||||
| RF-KDS-04 | Bump item / bump pedido na estação | Sim |
|
||||
| RF-KDS-05 | Multi-estação no mesmo pedido | Sim |
|
||||
| RF-KDS-06 | Som/alerta opcional de novo ticket | Sim |
|
||||
| RF-KDS-07 | Modo tela cheia / tablet / TV | Sim |
|
||||
| RF-KDS-08 | Offline degradado (fila local + sync) | Fase 2 |
|
||||
|
||||
### 7.3 Etiquetas (RF-ETQ)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-ETQ-01 | Gerar etiqueta ao concluir expedição (ou sob demanda) | Sim |
|
||||
| RF-ETQ-02 | Impressão térmica ESC/POS | Sim (com fallback PDF) |
|
||||
| RF-ETQ-03 | Layout configurável por loja | Fase 2 |
|
||||
|
||||
### 7.4 Despacho e rotas (RF-ROT)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-ROT-01 | Lista de pedidos prontos para despacho | Fase 1 (lista simples) |
|
||||
| RF-ROT-02 | Atribuir entregador | Fase 2 |
|
||||
| RF-ROT-03 | Agrupar por bairro / proximidade | Fase 2 |
|
||||
| RF-ROT-04 | Reordenar saída (priorização) | Fase 1 |
|
||||
| RF-ROT-05 | App/PWA entregador com status | Fase 2 |
|
||||
| RF-ROT-06 | Cálculo de pagamento de entregadores | Fase 3 |
|
||||
|
||||
### 7.5 Cardápio e PDV (RF-MEN)
|
||||
|
||||
> Detalhe: [`docs/SPEC-CARDAPIO.md`](docs/SPEC-CARDAPIO.md) · pesquisa [`docs/CARDAPIO-JOINVILLE.md`](docs/CARDAPIO-JOINVILLE.md)
|
||||
> Ponte estoque: [`docs/SPEC-ESTOQUE.md`](docs/SPEC-ESTOQUE.md)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-MEN-01 | CRUD categorias, itens, preços, adicionais | Sim |
|
||||
| RF-MEN-02 | Mapear item → estações KDS | Sim |
|
||||
| RF-MEN-03 | Tempo SLA por categoria/item | Sim |
|
||||
| RF-MEN-04 | Cardápio digital público (PWA cliente) | **Track C1** |
|
||||
| RF-MEN-05 | Combos / meia-a-meia (pizzaria) | Fase 1 (meia-a-meia) |
|
||||
| RF-MEN-06 | Horário de funcionamento / loja aberta-fechada no cardápio | Track C1 |
|
||||
| RF-MEN-07 | Áreas de entrega + taxa por bairro no cardápio | Track C1 |
|
||||
| RF-MEN-08 | Tamanhos com preço (`broto`/`grande`/…) | Track M1 |
|
||||
| RF-MEN-09 | Pausar item (manual ou estoque baixo) | Track M1 |
|
||||
| RF-MEN-10 | Ficha técnica (BOM) ligada ao item | Track M1 ↔ EST |
|
||||
| RF-MEN-11 | Bebida `stockMode=sku` 1:1 | Track M1 |
|
||||
| RF-MEN-12 | Vitamina/suco natural via BOM | Track M1 |
|
||||
|
||||
### 7.6 Gestão e métricas (RF-GES)
|
||||
|
||||
| ID | Requisito | MVP |
|
||||
|----|-----------|-----|
|
||||
| RF-GES-01 | Dashboard: pedidos abertos, atrasados, tempo médio | Sim |
|
||||
| RF-GES-02 | Tempo médio por estação | Sim |
|
||||
| RF-GES-03 | Relatório de fluxo do pedido (timestamps) | Sim |
|
||||
| RF-GES-04 | Usuários, papéis, permissões | Sim |
|
||||
| RF-GES-05 | Multi-loja / tenant | Fase 3 |
|
||||
|
||||
### 7.7 Integrações (RF-INT)
|
||||
|
||||
| ID | Requisito | Fase |
|
||||
|----|-----------|------|
|
||||
| RF-INT-01 | Webhook genérico de pedido | Track C1 |
|
||||
| RF-INT-02 | iFood (ou agregador BR) | Track C2 |
|
||||
| RF-INT-03 | Pagamento online (Pix / dinheiro na entrega) | Track C1 |
|
||||
| RF-INT-04 | Impressoras de rede | 1 |
|
||||
| RF-INT-05 | Deep-link WhatsApp → cardápio da loja | Track C1 |
|
||||
|
||||
### 7.8 Canais digitais (RF-CAN)
|
||||
|
||||
| ID | Requisito | Track |
|
||||
|----|-----------|-------|
|
||||
| RF-CAN-01 | PWA cardápio público por loja (`/c/:slug`) | C1 |
|
||||
| RF-CAN-02 | Carrinho cliente → Order com `origin=cardapio` | C1 |
|
||||
| RF-CAN-03 | Escolha fulfillment entrega/retirada no cardápio | C1 |
|
||||
| RF-CAN-04 | Taxa / área de entrega no checkout | C1 |
|
||||
| RF-CAN-05 | Pedido digital no `/pedidos` ≤ 500 ms (WS) | C1 |
|
||||
| RF-CAN-06 | Badge de origem no card (CARDÁPIO / IFOOD / …) | C1 |
|
||||
| RF-CAN-07 | Webhook iFood → normalizador | C2 |
|
||||
| RF-CAN-08 | Idempotência / dedupe de pedidos externos | C2 |
|
||||
|
||||
### 7.9 Estoque (RF-EST)
|
||||
|
||||
> Detalhe: [`docs/SPEC-ESTOQUE.md`](docs/SPEC-ESTOQUE.md) · dispara a partir do Order · pausa o Cardápio
|
||||
|
||||
| ID | Requisito | Track |
|
||||
|----|-----------|-------|
|
||||
| RF-EST-01 | CRUD insumos (unidade, min, custo) | E1 |
|
||||
| RF-EST-02 | Ficha técnica por MenuItem | E1 |
|
||||
| RF-EST-03 | Baixa automática no pedido (`em_producao`) | E1 |
|
||||
| RF-EST-04 | Entrada compra / ajuste manual | E1 |
|
||||
| RF-EST-05 | Alerta estoque mínimo (WS) | E1 |
|
||||
| RF-EST-06 | Pausa automática do item no cardápio | E2 |
|
||||
| RF-EST-07 | Estorno em cancelamento elegível | E1 |
|
||||
| RF-EST-08 | CMV teórico por dia/item | E2 |
|
||||
| RF-EST-13 | Ficha por pizza com qty em **gramas** (38 tipos) | E1 |
|
||||
| RF-EST-14 | Catálogo `fichas-pizzas-peso.json` | E1 |
|
||||
|
||||
**Regra de ouro:** Cardápio declara `stockMode` (`bom`\|`sku`\|`none`); Estoque executa a baixa.
|
||||
**Pizzas:** baixa por **peso (g)** por tipo — [`docs/FICHAS-TECNICAS-PESO.md`](docs/FICHAS-TECNICAS-PESO.md).
|
||||
|
||||
---
|
||||
|
||||
## 8. Requisitos não-funcionais
|
||||
|
||||
| ID | Tema | Requisito |
|
||||
|----|------|-----------|
|
||||
| RNF-01 | Latência KDS | Novo pedido visível na tela ≤ **500 ms** (LAN saudável) |
|
||||
| RNF-02 | Disponibilidade | MVP single-node OK; alvo produção 99.5% |
|
||||
| RNF-03 | Concorrência | Suportar pico de **60 pedidos/hora** no piloto |
|
||||
| RNF-04 | UX cozinha | Touch-first, botões grandes, alto contraste, legível a 1–2 m |
|
||||
| RNF-05 | Segurança | Auth JWT/session; senhas hashed; HTTPS; RBAC |
|
||||
| RNF-06 | Auditoria | Log de mudanças de status com user + timestamp |
|
||||
| RNF-07 | Observabilidade | Logs estruturados + healthcheck |
|
||||
| RNF-08 | Idioma | PT-BR nativo |
|
||||
| RNF-09 | Deploy | Docker Compose local; caminho claro para VPS/Proxmox |
|
||||
| RNF-10 | LGPD | Dados de cliente mínimos; retenção configurável |
|
||||
|
||||
---
|
||||
|
||||
## 9. Arquitetura alvo
|
||||
|
||||
### 9.1 Princípios
|
||||
|
||||
1. **Order bus / schema único** — todo canal vira o mesmo `Order`
|
||||
2. **Event-driven para cozinha** — WebSocket fan-out por loja/estação
|
||||
3. **Station routing** — regras no servidor, não na UI
|
||||
4. **PWA** — KDS e entregador rodam no browser/tablet
|
||||
5. **Começar simples** — 1 processo API + Postgres + Redis; extrair microsserviços só se necessário
|
||||
|
||||
### 9.2 Stack sugerida (do zero)
|
||||
|
||||
| Camada | Tecnologia | Motivo |
|
||||
|--------|------------|--------|
|
||||
| Frontend | React + Vite + TypeScript (PWA) | KDS/PDV touch, ecossistema forte |
|
||||
| Realtime | Socket.io ou native WS | Padrão dos KDS de referência |
|
||||
| API | NestJS ou Fastify (Node/TS) | Tipagem compartilhada, rapidez |
|
||||
| DB | PostgreSQL | Pedidos, auditoria, relatórios |
|
||||
| Cache / pubsub | Redis | Tickets abertos + fan-out |
|
||||
| Filas (fase 2+) | BullMQ | Webhooks, impressão, retries |
|
||||
| Print | ESC/POS (node) + PDF fallback | Etiquetas térmicas |
|
||||
| Infra local | Docker Compose | Postgres + Redis + API + Web |
|
||||
|
||||
Referências de forma (não copy-paste cego):
|
||||
|
||||
- **kitchenOS** → ingestão + KDS + Redis stream
|
||||
- **Tasty-Station** → Socket.io POS↔KDS
|
||||
- **rusterando** → **referência oficial** pizzaria + rotas / ORS / driver tour (modelo, não fork AGPL)
|
||||
- **URY Mosaic** → multi-kitchen KDS
|
||||
|
||||
### 9.3 Apps da plataforma
|
||||
|
||||
| App | URL mental | Usuário |
|
||||
|-----|------------|---------|
|
||||
| `web-pdv` | `/pdv` | Caixa |
|
||||
| `web-kds` | `/kds/:station` | Cozinha |
|
||||
| `web-dispatch` | `/despacho` | Despachante |
|
||||
| `web-admin` | `/admin` | Gestor |
|
||||
| `web-driver` | `/entregador` | Motoboy (fase 2) |
|
||||
| `web-menu` | `/m/:slug` | Cliente (fase 3) |
|
||||
|
||||
Monorepo sugerido:
|
||||
|
||||
```text
|
||||
pizza-digital/
|
||||
├── SPEC.md # este documento
|
||||
├── docs/
|
||||
│ ├── domain.md # glossário e regras
|
||||
│ ├── api.md # contratos HTTP/WS
|
||||
│ └── ui-kds.md # wireframes textuais
|
||||
├── apps/
|
||||
│ ├── web/ # PDV + KDS + Admin (rotas)
|
||||
│ └── api/
|
||||
├── packages/
|
||||
│ ├── domain/ # types Order, Station, events
|
||||
│ └── escpos/ # impressão
|
||||
└── docker-compose.yml
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Modelo de domínio (MVP)
|
||||
|
||||
### 10.1 Entidades
|
||||
|
||||
- **Store** — loja
|
||||
- **User** — usuário + role
|
||||
- **Station** — estação KDS
|
||||
- **Category / Product / Modifier** — cardápio ([SPEC-CARDAPIO](docs/SPEC-CARDAPIO.md))
|
||||
- **ProductStationRule** — produto → estações
|
||||
- **Recipe / RecipeLine** — ficha técnica BOM ([SPEC-ESTOQUE](docs/SPEC-ESTOQUE.md))
|
||||
- **Ingredient** — insumo com saldo
|
||||
- **StockMovement** — entrada/saída/ajuste
|
||||
- **Order** — pedido
|
||||
- **OrderItem** — item do pedido (+ modifiers, notes)
|
||||
- **StationTicket** — fatia do item/pedido na estação
|
||||
- **LabelPrintJob** — job de etiqueta
|
||||
- **Courier** (fase 2) — entregador
|
||||
- **DispatchBatch** (fase 2) — lote de rota
|
||||
|
||||
### 10.2 Campos críticos de `Order`
|
||||
|
||||
```text
|
||||
id, store_id, code, channel, type (delivery|pickup|dine_in),
|
||||
status, customer_name, customer_phone, address,
|
||||
notes, priority, promised_at,
|
||||
accepted_at, production_started_at, ready_at,
|
||||
dispatched_at, delivered_at, canceled_at,
|
||||
created_at, updated_at
|
||||
```
|
||||
|
||||
### 10.3 Eventos (WebSocket)
|
||||
|
||||
```text
|
||||
order.created
|
||||
order.updated
|
||||
order.canceled
|
||||
ticket.upserted
|
||||
ticket.bumped
|
||||
order.ready
|
||||
order.dispatched
|
||||
order.delivered
|
||||
label.printed
|
||||
```
|
||||
|
||||
Rooms: `store:{id}`, `store:{id}:station:{stationId}`
|
||||
|
||||
---
|
||||
|
||||
## 11. Regras de negócio — pizzaria
|
||||
|
||||
1. **Meia-a-meia**: 1 pizza = 2 sabores; preço = maior sabor (configurável); aparece no KDS como um item com 2 metades.
|
||||
2. **Borda / adicionais**: modifiers ligados à montagem (ou massa, se borda recheada).
|
||||
3. **Tempo SLA padrão** (editável): Massa 3 min · Montagem 5 · Forno 8 · Expedição 3 — total alvo ~20 min.
|
||||
4. **Pedido delivery sem endereço** não confirma.
|
||||
5. **Bump na expedição** só liberado se estações anteriores concluídas (ou override gestor).
|
||||
6. **Prioridade urgente** não pula forno fisicamente — só sobe na fila visual e alerta.
|
||||
|
||||
---
|
||||
|
||||
## 12. UX — KDS (requisitos de interface)
|
||||
|
||||
- Cards grandes por pedido; código `#A42` enorme
|
||||
- Observação do cliente em **cor de alerta**
|
||||
- Timer no canto; vira amarelo/vermelho por threshold
|
||||
- Botão único: **PRONTO** (item) / **TUDO PRONTO** (estação)
|
||||
- Badge do canal (PDV / iFood / …)
|
||||
- Sem cards decorativos desnecessários — densidade operacional
|
||||
- Funciona em tablet paisagem e TV 32"+
|
||||
|
||||
---
|
||||
|
||||
## 13. Segurança e permissões (RBAC)
|
||||
|
||||
| Recurso | Operador KDS | Expedição | PDV | Despacho | Gestor |
|
||||
|---------|--------------|-----------|-----|----------|--------|
|
||||
| Ver KDS da sua estação | ✓ | ✓ | — | — | ✓ |
|
||||
| Bump tickets | ✓ | ✓ | — | — | ✓ |
|
||||
| Criar pedido | — | — | ✓ | — | ✓ |
|
||||
| Cancelar pedido | — | — | ✓* | — | ✓ |
|
||||
| Despachar | — | ✓ | — | ✓ | ✓ |
|
||||
| Cardápio / users | — | — | — | — | ✓ |
|
||||
| Métricas | — | — | — | leitura | ✓ |
|
||||
|
||||
\*cancelar só antes de produção, salvo permissão.
|
||||
|
||||
---
|
||||
|
||||
## 14. Critérios de aceite do MVP
|
||||
|
||||
O MVP está **aceito** quando, em ambiente piloto:
|
||||
|
||||
1. Atendente cria pedido no PDV com 2 pizzas (1 meia-a-meia) + bebida + observação
|
||||
2. Tickets aparecem nas estações corretas em < 1 s
|
||||
3. Cada estação conclui seus itens; expedição só libera no fim
|
||||
4. Etiqueta é gerada/impressa
|
||||
5. Gestor reordena/prioritiza um pedido pronto
|
||||
6. Dashboard mostra tempos por estação daquele pedido
|
||||
7. Zero uso de papel na cozinha durante o teste supervisionado
|
||||
|
||||
---
|
||||
|
||||
## 15. Sequência oficial de implementação (S0–S9)
|
||||
|
||||
> **Congelado 2026-07-23 (SPEC 0.2.1).**
|
||||
> Ordem: **KDS por estação antes de delivery/rotas.**
|
||||
> Ciclo inspirado no rusterando: `cart/PDV → kitchen board → driver tour → ORS`.
|
||||
> Não forkamos o binário Rust/AGPL; reutilizamos o **modelo operacional** no stack Nest/React.
|
||||
|
||||
### 15.1 Mapa de sprints
|
||||
|
||||
| Sprint | Bloco (rusterando) | Entrega Pizza Digital | Status |
|
||||
|--------|--------------------|------------------------|--------|
|
||||
| **S0** | Foundation | Repo + Docker Compose + domain types + SPEC | ✅ |
|
||||
| **S1** | Auth + menu admin | Auth JWT + Store + Cardápio + mapa item→estações | ✅ (`pizza.myvexx.com`) |
|
||||
| **S2** | Cart → order | **PDV** + criar pedido + state machine `confirmado→em_producao→pronto` | ✅ |
|
||||
| **S3** | Kitchen board | **KDS multi-estação** realtime (bump, timers, rooms Socket.io) | ✅ |
|
||||
| **S4** | Ready / expedição | Etiquetas + fila `pronto` + priorização + dashboard tempos | pendente |
|
||||
| **S5** | Piloto cozinha | 1 pizzaria estável sem papel (aceite §14) | pendente |
|
||||
| **S6** | Driver tour (manual) | Despacho: fila só `pronto` → agrupar bairro → motoboy → reordenar | pendente |
|
||||
| **S7** | **ORS** | Gestor de Rotas: `DispatchBatch` + mapa permanente + heurística; ORS com `ORS_API_KEY` | ✅ parcial (ORS opcional) |
|
||||
| **S8** | Driver + push | `/motoboy` + rota + GPS ping (+ push staff depois) | ✅ parcial (sem PIN/offline) |
|
||||
| **S9 / Track C1** | Public menu / pay | **Cardápio digital** + checkout (Pix / dinheiro) → quadro `/pedidos` | **PRIORIDADE pós-S5** |
|
||||
| **Track C2** | Agregadores | iFood webhook → Order normalizado | após C1 |
|
||||
|
||||
**Gate entre mundos (obrigatório):**
|
||||
`order.ready` → fila despacho → batch → ORS *(só a partir de S6/S7)*.
|
||||
|
||||
### 15.2 Módulo Rotas (CityFoods → Pizza Digital)
|
||||
|
||||
**Referência oficial de dispatch:** [rusterando](https://github.com/holg/rusterando) (AGPL-3.0).
|
||||
|
||||
| Subfase | Capacidade | Sprint |
|
||||
|---------|------------|--------|
|
||||
| MVP rotas | Fila só pedidos `pronto` | S6 |
|
||||
| MVP rotas | Agrupar por bairro (manual + sugestão) | S6 |
|
||||
| MVP rotas | Atribuir motoboy | S6 |
|
||||
| MVP rotas | Reordenar saída (“2 cliques” CityFoods) | S6 |
|
||||
| Ampliação | OpenRouteService — ordem ótima da tour | S7 |
|
||||
| Ampliação | Mapa no despacho | S7 |
|
||||
| Ampliação | PWA entregador + tracking | S8 |
|
||||
| Ampliação | Pagamento / comissão por entrega | S8–S9 |
|
||||
|
||||
Papéis rusterando → Pizza Digital: Admin≈gestor · Kitchen≈kds/estações · Driver≈entregador · Customer≈canal cardápio (S9).
|
||||
|
||||
### 15.3 Referências por domínio
|
||||
|
||||
| Domínio | Fonte |
|
||||
|---------|--------|
|
||||
| KDS / estações / bump | Este SPEC + kitchenOS / Tasty-Station |
|
||||
| Despacho / ORS / driver tour | **rusterando** (oficial) + CityFoods Gestor de Rotas |
|
||||
| UX “2 cliques” priorização | CityFoods |
|
||||
|
||||
### 15.4 Design UI
|
||||
|
||||
Status: **ainda não congelado.** S1 tem UI funcional (login + admin cardápio).
|
||||
Antes de S3: wireframes PDV → KDS (touch) → Despacho. Design system em documento separado (`docs/DESIGN.md`) quando existir.
|
||||
|
||||
---
|
||||
|
||||
## 16. Riscos
|
||||
|
||||
| Risco | Mitigação |
|
||||
|-------|-----------|
|
||||
| Cozinha rejeita tela (hábito do papel) | UX bump mínima + treinamento 1 turno + impressão híbrida opcional |
|
||||
| Impressora térmica incompatível | Driver ESC/POS + fallback PDF/A4 |
|
||||
| Pico de pedidos derruba WS | Redis pubsub + rooms por loja; load test 60/h |
|
||||
| Escopo inchando (virar iFood) | Freeze: MVP = cozinha digital, não marketplace |
|
||||
| Integração iFood cedo demais | Só após piloto estável |
|
||||
|
||||
---
|
||||
|
||||
## 17. Glossário
|
||||
|
||||
| Termo | Significado |
|
||||
|-------|-------------|
|
||||
| **KDS** | Kitchen Display System — tela digital da cozinha |
|
||||
| **Bump** | Marcar item/pedido como pronto na estação |
|
||||
| **Estação** | Setor de produção (massa, forno, …) |
|
||||
| **Ticket** | Representação do trabalho de um item/pedido numa estação |
|
||||
| **SLA** | Tempo alvo até alerta de atraso |
|
||||
| **Despacho** | Liberação do pedido pronto para entregador |
|
||||
| **Canal** | Origem do pedido (PDV, cardápio, iFood, …) |
|
||||
| **Cozinha Digital** | Operação completa sem comanda de papel + métricas |
|
||||
| **BOM / Ficha técnica** | Lista de insumos por item do cardápio |
|
||||
| **stockMode** | `bom` · `sku` · `none` — como o item consome estoque |
|
||||
| **CMV** | Custo da mercadoria vendida (teórico via BOM) |
|
||||
|
||||
---
|
||||
|
||||
## 18. Decisões em aberto (para fechar com Roger)
|
||||
|
||||
1. Stack final: **NestJS** ✅ (congelado Fase 1)
|
||||
2. Piloto: qual pizzaria / volume médio de pedidos/dia?
|
||||
3. Impressora térmica já definida (marca/modelo)?
|
||||
4. MVP inclui **mesa/comanda salão** ou só delivery + balcão?
|
||||
5. Hospedagem inicial → **Proxmox BIG VM125** (`10.10.10.125`) ✅
|
||||
6. Domínio → **`https://pizza.myvexx.com`** ✅
|
||||
7. Sequência S0–S9 + rusterando = ref. rotas → **congelado §15** ✅
|
||||
8. Design UI/UX (wireframes KDS/PDV/despacho) — em aberto
|
||||
|
||||
---
|
||||
|
||||
## 19. Referências
|
||||
|
||||
- CityFoods Cozinha Digital — https://abre.ai/cityfoods / https://cityfoods.com.br/
|
||||
- CityFoods Instagram — https://www.instagram.com/cityfoods_app/
|
||||
- kitchenOS — https://github.com/rajeshselvam02/kitchenOS
|
||||
- rusterando (**ref. oficial dispatch/ORS**) — https://github.com/holg/rusterando
|
||||
- URY / Mosaic — https://github.com/ury-erp/ury
|
||||
- MaSoVa — https://github.com/SVamseekar/masova-platform
|
||||
- Tasty-Station POS — https://github.com/hey-Zayn/Tasty-Station-POS
|
||||
|
||||
---
|
||||
|
||||
## 20. Próximo passo imediato
|
||||
|
||||
1. **S4** — Etiquetas + priorização + dashboard tempos (fechar cozinha)
|
||||
2. **S5** — Piloto pizzaria (cozinha estável)
|
||||
3. **Track C1 — entrada digital** — cardápio público → `/pedidos` (§5.5) ← **foco de produto**
|
||||
4. **Track E1 / M1** — Estoque + ficha técnica ligados ao cardápio Joinville
|
||||
5. Em paralelo ou depois: S6–S8 (rotas) · Track C2 (iFood)
|
||||
|
||||
---
|
||||
|
||||
*Documento vivo. Mudanças de escopo devem atualizar versão (ex.: 0.2.0 → 0.2.1) e registrar changelog em `docs/CHANGELOG-SPEC.md`.*
|
||||
85
docs/SSH-CLAUDE-CLOUD-HANDOFF.md
Normal file
85
docs/SSH-CLAUDE-CLOUD-HANDOFF.md
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Handoff — SSH Claude Cloud → VM125
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| Data | 2026-10-19 |
|
||||
| Status | **Parcial** — chave instalada; túnel Cloudflare **ainda não** provisionado |
|
||||
| Motivo da pausa | Usuário vai desligar a máquina |
|
||||
|
||||
---
|
||||
|
||||
## Objetivo
|
||||
|
||||
Dar SSH à VM125 (`mini@10.10.10.125`, projeto `~/pizza-digital`) para Claude Code em contêiner cloud que:
|
||||
|
||||
- não alcança `10.10.10.x` nem Tailscale `100.x`
|
||||
- só sai via proxy HTTPS (:443)
|
||||
- só acessa domínios liberados na política de rede do ambiente
|
||||
|
||||
---
|
||||
|
||||
## Hostname proposto
|
||||
|
||||
| Item | Valor |
|
||||
|------|--------|
|
||||
| Hostname | **`ssh-pizza.myvexx.com`** |
|
||||
| Proteção | Cloudflare Access + **service token** (Client ID + Client Secret) |
|
||||
| Cliente Claude | `cloudflared access ssh` como `ProxyCommand` |
|
||||
| Backend do tunnel | `localhost:22` na VM125 |
|
||||
|
||||
Service token: **não colar no chat** — configurar como secrets/env no ambiente Claude (`CF_ACCESS_CLIENT_ID`, `CF_ACCESS_CLIENT_SECRET`).
|
||||
|
||||
---
|
||||
|
||||
## Feito nesta sessão (antes do desligamento)
|
||||
|
||||
1. **Chave pública instalada** em `/home/mini/.ssh/authorized_keys`:
|
||||
```
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIlXrLNy18IDQcCqJd+NeXIbng4+39FRqi10CVbSPk2D claude-cloud-pizza
|
||||
```
|
||||
2. `chmod 700 ~/.ssh` · `chmod 600 ~/.ssh/authorized_keys`
|
||||
3. Projeto continua em `/home/mini/pizza-digital`
|
||||
|
||||
## Pendente (quando religar a VM)
|
||||
|
||||
1. Instalar `cloudflared` na VM125
|
||||
2. Criar Tunnel Cloudflare apontando SSH → `ssh-pizza.myvexx.com`
|
||||
3. DNS CNAME no zone `myvexx.com`
|
||||
4. Cloudflare Access Application (SSH) + **Service Token**
|
||||
5. Liberar `ssh-pizza.myvexx.com` (e `*.cloudflareaccess.com` se necessário) na Network access do ambiente Claude
|
||||
6. Testar: `ssh -o ProxyCommand='cloudflared access ssh --hostname %h' mini@ssh-pizza.myvexx.com`
|
||||
|
||||
## PasswordAuthentication
|
||||
|
||||
| Acesso | Política recomendada |
|
||||
|--------|----------------------|
|
||||
| LAN / Tailscale (atual) | Mantém `PasswordAuthentication yes` (ops local com senha `805353`) |
|
||||
| Via túnel Cloudflare | Só chave (`claude-cloud-pizza`); Access + service token; **não** depender de senha no caminho externo |
|
||||
|
||||
**Não** desligamos `PasswordAuthentication` globalmente nesta pausa (quebraria o hop local). Quando o túnel estiver estável, pode restringir senha só em `Match Address` / ou deixar senha só na LAN.
|
||||
|
||||
## Alternativa (se Cloudflare atrasar)
|
||||
|
||||
Hop público no big1 (`95.216.14.162` ou host público do Proxmox) com `sshd` em **443** + ProxyJump até `10.10.10.125`. Preferência atual do usuário: **Cloudflare Tunnel**.
|
||||
|
||||
---
|
||||
|
||||
## Credenciais locais (referência ops — não usar no Claude Cloud)
|
||||
|
||||
| Campo | Valor |
|
||||
|-------|--------|
|
||||
| Host LAN | `10.10.10.125` |
|
||||
| User | `mini` |
|
||||
| Password LAN | `805353` |
|
||||
| Hop Tailscale | `big1` → `100.116.138.100` |
|
||||
| Projeto | `~/pizza-digital` |
|
||||
|
||||
---
|
||||
|
||||
## Checklist pós-boot
|
||||
|
||||
- [ ] VM125 no ar · Docker pizza-digital up
|
||||
- [ ] `cloudflared` tunnel `ssh-pizza.myvexx.com` healthy
|
||||
- [ ] Access service token criado e só em env do Claude
|
||||
- [ ] Domínio liberado na Network access
|
||||
- [ ] Claude conecta com a chave `claude-cloud-pizza` sem senha
|
||||
40
docs/SYNC-PIZZA-DIGITAL.md
Normal file
40
docs/SYNC-PIZZA-DIGITAL.md
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
# Sync — Pizza Digital ↔ Obsidian
|
||||
|
||||
**Última sync:** 2026-11-09T19:44:42Z
|
||||
**Direção padrão:** VM125 → Vault (pull) · Vault → VM125 (push)
|
||||
|
||||
## Mapa
|
||||
|
||||
| Origem (VM125) | Destino (CT130 vault) |
|
||||
|----------------|------------------------|
|
||||
| `/home/mini/pizza-digital/SPEC.md` | `Pizza-Digital/SPEC.md` |
|
||||
| `/home/mini/pizza-digital/README.md` | `Pizza-Digital/README.md` |
|
||||
| `/home/mini/pizza-digital/docs/**` | `Pizza-Digital/docs/**` |
|
||||
|
||||
## Comandos
|
||||
|
||||
No **CT130** (hub):
|
||||
|
||||
```bash
|
||||
# VM → Vault
|
||||
/opt/ligbox-spec-hub/scripts/sync-pizza-digital.sh pull
|
||||
|
||||
# Vault → VM
|
||||
/opt/ligbox-spec-hub/scripts/sync-pizza-digital.sh push
|
||||
```
|
||||
|
||||
Na **VM125**:
|
||||
|
||||
```bash
|
||||
# Vault → VM (atualiza docs locais a partir do Obsidian)
|
||||
~/pizza-digital/scripts/sync_obsidian.sh pull
|
||||
|
||||
# VM → Vault
|
||||
~/pizza-digital/scripts/sync_obsidian.sh push
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- SSH: CT130 `root` → `mini@10.10.10.125` (chave `id_ed25519` spec-hub)
|
||||
- Não sincroniza código (`apps/`, `packages/`, `.env`) — só documentação
|
||||
- Após pull no vault, fazer commit no repo `obsidian-vault` se quiser versionar
|
||||
595
docs/data/fichas-pizzas-peso.json
Normal file
595
docs/data/fichas-pizzas-peso.json
Normal file
|
|
@ -0,0 +1,595 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"region": "Joinville/SC",
|
||||
"unit": "g",
|
||||
"notes": "Quantização de estoque por peso. Baixa = qty_g × OrderItem.quantity. Meia-a-meia = 0.5 × BOM de cada sabor.",
|
||||
"bases": {
|
||||
"grande": {
|
||||
"sizeCode": "grande",
|
||||
"diameterCm": 40,
|
||||
"slices": 12,
|
||||
"massa_g": 480,
|
||||
"molho_g": 90,
|
||||
"mussarela_base_g": 200,
|
||||
"scaleVsGrande": 1
|
||||
},
|
||||
"broto": {
|
||||
"sizeCode": "broto",
|
||||
"diameterCm": 25,
|
||||
"slices": 4,
|
||||
"massa_g": 200,
|
||||
"molho_g": 40,
|
||||
"mussarela_base_g": 85,
|
||||
"scaleVsGrande": 0.42
|
||||
}
|
||||
},
|
||||
"ingredientsCatalog": [
|
||||
{ "sku": "massa-bola", "name": "Massa (bola/disco)", "unit": "g", "minQty": 5000 },
|
||||
{ "sku": "molho-tomate", "name": "Molho de tomate", "unit": "g", "minQty": 2000 },
|
||||
{ "sku": "mussarela", "name": "Mussarela", "unit": "g", "minQty": 5000 },
|
||||
{ "sku": "calabresa", "name": "Calabresa fatiada", "unit": "g", "minQty": 2000 },
|
||||
{ "sku": "calabresa-moida", "name": "Calabresa moída", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "cebola", "name": "Cebola", "unit": "g", "minQty": 1000 },
|
||||
{ "sku": "frango", "name": "Frango desfiado", "unit": "g", "minQty": 2000 },
|
||||
{ "sku": "catupiry", "name": "Catupiry / requeijão", "unit": "g", "minQty": 2000 },
|
||||
{ "sku": "presunto", "name": "Presunto", "unit": "g", "minQty": 2000 },
|
||||
{ "sku": "ovo", "name": "Ovo", "unit": "un", "minQty": 30 },
|
||||
{ "sku": "azeitona", "name": "Azeitona", "unit": "g", "minQty": 800 },
|
||||
{ "sku": "provolone", "name": "Provolone", "unit": "g", "minQty": 1000 },
|
||||
{ "sku": "gorgonzola", "name": "Gorgonzola", "unit": "g", "minQty": 800 },
|
||||
{ "sku": "parmesao", "name": "Parmesão", "unit": "g", "minQty": 800 },
|
||||
{ "sku": "tomate", "name": "Tomate", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "manjericao", "name": "Manjericão", "unit": "g", "minQty": 200 },
|
||||
{ "sku": "bacon", "name": "Bacon", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "pepperoni", "name": "Pepperoni", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "lombo", "name": "Lombo defumado", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "pimenta", "name": "Molho de pimenta", "unit": "g", "minQty": 300 },
|
||||
{ "sku": "pimentao", "name": "Pimentão", "unit": "g", "minQty": 800 },
|
||||
{ "sku": "palmito", "name": "Palmito", "unit": "g", "minQty": 1200 },
|
||||
{ "sku": "brocolis", "name": "Brócolis", "unit": "g", "minQty": 1000 },
|
||||
{ "sku": "milho", "name": "Milho", "unit": "g", "minQty": 1000 },
|
||||
{ "sku": "ervilha", "name": "Ervilha", "unit": "g", "minQty": 800 },
|
||||
{ "sku": "atum", "name": "Atum", "unit": "g", "minQty": 1200 },
|
||||
{ "sku": "batata-palha", "name": "Batata palha", "unit": "g", "minQty": 800 },
|
||||
{ "sku": "estrogonofe-frango", "name": "Estrogonofe de frango", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "alho", "name": "Alho", "unit": "g", "minQty": 400 },
|
||||
{ "sku": "linguica-blumenau", "name": "Linguiça Blumenau", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "aipim", "name": "Purê de aipim", "unit": "g", "minQty": 2000 },
|
||||
{ "sku": "coracao-frango", "name": "Coração de frango", "unit": "g", "minQty": 1200 },
|
||||
{ "sku": "chocolate-preto", "name": "Chocolate preto", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "chocolate-branco", "name": "Chocolate branco", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "banana", "name": "Banana", "unit": "g", "minQty": 1500 },
|
||||
{ "sku": "canela", "name": "Canela", "unit": "g", "minQty": 200 },
|
||||
{ "sku": "leite-condensado", "name": "Leite condensado", "unit": "g", "minQty": 1000 },
|
||||
{ "sku": "coco", "name": "Coco ralado", "unit": "g", "minQty": 800 },
|
||||
{ "sku": "goiabada", "name": "Goiabada", "unit": "g", "minQty": 1000 },
|
||||
{ "sku": "morango", "name": "Morango", "unit": "g", "minQty": 1000 },
|
||||
{ "sku": "oregano", "name": "Orégano", "unit": "g", "minQty": 300 }
|
||||
],
|
||||
"recipes": [
|
||||
{
|
||||
"menuItemId": "seed-item-calabresa",
|
||||
"name": "Calabresa G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 930,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 200, "unit": "g" },
|
||||
{ "sku": "calabresa", "qty": 120, "unit": "g" },
|
||||
{ "sku": "cebola", "qty": 40, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-mussarela",
|
||||
"name": "Mussarela G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 772,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 200, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-frango-catupiry",
|
||||
"name": "Frango Catupiry G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 992,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "frango", "qty": 150, "unit": "g" },
|
||||
{ "sku": "catupiry", "qty": 90, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-portuguesa",
|
||||
"name": "Portuguesa G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 1012,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "presunto", "qty": 100, "unit": "g" },
|
||||
{ "sku": "ovo", "qty": 2, "unit": "un" },
|
||||
{ "sku": "cebola", "qty": 40, "unit": "g" },
|
||||
{ "sku": "azeitona", "qty": 40, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-quatro",
|
||||
"name": "4 Queijos G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 912,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 70, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 120, "unit": "g" },
|
||||
{ "sku": "provolone", "qty": 80, "unit": "g" },
|
||||
{ "sku": "gorgonzola", "qty": 60, "unit": "g" },
|
||||
{ "sku": "catupiry", "qty": 100, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-margherita",
|
||||
"name": "Marguerita G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 852,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "tomate", "qty": 80, "unit": "g" },
|
||||
{ "sku": "parmesao", "qty": 20, "unit": "g" },
|
||||
{ "sku": "manjericao", "qty": 8, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-bacon",
|
||||
"name": "Bacon G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 892,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 200, "unit": "g" },
|
||||
{ "sku": "bacon", "qty": 120, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-pepperoni",
|
||||
"name": "Pepperoni G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 922,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 200, "unit": "g" },
|
||||
{ "sku": "pepperoni", "qty": 120, "unit": "g" },
|
||||
{ "sku": "pimentao", "qty": 30, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-lombo-catupiry",
|
||||
"name": "Lombo Catupiry G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 962,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "lombo", "qty": 120, "unit": "g" },
|
||||
{ "sku": "catupiry", "qty": 90, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-baiana",
|
||||
"name": "Baiana G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 902,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "calabresa-moida", "qty": 130, "unit": "g" },
|
||||
{ "sku": "pimenta", "qty": 20, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-napolitana",
|
||||
"name": "Napolitana G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 842,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "tomate", "qty": 70, "unit": "g" },
|
||||
{ "sku": "parmesao", "qty": 20, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-presunto",
|
||||
"name": "Presunto G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 872,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 200, "unit": "g" },
|
||||
{ "sku": "presunto", "qty": 100, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-palmito",
|
||||
"name": "Palmito G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 892,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 200, "unit": "g" },
|
||||
{ "sku": "palmito", "qty": 120, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-brocolis",
|
||||
"name": "Brócolis G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 942,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "brocolis", "qty": 120, "unit": "g" },
|
||||
{ "sku": "bacon", "qty": 70, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-toscana",
|
||||
"name": "Toscana G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 922,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "calabresa-moida", "qty": 130, "unit": "g" },
|
||||
{ "sku": "cebola", "qty": 40, "unit": "g" },
|
||||
{ "sku": "pimenta", "qty": 15, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-moda-casa",
|
||||
"name": "Moda da Casa G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 1002,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "presunto", "qty": 80, "unit": "g" },
|
||||
{ "sku": "tomate", "qty": 50, "unit": "g" },
|
||||
{ "sku": "cebola", "qty": 30, "unit": "g" },
|
||||
{ "sku": "milho", "qty": 50, "unit": "g" },
|
||||
{ "sku": "ervilha", "qty": 40, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-atum",
|
||||
"name": "Atum G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 932,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "atum", "qty": 140, "unit": "g" },
|
||||
{ "sku": "cebola", "qty": 40, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-crocante",
|
||||
"name": "Crocante G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 922,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 200, "unit": "g" },
|
||||
{ "sku": "bacon", "qty": 100, "unit": "g" },
|
||||
{ "sku": "batata-palha", "qty": 50, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-estrogonofe",
|
||||
"name": "Estrogonofe Frango G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 1002,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 60, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 160, "unit": "g" },
|
||||
{ "sku": "estrogonofe-frango", "qty": 250, "unit": "g" },
|
||||
{ "sku": "batata-palha", "qty": 50, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-item-vegetariana",
|
||||
"name": "Vegetariana G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 972,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "brocolis", "qty": 70, "unit": "g" },
|
||||
{ "sku": "palmito", "qty": 60, "unit": "g" },
|
||||
{ "sku": "milho", "qty": 50, "unit": "g" },
|
||||
{ "sku": "ervilha", "qty": 40, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-broto-calabresa",
|
||||
"name": "Calabresa Broto",
|
||||
"sizeCode": "broto",
|
||||
"weightTotalApprox_g": 392,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 200, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 40, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 85, "unit": "g" },
|
||||
{ "sku": "calabresa", "qty": 50, "unit": "g" },
|
||||
{ "sku": "cebola", "qty": 15, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 1, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-broto-mussarela",
|
||||
"name": "Mussarela Broto",
|
||||
"sizeCode": "broto",
|
||||
"weightTotalApprox_g": 326,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 200, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 40, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 85, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 1, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-broto-frango",
|
||||
"name": "Frango Catupiry Broto",
|
||||
"sizeCode": "broto",
|
||||
"weightTotalApprox_g": 416,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 200, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 40, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 75, "unit": "g" },
|
||||
{ "sku": "frango", "qty": 60, "unit": "g" },
|
||||
{ "sku": "catupiry", "qty": 40, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 1, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-broto-portuguesa",
|
||||
"name": "Portuguesa Broto",
|
||||
"sizeCode": "broto",
|
||||
"weightTotalApprox_g": 426,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 200, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 40, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 75, "unit": "g" },
|
||||
{ "sku": "presunto", "qty": 45, "unit": "g" },
|
||||
{ "sku": "ovo", "qty": 1, "unit": "un" },
|
||||
{ "sku": "cebola", "qty": 15, "unit": "g" },
|
||||
{ "sku": "azeitona", "qty": 15, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 1, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-broto-4queijos",
|
||||
"name": "4 Queijos Broto",
|
||||
"sizeCode": "broto",
|
||||
"weightTotalApprox_g": 386,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 200, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 30, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 50, "unit": "g" },
|
||||
{ "sku": "provolone", "qty": 35, "unit": "g" },
|
||||
{ "sku": "gorgonzola", "qty": 25, "unit": "g" },
|
||||
{ "sku": "catupiry", "qty": 45, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 1, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-esp-joinville",
|
||||
"name": "Joinville G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 852,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 200, "unit": "g" },
|
||||
{ "sku": "alho", "qty": 20, "unit": "g" },
|
||||
{ "sku": "parmesao", "qty": 30, "unit": "g" },
|
||||
{ "sku": "pimentao", "qty": 40, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-esp-blumenau",
|
||||
"name": "Linguiça Blumenau G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 982,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "linguica-blumenau", "qty": 140, "unit": "g" },
|
||||
{ "sku": "catupiry", "qty": 90, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-esp-aipim-bacon",
|
||||
"name": "Aipim com Bacon G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 1012,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 60, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 150, "unit": "g" },
|
||||
{ "sku": "aipim", "qty": 200, "unit": "g" },
|
||||
{ "sku": "bacon", "qty": 120, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-esp-catarina",
|
||||
"name": "Catarina G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 942,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "calabresa-moida", "qty": 120, "unit": "g" },
|
||||
{ "sku": "ovo", "qty": 2, "unit": "un" },
|
||||
{ "sku": "cebola", "qty": 40, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-esp-aipim-blumenau",
|
||||
"name": "Aipim Blumenau G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 1052,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 60, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 150, "unit": "g" },
|
||||
{ "sku": "aipim", "qty": 200, "unit": "g" },
|
||||
{ "sku": "linguica-blumenau", "qty": 140, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-esp-coracao",
|
||||
"name": "Coração G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 972,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "molho-tomate", "qty": 90, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 180, "unit": "g" },
|
||||
{ "sku": "coracao-frango", "qty": 140, "unit": "g" },
|
||||
{ "sku": "catupiry", "qty": 80, "unit": "g" },
|
||||
{ "sku": "oregano", "qty": 2, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-doce-choc-preto",
|
||||
"name": "Chocolate Preto G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 780,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 100, "unit": "g" },
|
||||
{ "sku": "chocolate-preto", "qty": 200, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-doce-choc-branco",
|
||||
"name": "Chocolate Branco G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 780,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 100, "unit": "g" },
|
||||
{ "sku": "chocolate-branco", "qty": 200, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-doce-banana",
|
||||
"name": "Banana Canela G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 860,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 80, "unit": "g" },
|
||||
{ "sku": "banana", "qty": 200, "unit": "g" },
|
||||
{ "sku": "canela", "qty": 5, "unit": "g" },
|
||||
{ "sku": "leite-condensado", "qty": 95, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-doce-prestigio",
|
||||
"name": "Prestígio G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 840,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 80, "unit": "g" },
|
||||
{ "sku": "chocolate-preto", "qty": 180, "unit": "g" },
|
||||
{ "sku": "coco", "qty": 100, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-doce-romeu",
|
||||
"name": "Romeu e Julieta G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 810,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 120, "unit": "g" },
|
||||
{ "sku": "goiabada", "qty": 150, "unit": "g" },
|
||||
{ "sku": "provolone", "qty": 60, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-doce-morango",
|
||||
"name": "Morango Chocolate G",
|
||||
"sizeCode": "grande",
|
||||
"weightTotalApprox_g": 860,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 480, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 80, "unit": "g" },
|
||||
{ "sku": "morango", "qty": 150, "unit": "g" },
|
||||
{ "sku": "chocolate-preto", "qty": 150, "unit": "g" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"menuItemId": "seed-doce-broto-choc",
|
||||
"name": "Chocolate Broto",
|
||||
"sizeCode": "broto",
|
||||
"weightTotalApprox_g": 325,
|
||||
"lines": [
|
||||
{ "sku": "massa-bola", "qty": 200, "unit": "g" },
|
||||
{ "sku": "mussarela", "qty": 40, "unit": "g" },
|
||||
{ "sku": "chocolate-preto", "qty": 85, "unit": "g" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
31
scripts/sync_obsidian.sh
Executable file
31
scripts/sync_obsidian.sh
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env bash
|
||||
# Pizza Digital — sync docs com Obsidian (CT130)
|
||||
# Requer: chave SSH de mini → root@10.10.10.130 OU execute no CT130:
|
||||
# /opt/ligbox-spec-hub/scripts/sync-pizza-digital.sh {pull|push}
|
||||
set -euo pipefail
|
||||
MODE="${1:-}"
|
||||
CT130="root@10.10.10.130"
|
||||
REMOTE_SCRIPT="/opt/ligbox-spec-hub/scripts/sync-pizza-digital.sh"
|
||||
SSH_OPTS="-o BatchMode=yes -o ConnectTimeout=10 -o StrictHostKeyChecking=accept-new"
|
||||
|
||||
if [[ -z "$MODE" ]]; then
|
||||
echo "Usage: $0 {pull|push}"
|
||||
echo " pull = Vault → VM (atualiza docs locais)"
|
||||
echo " push = VM → Vault"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Map VM local names to CT130 script directions
|
||||
case "$MODE" in
|
||||
pull) REMOTE_MODE=push ;; # vault→vm is CT130 push
|
||||
push) REMOTE_MODE=pull ;; # vm→vault is CT130 pull
|
||||
*) echo "Usage: $0 {pull|push}"; exit 1 ;;
|
||||
esac
|
||||
|
||||
if ssh $SSH_OPTS "$CT130" "test -x $REMOTE_SCRIPT" 2>/dev/null; then
|
||||
ssh $SSH_OPTS "$CT130" "$REMOTE_SCRIPT $REMOTE_MODE"
|
||||
else
|
||||
echo "Sem SSH mini→CT130. Rode no CT130:"
|
||||
echo " $REMOTE_SCRIPT $REMOTE_MODE"
|
||||
exit 2
|
||||
fi
|
||||
Loading…
Reference in a new issue