Skip to content

About

Plataforma multi-espaço de pedidos por QR code para restaurantes: cardápio no celular, Kanban da cozinha, estoque, mesas/QR, equipe por espaço e avaliações. React 19 + TypeScript + Vite / Node.js + Express 5 + PostgreSQL. Roda local com docker compose.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Comanda Digital — cardápio por QR code e pedidos na mesa

Plataforma web para bares, restaurantes e eventos. O cliente lê o QR code da mesa, vê o cardápio do espaço, monta o carrinho e faz o pedido pelo celular. A cozinha recebe tudo num quadro Kanban que se atualiza sozinho, e o cliente acompanha o status do pedido em tempo quase real.

É multi-espaço: uma mesma instalação atende vários estabelecimentos, cada um com seu cardápio, estoque, mesas, equipe e QR codes. A mesma conta de cliente funciona em qualquer espaço.

Sobre esta versão. Versão anonimizada de um sistema real que desenvolvi para uma empresa; nomes, marca, domínios, conteúdo proprietário e dados foram substituídos por fictícios. A arquitetura, as regras de negócio e o código são os do sistema real.

English summary: Multi-tenant QR-code ordering platform for restaurants and events (React 19 + TypeScript + Vite + Tailwind / Node.js + Express 5 + PostgreSQL). Customers scan the table's QR code, browse the venue's menu and order from their phone. Staff work on an auto-refreshing kitchen Kanban (new → preparing → ready → delivered), manage menu, stock and tables, print per-table QR codes, take manual orders, close the kitchen with a message, and read customer reviews. Role-based access (platform admin, kitchen manager, waiter, customer) is enforced per venue by middleware. Optional WhatsApp Cloud API notification on each new order. Runs locally with one docker compose up.


Sumário


O que a plataforma faz

Cliente (celular)

Módulo O que faz
Entrada pela mesa O QR code abre /e/<espaço>/m/<mesa>. Dá para ler o QR pela própria tela (câmera do aparelho, decodificada com jsQR) ou colar o link. Se o cliente já estava em outra mesa, o app pergunta se ele quer trocar.
Conta Cadastro e login com e-mail e senha. O cadastro público só cria cliente: tentar mandar outro papel é recusado pela API.
Cardápio Produtos por categoria, com busca. Itens desabilitados não aparecem, itens sem estoque aparecem como esgotados e o preço pode ficar oculto (por exemplo, "prato do dia"). O cardápio é recarregado a cada 2 s, então o estoque na tela acompanha a cozinha.
Carrinho e pedido Carrinho salvo no aparelho, com observação por item. O pedido baixa o estoque dentro de uma transação. Também há o pedido por texto livre ("traz mais pão, por favor").
Acompanhamento Status do pedido (aguardando, em preparo, pronto) atualizado a cada 2 s, e histórico com "pedir de novo".
Cozinha fechada Quando a equipe fecha a cozinha, o cliente vê a mensagem e o horário de volta, e a API recusa novos pedidos.
Avaliação Nota de 1 a 5, motivo e comentário, com opção de enviar de forma anônima.

Equipe do espaço (gerente de cozinha e garçom)

  • Painel (/dashboard): lista os espaços de que a pessoa faz parte.
  • Kanban da cozinha (/e/<espaço>/kds): colunas Novos → Em preparo → Pronto, atualizadas a cada 2 s. Marcar como entregue tira o pedido do quadro e manda para o histórico.
  • Pedido avulso: a equipe lança um pedido para um cliente (com busca entre quem já pediu no espaço) ou para um nome qualquer, com mesa e observação.
  • Cardápio e estoque (gerente): categorias, produtos com foto por URL, edição, reposição de estoque, habilitar/desabilitar e excluir.
  • Abrir e fechar a cozinha com mensagem e horário de volta opcional.
  • Avaliações recebidas e histórico de pedidos entregues.
  • Mesas e QR codes (/e/<espaço>/qr): cria e remove mesas e imprime um QR por mesa.
  • Equipe (/e/<espaço>/manage): o gerente cadastra garçons. O administrador da plataforma cadastra gerentes e garçons e vincula contas que já existem.

