Add Pizza-Digital docs to Obsidian vault and sync with VM125.

Mirror all SPECs and construction docs from pizza-digital, add hub/sidebar links and bidirectional sync scripts.
This commit is contained in:
Roger 2026-11-09 19:45:11 +00:00
parent d882719549
commit 62a20a2573
31 changed files with 5052 additions and 0 deletions

View file

@ -0,0 +1 @@
2026-11-09T19:44:56Z

View file

@ -0,0 +1 @@
2026-11-09T19:44:43Z

View 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

29
Pizza-Digital/README.md Normal file
View file

@ -0,0 +1,29 @@
# Pizza Digital — Fase 1 bootstrap Docker
## Subir tudo (na VM 125)
```bash
cd ~/pizza-digital
cp -n .env.example .env
docker compose down
# parar coming-soon se estiver na :3000
docker rm -f pizza-coming-soon 2>/dev/null || true
docker compose up --build -d
docker compose ps
curl -sS http://127.0.0.1:3000/api/health
```
## Serviços
| Container | Porta | Função |
|-----------|-------|--------|
| pizza-gateway | **3000** | Nginx → web + `/api` |
| pizza-api | 4000 | NestJS + Socket.io |
| pizza-web | (interno) | React build |
| pizza-postgres | — | Postgres 16 |
| pizza-redis | — | Redis 7 |
## Apps web
- `/` home + status
- `/pdv` `/kds/:station` `/despacho` `/admin` (scaffold)

724
Pizza-Digital/SPEC.md Normal file
View 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`.*

View 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

View 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)

View 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) |

View 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

View 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.

View 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';
```

View 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 | ☐ |

View 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).

View 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).

View 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.*

View 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.*

View 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 &lt; 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 &lt; 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.*

View 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.

View 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)

View 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.*

View 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.

View 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.

View 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.*

View 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.

View 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
Pizza-Digital/docs/SPEC.md Normal file
View 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 &lt; 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`.*

View 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

View 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

View 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" }
]
}
]
}

View 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

View file

@ -12,6 +12,7 @@
- [VM500 PROJETOS](ligbox-ops-platform/docs/vms/VM500.md) - [VM500 PROJETOS](ligbox-ops-platform/docs/vms/VM500.md)
- [SPEC VM500 Rent Charge](Infra-Network/SPEC-VM500-RENTCHARGE.md) - [SPEC VM500 Rent Charge](Infra-Network/SPEC-VM500-RENTCHARGE.md)
- [Rentcharge Cloudflare Tunnel](Infra-Network/RENTCHARGE_CLOUDFLARE_TUNNEL.md) - [Rentcharge Cloudflare Tunnel](Infra-Network/RENTCHARGE_CLOUDFLARE_TUNNEL.md)
- [Rently — Guias rápidos (PDF)](Infra-Network/guides/README.md)
- [Cursor Tapo Tuya MCP](Infra-Network/CURSOR_TAPO_TUYA_MCP.md) - [Cursor Tapo Tuya MCP](Infra-Network/CURSOR_TAPO_TUYA_MCP.md)
--- ---
## Casa Dublin (energia) ## Casa Dublin (energia)
@ -19,6 +20,18 @@
- [Boiler PROJECT](Boiler-Tuya-Energy/PROJECT.md) - [Boiler PROJECT](Boiler-Tuya-Energy/PROJECT.md)
- [R630 Tapo dashboard](https://r630.myvexx.com/) - [R630 Tapo dashboard](https://r630.myvexx.com/)
--- ---
## Pizza Digital (VM125)
- [Hub Pizza](Pizza-Digital/Pizza-Digital.md)
- [SPEC mãe](Pizza-Digital/SPEC.md)
- [SPEC Fase 1](Pizza-Digital/docs/SPEC-FASE1.md)
- [Canais digitais](Pizza-Digital/docs/SPEC-CANAIS-DIGITAIS.md)
- [Cardápio](Pizza-Digital/docs/SPEC-CARDAPIO.md)
- [Estoque](Pizza-Digital/docs/SPEC-ESTOQUE.md)
- [Matriz acesso](Pizza-Digital/docs/SPEC-MATRIZ-ACESSO.md)
- [KDS Claim](Pizza-Digital/docs/SPEC-KDS-CLAIM.md)
- [Despacho CityFoods](Pizza-Digital/docs/SPEC-DESPACHO-CITYFOODS-PARITY.md)
- [Sync Pizza](Pizza-Digital/SYNC-PIZZA-DIGITAL.md)
---
## MyVexx / Invest ## MyVexx / Invest
- [Invest hub](Invest-MyVexx/Invest-MyVexx.md) - [Invest hub](Invest-MyVexx/Invest-MyVexx.md)
- [SPEC-050 AI Agents](Invest-MyVexx/SPEC-050-INVEST-AI-AGENTS-PAPER.md) - [SPEC-050 AI Agents](Invest-MyVexx/SPEC-050-INVEST-AI-AGENTS-PAPER.md)