Files

234 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Secretária Virtual — Roadmap Completo (Opção 3 Híbrida)
## Arquitetura Alvo
```
Plugin Alemão (satélite) Motor NewWhats
┌──────────────────────────────┐ ┌──────────────────────────────┐
│ webhook-receiver.js │ POST ask │ ProtocolEngine (brain.ts) │
│ └─ context-builder.js │ ───────────► │ system prompt dinâmico │
│ └─ DB local (clientes, │ │ histórico + summarização │
│ planos, promoções) │ ◄─────────── │ fallback chain AI providers │
│ │ reply │ sec_conversations/messages │
│ HandoffButton (UI) │ │ │
│ └─ per-chat mode: │ PATCH mode │ chat_handoff_mode │
│ ia | humano | pausado │ ───────────► │ (campo por conversa) │
└──────────────────────────────┘ └──────────────────────────────┘
```
---
## LEGENDA DE STATUS
- ✅ Implementado e funcionando
- 🔲 Pendente — não iniciado
- 🚧 Parcialmente feito
---
## BLOCO 1 — Handoff Humano ↔ IA (Botão Flutuante)
> Botão flutuante acima do input bar no chat. Um clique pausa a IA e o humano
> assume. Outro clique devolve o controle à IA. Se o humano ficar 15 min sem
> responder (ou a conexão cair), a IA reassume automaticamente.
### 1.1 — Motor: campo de modo por conversa
- 🔲 Adicionar coluna `handoff_mode` ENUM(`ia`, `humano`, `pausado`) DEFAULT `ia` na tabela `sec_conversations` (migration Knex)
- 🔲 Adicionar coluna `handoff_human_at` TIMESTAMP NULL na tabela `sec_conversations` (registra quando humano assumiu)
- 🔲 Rodar migration no motor (`plugins/secretaria/migrate.ts`)
### 1.2 — Motor: rotas de handoff (ext-api)
- 🔲 Criar `PATCH /ext/v1/sec/conversations/:id/handoff` — body `{ mode: 'ia'|'humano' }` — atualiza `handoff_mode` e `handoff_human_at`
- 🔲 Em `POST /ext/v1/secretaria/ask` — verificar `handoff_mode` antes de chamar brain: se `humano`, retornar `{ skipped: true }` sem chamar IA
### 1.3 — Motor: timer de retomada automática (15 min)
- 🔲 Criar job BullMQ `handoff-timeout` no motor: ao mudar para modo `humano`, enfileirar job com delay de 15 min
- 🔲 Job verifica se `handoff_mode` ainda é `humano` E `handoff_human_at` há mais de 15 min → muda para `ia` → emite evento WebSocket `conversation.handoff` para o satélite
- 🔲 Ao receber mensagem humana (`PATCH /handoff mode=humano`), cancelar job anterior e enfileirar novo (reset do timer)
- 🔲 Se conexão WS do satélite cair (`disconnect`), mudar todas as conversas desse satélite em modo `humano` para `ia` automaticamente
### 1.4 — Frontend: NwService métodos
- 🔲 Adicionar `NwService.getHandoffMode(chatId)``GET /nw/v1/sec/conversations/:id/handoff` (ou campo incluso no chat)
- 🔲 Adicionar `NwService.setHandoffMode(chatId, mode)``PATCH /nw/v1/sec/conversations/:id/handoff`
### 1.5 — Frontend: HandoffButton component (WhatsAppInbox.tsx)
- 🔲 Criar componente `HandoffButton` — botão flutuante posicionado `absolute bottom-[72px] right-4 z-20`
- 🔲 Estado local `handoffMode: 'ia' | 'humano'` por chat — carrega ao abrir o chat
- 🔲 Visual quando modo `ia`: ícone `Bot` verde com tooltip "IA respondendo — clique para assumir"
- 🔲 Visual quando modo `humano`: ícone `UserCheck` laranja pulsando com tooltip "Você está respondendo — clique para devolver à IA" + contador regressivo de 15 min
- 🔲 Ao clicar: chama `NwService.setHandoffMode`, atualiza estado local otimisticamente
- 🔲 Mostrar o botão apenas se sessão CONNECTED e chat selecionado
- 🔲 Esconder o botão se auto-reply mode = `off` (secretária desligada globalmente)
### 1.6 — Frontend: exibição visual no chat
- 🔲 Injetar mensagem de sistema no fluxo de mensagens ao mudar de modo: "🤖 IA assumiu o atendimento" / "👤 Atendente humano assumiu"
- 🔲 Header do chat mostrar badge discreto: `IA` (verde) ou `Humano` (laranja)
### 1.7 — Frontend: retomada automática via WebSocket
- 🔲 Escutar evento WS `conversation.handoff` → atualizar `handoffMode` local sem recarregar a página
- 🔲 Exibir toast "Secretária retomou o atendimento automaticamente (15 min sem resposta)"
### 1.8 — Motor: emitir evento WS ao retomar
- 🔲 Após job BullMQ retomar modo `ia`, emitir via WS `{ event: 'conversation.handoff', data: { chatId, mode: 'ia', reason: 'timeout' } }` para o satélite correto
### 1.9 — Webhook receiver: respeitar handoff
- 🔲 Em `webhook-receiver.js`, antes de chamar `/secretaria/ask`, consultar modo da conversa
- 🔲 Se modo `humano` → não chamar IA, apenas registrar mensagem recebida
- 🔲 Resetar timer de 15 min ao detectar mensagem do atendente humano (mensagem `fromMe: true`)
---
## BLOCO 2 — Injeção de Contexto Local (Opção 3 Híbrida)
> Hoje a secretária fica cega ao DB do projeto. O plugin precisa enriquecer
> cada chamada ao motor com dados reais do cliente.
### 2.1 — Motor: ampliar brain.chat() para aceitar contexto
- 🔲 Modificar `brain.ts``chat(convId, message, opts?: { contextData?: object; systemExtra?: string })`
- 🔲 Serializar `contextData` como bloco de texto e appendar ao system prompt antes da chamada à IA
- 🔲 Modificar `POST /ext/v1/secretaria/ask` → extrair `context` e `system_prompt_extra` do body e repassar ao brain
### 2.2 — Plugin: criar context-builder.js
- 🔲 Criar `sistema-nuvem/plugins/newwhats/context-builder.js`
- 🔲 Implementar `buildContext(phone)`:
- Buscar cliente pelo telefone no DB alemão (tabela `users` ou `clients`)
- Buscar status de assinatura do clube
- Buscar último pedido / histórico recente (últimos 3)
- Buscar promoções ativas do dia
- Buscar vagas abertas (caso relevante)
- 🔲 Retornar objeto `{ cliente, plano, ultimos_pedidos, promocoes_ativas }`
### 2.3 — Plugin: webhook-receiver usa context-builder
- 🔲 Modificar `webhook-receiver.js`: extrair número do remetente de `data.from`
- 🔲 Chamar `context-builder.js` com o número antes de chamar `/secretaria/ask`
- 🔲 Enviar `context` e `system_prompt_extra` no payload do `ask`
---
## BLOCO 3 — Tool Calling (IA que age, não só fala)
> Sem tools, a IA só gera texto. Com tools ela pode agendar, buscar e escalar.
### 3.1 — Motor: suporte a tools no ProtocolEngine
- ✅ Criado `plugins/secretaria/tools.ts` com interface `ToolDef { name, description, parameters, execute() }`
- ✅ Tools implementadas: `listar_horarios`, `agendar_horario`, `escalar_humano`, `encerrar_protocolo`
-`brain.chat()` aceita `tools?: string[]` — resolve via `resolveTools()` e habilita tool calling
- ✅ Loop de tool calling por provider: OpenAI (tool_calls), Anthropic (tool_use), Gemini (functionCall) — máx 5 iterações
- ✅ Fallback gracioso: se nenhum provider com tools disponível, usa modo texto normal
-`POST /ext/v1/secretaria/ask` aceita `tools: string[]` e repassa ao brain com hooks + tenantId
### 3.2 — Plugin: integração com tools do motor
-`webhook-receiver.js` inclui `tools: ['listar_horarios', 'agendar_horario', 'escalar_humano', 'encerrar_protocolo']` no payload do ask
-`escalar_humano` aciona handoff_mode=humano + emite `ext:handoff` via HookBus → chega ao frontend via WS
- `buscar_cliente` / `verificar_plano` resolvidos via `context-builder.js` (Bloco 2) — contexto pré-computado injetado no system prompt, não como tool call (mais simples e confiável)
---
## BLOCO 4 — Painel Admin: Melhorias
### 4.1 — Visualização de conversa
- ✅ Sub-tab Conversas: clicar em protocolo abre drawer lateral com todas as mensagens
- ✅ Botão "Finalizar protocolo" no drawer → `POST /sec/conversations/:id/finalize`
- ✅ Badge visual em conversas com `handoff_mode = humano` (ícone laranja)
- ✅ Badge visual em conversas escaladas (`status = escalated`)
### 4.2 — Chat de teste inline
- ✅ Sub-tab "Testar" no painel Secretária: campo de texto + botão enviar
- ✅ Chama `/secretaria/ask` com um chatId de teste fictício e exibe resposta
- ✅ Útil para testar prompts sem precisar de celular conectado
### 4.3 — Segurança das API Keys
-`GET /ext/v1/sec/keys` mascarar valores: `sk-...****` (mostrar só prefixo + últimos 4 chars)
- ✅ Campo de senha no painel com placeholder `••••••••` quando já salvo
---
## BLOCO 5 — Escalação Real
### 5.1 — Detecção e notificação
- ✅ Motor: `escalar_humano` tool emite `ext:escalated` com protocolNumber + motivo
- ✅ Motor: ws-bridge encaminha `ext:escalated``conversation.escalated` ao satélite
- ✅ Motor: listener em `buildExtRoutes` envia WA ao `admin_notify_phone` configurado
- ✅ Motor: `GET/PUT /sec/keys` inclui `admin_notify_phone` (em claro, não mascarado)
- ✅ Painel: toast laranja `⚠️ Escalação` ao receber `conversation.escalated` via WS
- ✅ Painel: badge "Escalada" vermelho nas conversas (implementado no Bloco 4)
- ✅ Painel: campo "WhatsApp do administrador" na aba Config
---
## RESUMO PRIORIZADO
| # | Bloco | Tarefa | Impacto | Esforço |
|---|---|---|---|---|
| 1 | Handoff | Migration: `handoff_mode` + `handoff_human_at` | 🔴 Crítico | Trivial |
| 2 | Handoff | `PATCH /sec/conversations/:id/handoff` + skip no ask | 🔴 Crítico | Pequeno |
| 3 | Handoff | `HandoffButton` React component | 🔴 Crítico | Pequeno |
| 4 | Handoff | Timer 15 min via BullMQ + retomada automática | 🔴 Crítico | Médio |
| 5 | Handoff | Evento WS `conversation.handoff` | 🟠 Alto | Pequeno |
| 6 | Handoff | Webhook-receiver respeitar modo humano | 🟠 Alto | Pequeno |
| 7 | Contexto | `context-builder.js` no plugin | 🔴 Crítico | Médio |
| 8 | Contexto | Motor aceitar `context` no brain.chat() | 🔴 Crítico | Pequeno |
| 9 | Contexto | Webhook receiver enviar contexto no ask | 🟠 Alto | Pequeno |
| 10 | Painel | Visualizar mensagens do protocolo + finalizar | 🟡 Médio | Pequeno |
| 11 | Painel | Mascarar API keys | 🟡 Médio | Trivial |
| 12 | Tools | Tool calling no ProtocolEngine | 🟠 Alto | Grande |
| 13 | Tools | Tools locais no plugin (buscar_cliente, etc.) | 🟠 Alto | Médio |
| 14 | Escalação | Notificação real ao admin + handoff automático | 🟡 Médio | Médio |
| 15 | Painel | Chat de teste inline | 🟢 Baixo | Pequeno |
---
## FLUXO ALVO COMPLETO
```
Mensagem recebida no WA
webhook-receiver.js
├─ extrai phone do remetente
├─ context-builder.js → { cliente, plano, pedidos, promoções }
├─ verifica handoff_mode da conversa
│ ├─ modo "humano" → não chama IA, reseta timer 15min, fim
│ └─ modo "ia" → continua
└─ POST /secretaria/ask { chatId, message, context, tools: [...] }
Motor ProtocolEngine.chat()
├─ monta system prompt = persona + knowledge + rules + calendar + context_local
├─ chama IA com tool definitions
│ ├─ IA chama tool → executa → retorna resultado → IA gera resposta
│ └─ IA responde diretamente
└─ retorna { reply, conversationId, protocolNumber }
webhook-receiver.js envia reply via WA ao cliente
Humano clica HandoffButton
├─ PATCH /sec/conversations/:id/handoff { mode: "humano" }
├─ BullMQ enfileira job com delay 15min
├─ UI exibe badge laranja + contador regressivo
└─ próximas mensagens WA → webhook ignora IA
15 min sem resposta humana (ou WS cai)
├─ BullMQ job dispara → mode = "ia"
├─ Motor emite WS conversation.handoff
└─ UI atualiza badge → toast "Secretária retomou"
```