App Desktop (Electron)
O PopinaFlow Desktop é um instalador nativo (.exe / .dmg / .AppImage) que embute o NestJS, o MongoDB e o frontend num único executável — sem necessidade de Docker, Node.js ou configuração de servidor. Ideal para restaurantes que precisam de operação offline completa com acesso de outros dispositivos na rede local.
Arquitetura
Electron (modo servidor)
├── MongoDB → localhost:27018 (interno, mongodb-memory-server)
├── NestJS → localhost:3001 (interno, processo filho via fork)
└── HTTP Proxy → 0.0.0.0:3000 (visível na LAN)
├── GET /* serve frontend/dist (arquivos estáticos + SPA fallback)
├── /api/* proxy → localhost:3001 (reescreve Origin header)
└── WebSocket proxy upgrade → localhost:3001
Janela Electron → electron-serve (https://localhost), apiUrl direto em localhost:3001
Browsers na LAN → http://192.168.x.x:3000 (VITE_API_URL=/api relativo via proxy)Portas
| Serviço | Porta | Visibilidade |
|---|---|---|
| MongoDB | 27018 | localhost apenas |
| NestJS | 3001 | localhost apenas |
| HTTP Proxy | 3000 | 0.0.0.0 — toda a LAN |
O NestJS nunca é exposto diretamente na LAN. Todo o tráfego externo passa pelo proxy na porta 3000, que também serve os arquivos estáticos do frontend.
Por que o proxy reescreve o header Origin?
O NestJS tem CORS configurado para aceitar https://localhost (Electron renderer). Browsers na LAN enviam Origin: http://192.168.x.x:3000. O proxy intercepta e substitui por Origin: https://localhost antes de repassar ao NestJS — sem nenhuma mudança no backend.
Modos de operação
O app inicia na tela de Configuração inicial e pede para escolher um modo. A escolha fica salva em userData/mode-config.json.
Modo Servidor
- Inicia MongoDB + NestJS + proxy
- Exibe o IP da LAN, um QR code e links copiáveis para clientes e garçons
- Outros dispositivos na mesma rede Wi-Fi acessam via browser em
http://IP:3000 - Outros computadores com o app instalado podem conectar como modo cliente
Modo Cliente (terminal)
- O computador não executa servidor local
- O usuário informa o IP do computador que está no modo servidor
- O app faz health check em
http://IP:3000/api/healthantes de aceitar o IP - Útil para caixas ou tablets de garçom usando o app Electron
Armazenamento de dados
| Item | Localização |
|---|---|
| Dados do MongoDB | userData/mongodb-data/ (wiredTiger, persistente) |
| JWT Secret | userData/.jwt-secret (gerado na 1ª execução, persistido) |
| Modo escolhido | userData/mode-config.json |
userData resolve para:
- Windows:
%APPDATA%\PopinaFlow - macOS:
~/Library/Application Support/PopinaFlow - Linux:
~/.config/PopinaFlow
Binários do MongoDB embutidos
O instalador inclui binários pré-compilados do MongoDB 7.0.14:
| Plataforma | Arquivo |
|---|---|
| Windows x64 | resources/mongodb-binaries/mongod-x64-windows-7.0.14.exe |
| macOS x64 | resources/mongodb-binaries/mongod-x64-macos-7.0.14 |
| Linux x64 | resources/mongodb-binaries/mongod-x64-ubuntu-7.0.14 |
Em desenvolvimento (sem app.isPackaged), o mongodb-memory-server faz auto-download do binário na primeira execução.
Frontend no modo Electron
O frontend é buildado com VITE_API_URL=/api (URL relativa):
cd frontend-react && vite build --mode electronIsso usa frontend-react/.env.electron que define VITE_API_URL=/api. Assim:
- Janela Electron: usa
window.electron.apiUrl = http://localhost:3001/api(tem precedência sobreVITE_API_URLemapi.ts) - Browsers na LAN:
window.electronénull, usamVITE_API_URL=/api→ resolves parahttp://192.168.x.x:3000/api→ proxy → NestJS
Build de distribuição
cd electron
# Todas as plataformas (requer ambiente adequado)
npm run dist
# Por plataforma
npm run dist:win # Windows NSIS installer (.exe)
npm run dist:mac # macOS DMG (.dmg)
npm run dist:linux # AppImage + .debO electron-builder.yml configura o que vai dentro do instalador:
resources/
├── dist/ ← frontend-react/dist (buildado com --mode electron)
├── backend/ ← backend compilado com ncc (bundle único index.js)
└── mongodb-binaries/ ← binários mongod por plataformaDesenvolvimento local
# 1. Build do backend (necessário antes do Electron em dev)
cd backend && npm run build
# 2. Build do frontend para Electron
cd frontend-react && vite build --mode electron
# 3. Iniciar o app
cd electron && npm startEm modo dev (!app.isPackaged):
- Backend:
../backend/dist/main.js - Frontend dist:
../frontend-react/dist - MongoDB: auto-download na 1ª execução
IPC disponível no renderer
Exposto via window.electron (contextBridge em preload.js):
| Método | Descrição |
|---|---|
isElectron | true — permite ao frontend detectar o ambiente |
apiUrl | URL base da API (definida pelo main antes de criar a janela) |
wsUrl | URL base para WebSocket |
getMode() | Retorna { mode, serverIp } — modo atual salvo |
startServerMode() | Inicia MongoDB + NestJS + proxy; resolve com { apiUrl, wsUrl, lanIPs, port } |
startClientMode(ip) | Valida conectividade e salva modo cliente; resolve com { apiUrl, wsUrl } |
resetMode() | Limpa o modo salvo e recarrega a janela |
onSetupLog(cb) | Recebe logs de texto durante o startup (retorna função de unsubscribe) |
generateQr(text) | Gera QR code como data:image/png;base64,... via Node no processo principal |
Fallback de porta
Se a porta 3000 estiver ocupada, startProxyServer() tenta 3001, 3002... até encontrar uma disponível. O port real é retornado em startServerMode() e exibido na UI.
CSP (Content Security Policy)
Em modo local, o CSP permite conexões para:
http://localhost:3001 ws://localhost:3001 ← janela Electron (direto)
http://192.168.0.0/8 ws://192.168.0.0/8 ← subnet típica de LANEm modo cloud, apenas https://popinaflow.com, https://api.popinaflow.com e WSS equivalentes.
Comparação: Electron vs Docker
| Aspecto | Electron | Docker |
|---|---|---|
| Pré-requisito | Nenhum (instalador standalone) | Docker Desktop instalado |
| Setup inicial | < 1 minuto | 5–10 minutos (download de imagens) |
| Atualização | Novo instalador | Atualizar-Servidor.bat |
| Acesso LAN | http://IP:3000 (automático) | http://IP (porta 80) |
| QR code | Gerado automaticamente no app | Manual |
| Sincronização cloud | SYNC_ENABLED=false (offline puro) | Sync a cada 5 min |
| Memória estimada | ~400 MB | ~800 MB (3 containers) |
| Indicado para | Restaurante único, sem IT | Multi-unidade, com sync cloud |