Administrador da plataforma (/)

  • Cria espaços (o slug vira o endereço e não muda depois, para não invalidar QR codes impressos).
  • Ativa e desativa espaços. Espaço inativo some para o cliente.
  • Acessa o painel de qualquer espaço para dar suporte.

Papéis e permissões

A API confere tudo em cada requisição. Papel global vem do usuário, e o acesso a um espaço vem do vínculo (space_memberships).

Ação Cliente Garçom Gerente Admin
Ver cardápio, pedir, avaliar ✓
Ver pedidos e mudar status ✓ ✓ ✓
Cardápio, estoque, avaliações, pedido avulso ✓ ✓
Abrir/fechar cozinha, cadastrar garçom ✓ ✓
Mesas e QR codes¹ ✓ ✓
Criar/desativar espaços, cadastrar gerente ✓

¹ A tela de mesas e QR codes é do gerente e do admin. Na API, as rotas de mesas aceitam qualquer membro da equipe do espaço.


Arquitetura

flowchart LR
  subgraph Cliente
    C[Celular do cliente<br/>React SPA]
  end
  subgraph Equipe
    K[Kanban da cozinha<br/>e painéis]
  end

  subgraph API[API Node.js / Express 5]
    MW[Middlewares<br/>JWT, papel, espaço via X-Space-ID]
    R[Rotas REST<br/>auth, spaces, items, orders, reviews]
    WA[Aviso de pedido<br/>opcional]
  end

  DB[(PostgreSQL)]
  META[WhatsApp Cloud API]

  C -- Bearer + X-Space-ID --> MW
  K -- polling 2s --> MW
  MW --> R
  R --> DB
  R --> WA
  WA -.-> META
Loading

Fluxo principal

  1. O administrador cria o espaço. A API cria as mesas padrão (Mesa-1 a Mesa-5 e Balcão), e a equipe imprime os QR codes.
  2. O cliente lê o QR. O front busca o espaço pelo slug e, dali em diante, manda o id do espaço no cabeçalho X-Space-ID em toda chamada.
  3. O pedido é gravado numa transação que trava cada produto (SELECT ... FOR UPDATE), confere disponibilidade e estoque e dá a baixa. Se um item falhar, nada é gravado.
  4. A cozinha vê o pedido no Kanban e muda o status. O celular do cliente acompanha pelo endpoint de rastreio, que só devolve pedidos do próprio cliente.
  5. Opcionalmente, cada pedido novo dispara um template do WhatsApp para a equipe.

Stack

Frontend: React 19, TypeScript 5.9, Vite 7, Tailwind CSS 3, React Router 7, Zustand (sessão, carrinho e espaço atual, com persist), Framer Motion, qrcode.react (geração), jsqr (leitura), react-hot-toast, Lucide.

Backend: Node.js 22, Express 5, PostgreSQL (pg), JWT (jsonwebtoken), senha com scrypt do crypto nativo (salt por usuário e comparação em tempo constante), express-rate-limit, CORS configurável.

Infra: Dockerfiles (a imagem da API roda sem root) e docker-compose para o ambiente local. Em produção roda numa VPS com deploy por GitHub Actions; essa parte foi retirada deste repositório.


Rodar localmente

Opção 1: Docker (recomendado)

Pré-requisito: Docker Desktop.

git clone https://github.com/vitormouramaker/comanda-digital-platform.git
cd comanda-digital-platform
docker compose up --build

Na subida, o compose cria o banco, as tabelas e os dados de demonstração.

Opção 2: Sem Docker

Pré-requisitos: Node.js 22+ e PostgreSQL 14+ rodando localmente.

# 1. API
cd backend
cp .env.example .env          # ajuste DB_USER / DB_PASSWORD / DB_PORT se precisar
npm install
npm run db:init               # cria o banco comanda_digital e as tabelas
npm run seed:demo             # contas, espaço e cardápio de demonstração (senha Demo@123)
npm run dev                   # http://localhost:4444

