12 KiB
12 KiB
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_modeENUM(ia,humano,pausado) DEFAULTiana tabelasec_conversations(migration Knex) - 🔲 Adicionar coluna
handoff_human_atTIMESTAMP NULL na tabelasec_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' }— atualizahandoff_modeehandoff_human_at - 🔲 Em
POST /ext/v1/secretaria/ask— verificarhandoff_modeantes de chamar brain: sehumano, retornar{ skipped: true }sem chamar IA
1.3 — Motor: timer de retomada automática (15 min)
- 🔲 Criar job BullMQ
handoff-timeoutno motor: ao mudar para modohumano, enfileirar job com delay de 15 min - 🔲 Job verifica se
handoff_modeainda éhumanoEhandoff_human_athá mais de 15 min → muda paraia→ emite evento WebSocketconversation.handoffpara 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 modohumanoparaiaautomaticamente
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 posicionadoabsolute bottom-[72px] right-4 z-20 - 🔲 Estado local
handoffMode: 'ia' | 'humano'por chat — carrega ao abrir o chat - 🔲 Visual quando modo
ia: íconeBotverde com tooltip "IA respondendo — clique para assumir" - 🔲 Visual quando modo
humano: íconeUserChecklaranja 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) ouHumano(laranja)
1.7 — Frontend: retomada automática via WebSocket
- 🔲 Escutar evento WS
conversation.handoff→ atualizarhandoffModelocal 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
contextDatacomo bloco de texto e appendar ao system prompt antes da chamada à IA - 🔲 Modificar
POST /ext/v1/secretaria/ask→ extraircontextesystem_prompt_extrado 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
usersouclients) - 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)
- Buscar cliente pelo telefone no DB alemão (tabela
- 🔲 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 dedata.from - 🔲 Chamar
context-builder.jscom o número antes de chamar/secretaria/ask - 🔲 Enviar
contextesystem_prompt_extrano payload doask
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.tscom interfaceToolDef { name, description, parameters, execute() } - ✅ Tools implementadas:
listar_horarios,agendar_horario,escalar_humano,encerrar_protocolo - ✅
brain.chat()aceitatools?: string[]— resolve viaresolveTools()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/askaceitatools: string[]e repassa ao brain com hooks + tenantId
3.2 — Plugin: integração com tools do motor
- ✅
webhook-receiver.jsincluitools: ['listar_horarios', 'agendar_horario', 'escalar_humano', 'encerrar_protocolo']no payload do ask - ✅
escalar_humanoaciona handoff_mode=humano + emiteext:handoffvia HookBus → chega ao frontend via WS - ℹ️
buscar_cliente/verificar_planoresolvidos viacontext-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/askcom 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/keysmascarar 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_humanotool emiteext:escalatedcom protocolNumber + motivo - ✅ Motor: ws-bridge encaminha
ext:escalated→conversation.escalatedao satélite - ✅ Motor: listener em
buildExtRoutesenvia WA aoadmin_notify_phoneconfigurado - ✅ Motor:
GET/PUT /sec/keysincluiadmin_notify_phone(em claro, não mascarado) - ✅ Painel: toast laranja
⚠️ Escalaçãoao receberconversation.escalatedvia 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"