14 KiB
14 KiB
Tarefas — Alemão Conveniências Admin
Última atualização: 2026-04-26
Legenda
[ ]Pendente[~]Em andamento[x]Concluído
✅ Concluído
Correções de Auth
- Corrigir 401 em
/api/admin/nw/auto-reply-modepara funcionários (verifyAnyToken) - Corrigir 401 nas rotas
/api/admin/inbox/*para funcionários - Login de funcionário (secretaria Monique) — senha redefinida pelo painel admin
WhatsApp Inbox
- Nome do colaborador em negrito no início de cada mensagem WhatsApp enviada
- Pausa automática da IA ao enviar mensagem (handoff dispara sem clicar no botão)
- Botão de handoff fica laranja imediatamente após envio
- Timer do handoff corrigido para 15 minutos (estava exibindo 30 por erro de cálculo)
- Handoff salvo no banco local (
nw_handoff) — independente do motor NewWhats - Webhook-receiver verifica handoff local antes de chamar a IA
Inbox Interna
- Cores ajustadas para padrão branco/cinza do WhatsApp
- Layout coluna única (lista ou mensagens, sem duas colunas lado a lado)
Fase 1 — Escalação & Protocolos
- Botão "Assumir" no toast de escalação — cria thread + pausa IA + agenda expiração BullMQ
ref_idpreenchido automaticamente ao assumir (protocolo ↔ Inbox Interna)- Transferência de protocolo entre atendentes — modal com seleção de conta e mensagem de contexto
- Timer de expiração do handoff via BullMQ (15 min) com notificação SSE ao frontend
Fase 2 — IA & Conhecimento
- Brain do setor injetado no contexto da IA (
sector_brain_nodes→systemExtrano motor) - Human API com timeout — IA solicita resposta humana, BullMQ expira em 5 min, SSE notifica
- Endpoint de resposta Human API — colaborador responde pela Inbox Interna, entrega à IA
- Setor identificado pela IA sincronizado de volta ao banco local (
nw_chat_sectors) - SLA por setor — cron a cada minuto, alerta SSE
sla.breachquando ultrapassado
🔴 Alta Prioridade
- Quando human_api.timeout: notificar motor para retomar conversa sem resposta humana
🟢 Relatórios & Métricas de Atendimento
Fase 3 — Concluído
- Contador de atendimentos por colaborador — escalações assumidas, msgs enviadas, Human API respondidas
- Ranking de atendimentos — tabela por colaborador com tempo médio de 1ª resposta
- SLA breach por setor — contagem de escalações fora do prazo por setor
- Dashboard de atendimento — aba "Métricas" com cards resumo + tabelas por colaborador e setor
- Filtro por período — Hoje / 7 dias / 30 dias
- Human API stats — total, respondidas, timeout, % de resposta
Fase 4 — Concluído
- Nota por atendimento — cliente avalia 1–5 após encerramento via WhatsApp; capturado no webhook
- Registro de reclamações / sugestões / elogios — botão Flag no ThreadPanel com nota opcional
- Encerrar protocolo — botão no ThreadPanel envia pesquisa + zera handoff + retoma IA
- Aprendizado de conhecimento — botão "Brain" no ThreadPanel salva resposta; opção de promover para
sector_brain_nodes - Gráfico temporal — barras de escalações por dia no MetricasTab
- Métricas de flags e ratings — cards de satisfação, reclamações, sugestões, elogios no dashboard
🔵 Baixa Prioridade
- Tempo médio de resolução —
AVG(pr.sent_at - esc.first_escalation)no endpoint/api/admin/metrics/attendance; card "Tempo médio de resolução" no MetricasTab - Multi-tenant —
tenant_idadicionado anw_event_logsenw_auto_repliesvia migration; queries de auto-reply filtradas por tenant;protocol_ratings/protocol_flags/internal_messagesjá isoladas - Histórico de avaliações de satisfação — endpoint
/api/admin/metrics/ratings-historycom paginação e filtro por nota; tabela paginada com estrelas, status e atendente no MetricasTab
📝 Notas Técnicas
Infraestrutura
- Motor NewWhats:
http://127.0.0.1:8008— IA, conversas e envio de mensagens - Servidor satélite:
node server.jsem/sistema-nuvem/na porta 4001 - Frontend buildado em
/sistema-nuvem/public/admin-v2/assets/ - Banco: PostgreSQL
alemao, usuárionewwhats, senha em.env→PG_PASSWORD - Redis:
127.0.0.1:6379— usado pelo BullMQ (configurável viaREDIS_HOST/REDIS_PORT)
Jobs BullMQ (Redis)
| Queue | Trigger | Delay | Ação |
|---|---|---|---|
handoff-expire |
PATCH /api/nw/handoff/:chatId com mode=humano ou botão Assumir |
15 min | Zera nw_handoff.human_until; SSE handoff.expired |
human-api-timeout |
POST /api/human-api/request (interno, via webhook-receiver) |
5 min | Marca human_api_requests.status='timeout'; insere msg sistema; SSE human_api.timeout |
Crons (node-cron, server.js)
| Schedule | O que faz |
|---|---|
* * * * * (todo minuto) |
Verifica escalações abertas sem resposta humana que ultrapassaram sectors.sla_minutes; emite SSE sla.breach para todos os clientes conectados |
Eventos SSE (/api/sse)
| Evento | Payload | Quem consome |
|---|---|---|
handoff.expired |
{ chatId } |
WhatsAppInbox → toast + refresh HandoffButton |
human_api.new |
{ requestId, threadId, question, sectorName } |
WhatsAppInbox → toast com botão "Ver Inbox" |
human_api.answered |
{ requestId, threadId, answer } |
— (reservado) |
human_api.timeout |
{ requestId, threadId } |
WhatsAppInbox → informativo |
sla.breach |
{ threadId, sectorId, sectorName, slaMins, elapsedMins } |
WhatsAppInbox → toast de alerta |
Tabelas criadas / alteradas (Fases 1–4)
| Tabela | Propósito |
|---|---|
nw_handoff |
Pausa da IA por chat (human_until TIMESTAMPTZ) |
nw_chat_sectors |
Setor identificado pela IA por chat (chat_id → sector_id) |
human_api_requests |
Pedidos da IA para resposta humana (status, timeout_at, answer) |
internal_messages |
Inbox interna — threads de escalação, colaboração, human_api, ajuda |
sector_brain_nodes |
Base de conhecimento por setor, injetada no system prompt da IA |
protocol_ratings |
Avaliações de satisfação (1–5) enviadas ao cliente ao encerrar protocolo |
protocol_flags |
Flags de protocolo: reclamação, sugestão ou elogio registrado pelo atendente |
knowledge_learning |
Respostas humanas salvas para promoção à base de conhecimento |
Fluxo Human API
- Motor retorna
{ humanApiRequest: { question, sectorName } }em/secretaria/ask webhook-receiver.jschamaPOST /api/human-api/request(interno)- Banco: cria
human_api_requests+ threadinternal_messages(type=human_api) - BullMQ agenda timeout de 5 min; SSE
human_api.newnotifica colaboradores - Colaborador responde pela Inbox Interna →
POST /api/human-api/answer/:id - Banco: atualiza status para
answered; cancela job BullMQ; SSEhuman_api.answered - (próximo passo) Resposta re-injetada na conversa do motor via
/secretaria/ask
🐛 Revisão de Bugs — 2026-04-20
Revisão completa do código encontrou 40 bugs. Organizados abaixo por prioridade.
🔴 Crítico — Segurança Grave (9)
- BUG-06 — Adicionar verificação HMAC no webhook Asaas de rifas (
controllers/webhookController.js:8) — fraude de pagamento - BUG-07 — Adicionar verificação HMAC no webhook Asaas do clube (
controllers/clubController.js:445) — fraude de assinatura - BUG-04 — Trocar JWT secrets fracos no
.envpor valores fortes e aleatórios (.env:13,23) - BUG-01 — Eliminar interpolação direta em queries SQL; usar apenas parâmetros
$N(server.js:863,models/Analytics.js:6,models/ClubMember.js:99) - BUG-02 — Configurar CORS com origens explícitas (
server.js:28) - BUG-03 — Adicionar
secure: trueesameSite: 'lax'em todos os cookies (controllers/adminController.js:37,controllers/usersController.js:12) - BUG-05 — Substituir autenticação do webhook interno: header
x-internal-secretcomINTERNAL_WEBHOOK_SECRETdo env (server.js:373,454) - BUG-08 — Corrigir skip do rate limiter: verifica JWT com
jwt.verify()antes de bypassar (server.js:40) - BUG-09 —
.gitignorecriado emsistema-nuvem/protegendo.env,node_modulese logs
🟠 Alto — Bugs Funcionais (13)
- BUG-10 — Corrigir
JWT_SECRETindefinido emsignTeamToken→ usaTEAM_SECRET(middleware/teamAuth.js:75) - BUG-13 — Corrigir
.rowsduplicado nowebhookController—query()já retorna array (controllers/webhookController.js:31) - BUG-23 — Corrigir campos no
context-builder.js:name/phone→nome/telefone;findActivePlanusaclub_planseuser_cpf(plugins/newwhats/context-builder.js:42) - BUG-30 — Proteger
GET /api/raffle/ticketscomverifyToken(routes/apiRaffle.js:9) - BUG-33 — Substituir
localhost:4001porprocess.env.APP_URL(controllers/webhookController.js) - BUG-21 — Cleanup de
Orderórfã comOrder.delete()seTicket.reservefalhar (controllers/raffleController.js:83) - BUG-11 — Incluir
offsetna query SQL deClubMember.listByClub(models/ClubMember.js:55) - BUG-12 — Adicionar
tituloà query do sorteio emRaffleResult.draw(models/RaffleResult.js:12) - BUG-16 — Adicionar filtro
tenant_idemadminUpdateJob,adminDeleteJob,adminToggleJob(controllers/jobController.js) - BUG-17 — Adicionar filtro
tenant_idemcancelMyApplication(controllers/jobController.js:189) - BUG-18 — Adicionar filtro
tenant_idemadminGetApplicationeadminUpdateApplication(controllers/jobController.js) - BUG-19 — Verificação de tenant em
adminSaveContent(controllers/clubController.js:351) - BUG-20 — Permite reativação de membros
suspended/pending_payment; só bloqueia statusactive(controllers/clubController.js:65)
🟡 Médio — Qualidade e Tratamento de Erros (11)
- BUG-25 —
tenantMiddlewareretorna 503 em vez denext()semreq.tenant(middleware/tenant.js:66) - BUG-29 —
clubAsaasServiceaceitaapiKeypor chamada;clubControllerpassareq.tenant.asaas_key(services/clubAsaasService.js) - BUG-15 — Comparação corrigida:
active === 'true'(controllers/equipeController.js:54) - BUG-22 — Notificação ao ganhador movida para fora da transação com
setImmediateapós COMMIT (models/RaffleResult.js) - BUG-26 —
validateCPFadicionada e aplicada emsubscribeegetCard(controllers/clubController.js) - BUG-27 — Mínimo de senha de admins elevado para 8 caracteres em
createAdmineupdateAdmin - BUG-28 — Verifica
searchRes.okantes de acessarsearchData.data(services/asaasService.js:30) - BUG-31 —
limitmáximo de 1000;offsetmínimo de 0 emnotification-errors(server.js) - BUG-32 —
REDIS_PORTparseado comparseInt(services/whatsappService.js:10) - BUG-24 —
verifyAnyTokenrejeita imediatamente com 401 secloud_tokeninválido (middleware/teamAuth.js) - BUG-35 — Promoção de conhecimento sem
sector_idretorna 400 (server.js)
🟢 Baixo — Melhorias (7)
- BUG-36 — Usar
crypto.timingSafeEqualna comparação de chaves emnwAuth(plugins/newwhats/auth.js:27) - BUG-37 — Adicionar filtro
tenant_idemdeleteTicket(controllers/raffleController.js:211) - BUG-38 — Não assumir opt-in quando usuário com CPF não é encontrado (
services/whatsappService.js:244) - BUG-39 — Substituir interpolação de
promotedpor parâmetro$2(server.js:641) - BUG-40 — Reduzir expiração do cookie de usuário e adicionar revogação de token (
controllers/usersController.js:12) - BUG-14 — Refatorar declaração de
sseHandoffQueuepara antes das rotas que a usam (server.js:271) - BUG-34 — Escapar caracteres especiais de LIKE (
%,_) nocontext-builder.js(plugins/newwhats/context-builder.js:45)
Resumo
| Prioridade | Total |
|---|---|
| 🔴 Crítico | 9 |
| 🟠 Alto | 13 |
| 🟡 Médio | 11 |
| 🟢 Baixo | 7 |
| Total | 40 |
📬 Inbox Interna — v2 (Pendente)
Referência:
inbox-interna.md
Contexto: rota/admin-v2/cotacao/inbox
INB-SSE — SSE real-time para DMs
- INB-SSE-01 Adicionar evento
direct_message.newao endpoint SSE existente (/api/sse) — emitir para o destinatário ao receber nova DM - INB-SSE-02 No
CollaboratorsPanel.tsx, subscrever SSE e atualizar badge de não-lidas em tempo real (substituir o auto-refresh de 30s) - INB-SSE-03 No
DMConversationView.tsx, subscrever SSE e appendar nova mensagem em tempo real quando a conversa está aberta - INB-SSE-04 Testar isolamento: evento SSE deve chegar apenas ao
to_accountcorreto (não broadcast global)
INB-GC — Group Chat por Setor (v2.2)
- INB-GC-01 Criar tabela
group_chats(id,tenant_id,sector_id,name,created_at) - INB-GC-02 Membros via
team_membersexistente — sem tabela extra necessária - INB-GC-03 Criar tabela
group_messages(id,tenant_id,group_chat_id,from_account,content,created_at) - INB-GC-04 Endpoints:
GET /groups,GET /groups/:id/messages,POST /groups/:id/message,GET /groups/:id/members - INB-GC-05 Evento SSE
group_message.new— notifica membros do setor (viasseSendpor membro) - INB-GC-06 Suporte a
@menção— detectar@nomeno content e emitir SSEgroup_mentionpara o mencionado - INB-GC-07 Frontend:
GroupChatView.tsx+ tab "Grupos" noInboxInternaTab - INB-GC-08 Seed automático: grupo criado para cada setor ativo no
initialize()
INB-BC — Broadcast (v2.2)
- INB-BC-01 Criar tabela
broadcasts(id,tenant_id,from_account,content,created_at) - INB-BC-02 Endpoint
POST /api/admin/inbox/broadcast— restrito a rolesowneremanager - INB-BC-03 Evento SSE
broadcast.sentviasseBroadcastTenant— notifica todos do tenant - INB-BC-04 Frontend: tab "Avisos" visível para todos; campo de envio apenas para owner/manager com confirmação
- INB-BC-05 Broadcasts exibidos como cards destacados com borda âmbar no
BroadcastPanel