# 2. Front (outro terminal)
cd frontend
npm install
npm run dev                   # http://localhost:5173

O front usa http://localhost:4444 por padrão. Para apontar para outra API, defina VITE_API_URL em frontend/.env.local.

Sem chaves externas, tudo funciona

Sem... Comportamento
WhatsApp (WHATSAPP_*) O pedido é feito normalmente e o aviso aparece só no console da API.

Contas de demonstração

Criadas por npm run seed:demo (ou automaticamente no Docker). Senha de todas: Demo@123. As contas existem só no seu banco local. O roteiro se recusa a rodar com NODE_ENV=production.

Perfil E-mail Onde entrar
Administrador da plataforma master@demo.comanda.test http://localhost:5173/login
Gerente de cozinha do "Bistrô Demonstração" cozinha@demo.comanda.test http://localhost:5173/login
Garçom do "Bistrô Demonstração" garcom@demo.comanda.test http://localhost:5173/login
Cliente cliente@demo.comanda.test http://localhost:5173/e/bistro-demo/m/Mesa-1

O seed também cria o espaço bistro-demo, as mesas padrão e um cardápio fictício com produtos em estoque, com estoque infinito, com preço oculto e um produto indisponível (que só a equipe vê).

Roteiro para testar

  1. Abra http://localhost:5173/e/bistro-demo/m/Mesa-1 e entre como cliente (ou crie uma conta nova).
  2. Coloque dois itens no carrinho, com observação, e faça o pedido. Mande também um pedido por texto.
  3. Numa janela anônima, entre como cozinha em /login, abra o espaço e vá ao Kanban. Os pedidos aparecem em até 2 s. Mova um deles até "Pronto" e veja o status mudar no celular do cliente.
  4. Ainda como cozinha: reponha estoque, desabilite um produto (ele some do cardápio do cliente), lance um pedido avulso e feche a cozinha com uma mensagem. Tente pedir como cliente: a mensagem aparece e a API recusa.
  5. Como garçom, confira que dá para tocar o Kanban, mas não editar o cardápio.
  6. Como master, crie um espaço novo, cadastre um gerente para ele e abra /e/<slug>/qr para imprimir os QR codes.
  7. Como cliente, avalie o atendimento e veja a avaliação no painel da cozinha.

Integrações

WhatsApp Cloud API (Meta) — opcional

  • Implementado em backend/src/services/whatsapp.js.
  • A cada pedido (por itens ou por texto), envia um template aprovado com três parâmetros: cliente, espaço e itens (com observações).
  • O envio não segura a resposta do pedido, e uma falha no WhatsApp vira só log: o pedido nunca é perdido por causa do aviso.
  • Token, id do número, destinatário, template, idioma e versão da API vêm de variáveis de ambiente. Sem elas, a integração fica desligada.

Segurança

  • Senhas com scrypt e salt aleatório por usuário, comparadas com timingSafeEqual.
  • JWT assinado com segredo vindo do ambiente (HS256 fixo na verificação). Em produção a API não sobe sem um segredo de pelo menos 32 caracteres. A cada requisição, o usuário é recarregado do banco, então papel e vínculos valem na hora, não quando o token foi emitido.
  • Autorização em camadas: authenticateToken → requireRole (papel global) → requireSpaceAccess (vínculo com o espaço do X-Space-ID) → requireSpaceMemberRole (papel dentro do espaço). Toda query de escrita filtra também por space_id, para um espaço não alterar dados de outro.
  • Cadastro público só cria cliente. Vincular uma conta existente à equipe exige que ela já tenha o mesmo papel, para não promover cliente a equipe.
  • O pedido valida quantidade (inteira, de 1 a 99) e status (lista fechada), e o rastreio só devolve pedidos do próprio cliente.
  • Rate limit em login e cadastro, CORS com lista de origens e x-powered-by desligado. A rota pública do espaço não expõe ids internos de usuário.
  • Todas as queries são parametrizadas.

