# 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" ```