feat: initial project structure (Model Project) - Backend + Multi-Frontend + Docker
This commit is contained in:
@@ -0,0 +1,352 @@
|
||||
# NewWhats v2.2 — Plano Real (pós-análise do cérebro)
|
||||
|
||||
> Documento gerado em 2026-04-18 após análise completa do motor, satélite, proxy e ProtocolEngine.
|
||||
> Base: código real de `newwhats.local` + `alemaoconveniencias.local/sistema-nuvem/plugins/newwhats/`.
|
||||
|
||||
---
|
||||
|
||||
## 1. Estado atual — O que já existe (v2.1)
|
||||
|
||||
### Motor (`newwhats.local:8008`)
|
||||
| Componente | Rota interna | Status |
|
||||
|---|---|---|
|
||||
| Agents CRUD | `GET/POST/PUT/DELETE /api/secretaria/agents` | ✅ operacional |
|
||||
| Brain Nodes | `GET/POST/PUT/DELETE /api/secretaria/nodes` | ✅ operacional |
|
||||
| Conversations | `GET/POST/PATCH /api/secretaria/conversations` | ✅ operacional |
|
||||
| Chat (brain) | `POST /api/secretaria/conversations/:id/chat` | ✅ operacional |
|
||||
| Finalizar protocolo | `POST /api/secretaria/conversations/:id/finalize` | ✅ operacional |
|
||||
| Calendar | `GET/POST /api/secretaria/calendar` | ✅ operacional |
|
||||
| sec_numbers | `GET/POST/PUT/DELETE /api/secretaria/numbers` | ✅ tabela + rotas ok |
|
||||
| WebSocket stream | `UPGRADE /api/ext/v1/stream` | ✅ operacional |
|
||||
| Sessions QR | `GET /api/ext/v1/sessions/:id/qr` | ✅ operacional |
|
||||
|
||||
### ProtocolEngine (cérebro) — arquitetura real
|
||||
```
|
||||
Sistema prompt = persona + knowledge + rules + calendar + escalation
|
||||
(nós ativos, ordenados por sort_order)
|
||||
|
||||
Economia de tokens:
|
||||
context_window = 8 → carrega apenas últimas 8 mensagens
|
||||
summarize a cada 10 trocas → atualiza conversation.summary
|
||||
system prompt nunca cresce — sempre reconstruído do zero
|
||||
|
||||
Fallback chain de providers:
|
||||
1. openai (gpt-4o-mini) — se openai_key configurado
|
||||
2. gemini (gemini-2.0-flash) — se gemini_key configurado
|
||||
3. anthropic (claude-3-5-haiku) — se anthropic_key configurado
|
||||
4. ollama (llama3) — sempre disponível (local)
|
||||
|
||||
Persona pode override o model via node_model (por nó)
|
||||
```
|
||||
|
||||
### Satélite (`sistema-nuvem`) — o que existe hoje
|
||||
```
|
||||
/api/nw/v1/* → proxy REST para /api/ext/v1/* ✅
|
||||
/api/nw/v1/stream → proxy WS ✅
|
||||
/api/webhook/newwhats → webhook-receiver.js ✅ (só loga)
|
||||
/api/admin/nw/sync-knowledge → syncKnowledge() ✅ (corrigido)
|
||||
/api/admin/nw/sync-knowledge/preview → buildKnowledgeText() ✅
|
||||
|
||||
Rotas Alemão expostas ao motor (/nw/*):
|
||||
GET /nw/cliente?telefone= → user + tickets + clubes + candidaturas ✅
|
||||
GET /nw/clube?cpf= → memberships + dependentes + parceiros ✅
|
||||
GET /nw/candidaturas?cpf= → candidaturas com status PT ✅
|
||||
GET /nw/parceiros?clube=slug → parceiros de um clube ✅
|
||||
GET /nw/vagas → vagas abertas formatadas ✅
|
||||
GET /nw/sorteio → sorteio ativo + contagem ✅
|
||||
GET /nw/sorteio/tickets?tel= → tickets do cliente ✅
|
||||
GET /nw/promocoes → promoções ativas ✅
|
||||
```
|
||||
|
||||
### Knowledge Node — situação atual
|
||||
```
|
||||
Agente: Ana — Atendente Virtual
|
||||
Node ID: ed968e6c-a031-44d2-8f5b-1ab4e6f55075
|
||||
Chars: 1895
|
||||
Última sync: 2026-04-18T23:28:46Z
|
||||
|
||||
Conteúdo real (dados do banco):
|
||||
- Clube Odonto (planos + parceiros)
|
||||
- Clube Médico (planos + parceiros)
|
||||
- Sorteio ativo (preço + disponíveis)
|
||||
- Promoções ativas
|
||||
- Vagas abertas
|
||||
- Atendimento geral
|
||||
```
|
||||
|
||||
### O que NÃO existe ainda
|
||||
- ❌ Auto-resposta (webhook só loga, nunca envia)
|
||||
- ❌ Lookup automático de dados do cliente ao receber mensagem
|
||||
- ❌ UI de gerenciamento de números no admin (Frente 1 em progresso)
|
||||
- ❌ Botão "Sincronizar Knowledge" no painel
|
||||
- ❌ Escalação para humano com notificação
|
||||
- ❌ Métricas de atendimento IA
|
||||
- ❌ Modo semi-auto (IA sugere, humano confirma)
|
||||
|
||||
---
|
||||
|
||||
## 2. Frentes v2.2
|
||||
|
||||
### Frente 1 — Gerenciamento de Números no Admin UI
|
||||
**Onde:** `admin-app/src/pages/WhatsAppInbox.tsx`
|
||||
|
||||
**O que fazer:**
|
||||
- [x] Constantes ROLE_LABELS, ROLE_COLORS, PRIORITY_LABELS
|
||||
- [x] Componente `NumberBadge` (role + prioridade visual)
|
||||
- [x] Modal `AddNumberModal` — 2 etapas:
|
||||
- Step 1: preenche label, role, área, prioridade, notas
|
||||
- Step 2: chama `NwService.createSession(label)` → `NwService.saveNumber(...)` → poll QR a cada 2s
|
||||
- [x] Modal `EditNumberModal` — edita metadados + toggle ativo/inativo
|
||||
- [ ] Estado + `fetchNumbers()` no componente principal
|
||||
- [ ] Botão "+ Novo Número" no header da aba Sessões
|
||||
- [ ] Session cards: badges de categoria/prioridade (cruzar sessions × numbers por instance_id)
|
||||
- [ ] Ações por sessão: botão Editar (abre EditModal) + Excluir (confirm → deleteSession + deleteNumber)
|
||||
|
||||
**Fluxo de dados:**
|
||||
```
|
||||
NwService.getNumbers() → /api/nw/v1/secretaria/numbers
|
||||
→ proxy → /api/ext/v1/secretaria/numbers
|
||||
→ motor → SELECT sec_numbers ORDER BY priority, label
|
||||
|
||||
Cruzamento visual: session.id === number.instance_id
|
||||
```
|
||||
|
||||
**Lógica de fallback (display):**
|
||||
```
|
||||
Ao mostrar sessões ordená-las por: priority ASC (números sem metadado = priority 99)
|
||||
Badge role: azul=notificacoes | laranja=entregas | roxo=suporte | rosa=marketing | cinza=geral
|
||||
Badge prioridade: verde=Principal | amarelo=Fallback | cinza=Terciário+
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Frente 2 — Auto-resposta Inteligente
|
||||
**Onde:** `sistema-nuvem/plugins/newwhats/webhook-receiver.js`
|
||||
**Depende de:** Frente 1 concluída (sec_numbers deve ter roles configurados)
|
||||
|
||||
**Princípio de funcionamento:**
|
||||
```
|
||||
Motor dispara webhook → POST /api/webhook/newwhats
|
||||
evento: message.new, fromMe: false
|
||||
|
||||
Satélite:
|
||||
1. Verifica HMAC (já existe)
|
||||
2. Lookup sec_numbers pelo instanceId → obtém role
|
||||
3. Se role == 'notificacoes' → NUNCA responder (só logar)
|
||||
4. Se role == 'suporte' | 'geral' | 'marketing' | 'entregas':
|
||||
a. Lookup ou cria conversa no motor: POST /api/secretaria/conversations
|
||||
b. Chama brain: POST /api/secretaria/conversations/:id/chat { message }
|
||||
c. Envia resposta: POST /api/ext/v1/inbox/:chatId/send { text: reply }
|
||||
d. Atualiza nw_event_logs com status='replied'
|
||||
```
|
||||
|
||||
**Modos de operação (configurável por plugin_config):**
|
||||
```
|
||||
auto_reply_mode:
|
||||
off → nunca responde (comportamento atual)
|
||||
assistido → IA sugere na inbox, humano confirma antes de enviar
|
||||
automatico → IA responde direto, sem intervenção humana
|
||||
```
|
||||
|
||||
**Throttle / anti-loop:**
|
||||
```
|
||||
- Nunca responder se fromMe = true
|
||||
- Cooldown: não responder ao mesmo chatId por 30s após última resposta automática
|
||||
- Máximo: 5 respostas automáticas por conversa antes de escalar
|
||||
- Horário: respeitar horário de atendimento (plugin_config: auto_reply_hours)
|
||||
```
|
||||
|
||||
**Tabela nova: `nw_auto_replies`**
|
||||
```sql
|
||||
CREATE TABLE nw_auto_replies (
|
||||
id SERIAL PRIMARY KEY,
|
||||
instance_id VARCHAR(128) NOT NULL,
|
||||
chat_id VARCHAR(128) NOT NULL,
|
||||
conv_id UUID, -- sec_conversations.id no motor
|
||||
reply TEXT,
|
||||
status VARCHAR(20) DEFAULT 'sent',-- sent | failed | pending_confirm
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Frente 3 — Botão "Sincronizar Knowledge" no Painel
|
||||
**Onde:** `admin-app/src/pages/WhatsAppInbox.tsx` (aba Sessões) + `admin-app/src/services/api.ts`
|
||||
|
||||
**O que fazer:**
|
||||
```typescript
|
||||
// api.ts — adicionar ao NwService:
|
||||
syncKnowledge: () =>
|
||||
api.post('/admin/nw/sync-knowledge').then(r => r.data),
|
||||
previewKnowledge: () =>
|
||||
api.get('/admin/nw/sync-knowledge/preview').then(r => r.data),
|
||||
```
|
||||
|
||||
**UI:**
|
||||
- Botão `Sincronizar IA` com ícone `Brain/Zap` no header da aba Sessões (ao lado do `+Novo Número`)
|
||||
- Estado: idle → syncing (spinner) → ok (checkmark verde 2s) → idle
|
||||
- Toast: "✅ Base sincronizada — 1895 chars — Ana atualizada"
|
||||
- Botão `Preview` (expandable) mostra o texto que será enviado ao motor
|
||||
|
||||
**Quando disparar automaticamente (futuro):**
|
||||
Webhook interno: quando admin salva promoção, clube, vaga → dispara `syncKnowledge()` em background.
|
||||
|
||||
---
|
||||
|
||||
### Frente 4 — Escalação para Humano
|
||||
**Onde:** `webhook-receiver.js` + novo endpoint no admin
|
||||
|
||||
**Gatilhos de escalação (detectados pelo brain):**
|
||||
- Palavra-chave na resposta da IA: `[ESCALAR]`, `[HUMANO]`, `transferir para atendente`
|
||||
- Mais de 5 trocas sem resolução (contador na conversa)
|
||||
- Cliente digita: "falar com atendente", "humano", "responsável"
|
||||
|
||||
**Fluxo:**
|
||||
```
|
||||
Brain retorna resposta com [ESCALAR] → satélite detecta
|
||||
→ marca conversa: status='escalated'
|
||||
→ INSERT nw_escalations (chat_id, conv_id, reason, created_at)
|
||||
→ badge vermelho na inbox do admin (unread count especial)
|
||||
→ IA para de responder automaticamente para esse chatId
|
||||
→ Humano assume via inbox normal
|
||||
```
|
||||
|
||||
**Tabela nova: `nw_escalations`**
|
||||
```sql
|
||||
CREATE TABLE nw_escalations (
|
||||
id SERIAL PRIMARY KEY,
|
||||
instance_id VARCHAR(128) NOT NULL,
|
||||
chat_id VARCHAR(128) NOT NULL,
|
||||
conv_id UUID,
|
||||
reason TEXT,
|
||||
resolved BOOLEAN DEFAULT FALSE,
|
||||
resolved_by VARCHAR(128),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
resolved_at TIMESTAMPTZ
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Frente 5 — Métricas de Atendimento IA
|
||||
**Onde:** `sistema-nuvem/server.js` + novo card no Dashboard admin
|
||||
|
||||
**KPIs:**
|
||||
```
|
||||
- Total de mensagens recebidas (nw_event_logs)
|
||||
- Total respondidas pela IA (nw_auto_replies)
|
||||
- Taxa de auto-atendimento: respondidas / recebidas
|
||||
- Escalações hoje / semana
|
||||
- Tempo médio de resposta (created_at do webhook → created_at do reply)
|
||||
- Distribuição por role (suporte X entregas X geral)
|
||||
- Top 5 sessões por volume
|
||||
```
|
||||
|
||||
**Endpoint:**
|
||||
```
|
||||
GET /api/admin/nw/metrics?period=7d
|
||||
→ { total_in, total_replied, escalations, avg_response_ms, by_role[], top_sessions[] }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Sequência de implementação recomendada
|
||||
|
||||
```
|
||||
Semana 1:
|
||||
[ ] Frente 1 — concluir WhatsAppInbox.tsx (estado + UI badges + botões)
|
||||
[ ] Frente 3 — botão SyncKnowledge no painel (rápido, alto impacto)
|
||||
|
||||
Semana 2:
|
||||
[ ] Frente 2 — webhook auto-resposta (modo 'automatico')
|
||||
→ criar nw_auto_replies
|
||||
→ integrar brain via fetch interno
|
||||
→ throttle + anti-loop
|
||||
|
||||
Semana 3:
|
||||
[ ] Frente 4 — escalação (detectar [ESCALAR] na resposta + tabela + badge)
|
||||
[ ] Frente 5 — métricas básicas (endpoint + card dashboard)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Dependências técnicas críticas
|
||||
|
||||
### 4.1 O motor precisa de webhook configurado para auto-resposta funcionar
|
||||
```
|
||||
Motor → POST /api/webhook/newwhats (satélite)
|
||||
Configurar via: POST /api/ext/v1/webhooks {
|
||||
url: 'https://alemaoconveniencias.com.br/api/webhook/newwhats',
|
||||
events: ['message.new', 'session.status'],
|
||||
secret: '<webhook_secret do plugin_configs>'
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 Rotas do motor que o satélite usa internamente (sem auth)
|
||||
```
|
||||
/api/secretaria/conversations → busca/cria conversa por chatId
|
||||
/api/secretaria/conversations/:id/chat → chama brain
|
||||
/api/secretaria/agents → lista agentes ativos
|
||||
```
|
||||
|
||||
### 4.3 Rota que satélite usa com auth (api key) para enviar resposta
|
||||
```
|
||||
POST /api/ext/v1/inbox/:chatId/send { text }
|
||||
Header: x-nw-key: <integration_key>
|
||||
```
|
||||
|
||||
### 4.4 Lookup chatId → conversa existente
|
||||
O motor hoje não tem endpoint `GET /api/secretaria/conversations?chatId=`.
|
||||
Precisará ser adicionado no motor ou o satélite usa `sec_conversations` via lookup próprio.
|
||||
**Solução recomendada:** adicionar no motor:
|
||||
```
|
||||
GET /api/secretaria/conversations?chat_id=<chatId>&create=true
|
||||
→ retorna conversa existente ou cria nova automaticamente
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Arquivos impactados
|
||||
|
||||
| Arquivo | Frente | Mudança |
|
||||
|---|---|---|
|
||||
| `plugins/newwhats/webhook-receiver.js` | F2 | auto-resposta + throttle |
|
||||
| `plugins/newwhats/sync-knowledge.js` | F3 | sem mudança (já funcional) |
|
||||
| `database/database.js` | F2+F4 | CREATE TABLE nw_auto_replies, nw_escalations |
|
||||
| `sistema-nuvem/server.js` | F3+F5 | /admin/nw/metrics |
|
||||
| `admin-app/src/pages/WhatsAppInbox.tsx` | F1+F3 | estado numbers + modais + badges + sync btn |
|
||||
| `admin-app/src/services/api.ts` | F3 | NwService.syncKnowledge + previewKnowledge |
|
||||
| `newwhats.local/plugins/secretaria/routes.ts` | F4 | GET /conversations?chat_id= |
|
||||
|
||||
---
|
||||
|
||||
## 6. Riscos e mitigações
|
||||
|
||||
| Risco | Impacto | Mitigação |
|
||||
|---|---|---|
|
||||
| IA responde cliente errado (loop) | Alto | `fromMe=true` nunca dispara; cooldown 30s; max 5 auto/conversa |
|
||||
| Knowledge desatualizado | Médio | Sync automático ao salvar promoção/vaga; preview antes do sync |
|
||||
| Motor fora do ar | Alto | Fallback chain (openai→gemini→anthropic→ollama); modo `off` por padrão |
|
||||
| Webhook sem HMAC | Médio | Em dev aceita sem secret + aviso; em prod bloqueia |
|
||||
| chatId não mapeado para conversa | Médio | Criar conversa automaticamente no primeiro contato |
|
||||
| QR code expirado no modal | Baixo | Poll a cada 2s, aviso "QR expira em 60s" |
|
||||
| Escalação ignorada | Alto | Badge vermelho persistente + email de alerta (futuro) |
|
||||
|
||||
---
|
||||
|
||||
## 7. Glossário técnico deste projeto
|
||||
|
||||
| Termo | Significado |
|
||||
|---|---|
|
||||
| Motor | `newwhats.local:8008` — instância independente do NewWhats |
|
||||
| Satélite | `sistema-nuvem` do alemão — proxy + regras de negócio |
|
||||
| ProtocolEngine | Classe `brain.ts` — core de IA stateful com nós |
|
||||
| Brain Node | Registro em `sec_brain_nodes` (persona/knowledge/rules/calendar/escalation) |
|
||||
| sec_numbers | Tabela do motor com metadados dos números (role, priority, area) |
|
||||
| nw_event_logs | Tabela do satélite com todos os eventos recebidos via webhook |
|
||||
| integration_key | Chave usada no header `x-nw-key` para rotas `/api/ext/v1/*` |
|
||||
| motorUrl | URL do motor, lida de `plugin_configs WHERE key='newwhats_url'` |
|
||||
| sync-knowledge | Script que lê BD alemão e atualiza o nó knowledge da Ana |
|
||||
| context_window | Número de mensagens recentes passadas ao LLM (padrão: 8) |
|
||||
| protocol_number | Identificador único da conversa formato `DDMMAAHHmmss` |
|
||||
Reference in New Issue
Block a user