Estrutura do repositório

comanda-digital-platform/
├── docker-compose.yml          # Postgres + API + front para rodar local
├── backend/
│   ├── src/
│   │   ├── app.js              # bootstrap, CORS, rate limit, rotas
│   │   ├── config/env.js       # leitura e validação do ambiente
│   │   ├── routes/             # auth, spaces (espaços, mesas, equipe, cozinha),
│   │   │                       # delivery (categorias, itens, pedidos, avaliações)
│   │   ├── models/             # SQL por domínio (usuários, espaços, cardápio/pedidos)
│   │   ├── security/           # sessão/JWT e middlewares de acesso, hash de senha
│   │   ├── services/           # aviso de pedido pelo WhatsApp
│   │   └── server/             # conexão, schema idempotente, init do banco
│   └── scripts/seed-demo.js    # dados de demonstração
└── frontend/
    └── src/
        ├── pages/              # cardápio do cliente, Kanban, painéis, mesas/QR, equipe
        ├── components/         # carrinho, leitor de QR, estoque, pedido avulso, avaliações...
        ├── store/              # Zustand: sessão, carrinho, espaço atual
        └── lib/                # cliente da API (token + X-Space-ID), mesas/QR

Variáveis de ambiente

Todas estão documentadas em backend/.env.example e frontend/.env.example.

Variável Para quê Obrigatória localmente
PORT Porta da API (padrão 4444) não
DB_* Conexão com o PostgreSQL sim (o exemplo já funciona)
JWT_SECRET Assinatura dos tokens (≥ 32 caracteres; obrigatória em produção) sim (o exemplo já funciona)
JWT_EXPIRES_IN Validade da sessão (padrão 7d) não
CORS_ORIGINS Origens liberadas, separadas por vírgula (vazio = qualquer uma) não
WHATSAPP_TOKEN, WHATSAPP_PHONE_NUMBER_ID, WHATSAPP_NOTIFY_TO Liga o aviso de pedido novo não
WHATSAPP_TEMPLATE, WHATSAPP_TEMPLATE_LANGUAGE, WHATSAPP_API_VERSION Ajustes do template não
VITE_API_URL (front) Endereço da API usado pelo front não

Decisões técnicas

  • Multi-tenant por cabeçalho. O front resolve o espaço pelo slug da URL uma vez e manda o id em X-Space-ID. As rotas de cardápio e pedidos ficam iguais para todos os espaços, e o middleware garante o isolamento.
  • Estoque consistente sob concorrência. O pedido trava as linhas dos produtos (FOR UPDATE) e faz tudo numa transação. stock = NULL significa estoque infinito, e a reposição religa o produto automaticamente quando o estoque volta a ser positivo.
  • Schema idempotente no código (ensure_schema.js). A cada subida, a API confere e completa o banco (CREATE ... IF NOT EXISTS, ADD COLUMN IF NOT EXISTS). Um banco vazio e um de produção chegam ao mesmo estado sem ferramenta de migração.
  • Polling em vez de WebSocket. Kanban e acompanhamento consultam a API a cada 2 s. Para o volume de um salão, isso é simples, aguenta queda de rede sem lógica de reconexão e passa por qualquer proxy.
  • Slug imutável. O endereço do espaço vai impresso nos QR codes das mesas, então não pode mudar depois de criado.
  • Integração que não derruba o fluxo. O aviso de WhatsApp é disparado sem bloquear a resposta e sem propagar erro.

Autor: @vitormouramaker

Este código é exibido como portfólio. Ele não tem licença de uso aberta: todos os direitos são reservados.

About

Plataforma multi-espaço de pedidos por QR code para restaurantes: cardápio no celular, Kanban da cozinha, estoque, mesas/QR, equipe por espaço e avaliações. React 19 + TypeScript + Vite / Node.js + Express 5 + PostgreSQL. Roda local com docker compose.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages