'use strict' /** * Monitor de mensagens sem resposta no WhatsApp * * Detecta chats com mensagens recebidas há mais de LIMIAR_MINUTOS * que não receberam resposta da IA (status='sent') nem estão em handoff humano. * * A cada ciclo: * - Busca chats pendentes * - Cria mensagem interna no setor 'atendimento' (uma por chat, sem duplicar em 30min) * - Emite evento SSE 'wpp.sem_resposta' para o admin */ const { query, queryOne, execute } = require('../database/postgres') const LIMIAR_MINUTOS = 5 // tempo sem resposta para considerar pendente const INTERVALO_MS = 5 * 60 * 1000 // roda a cada 5 minutos const DEDUP_MINUTOS = 30 // não gera alerta duplicado no mesmo chat em 30min let timer = null async function checarMsgsSemResposta() { try { // Chats com mensagem recebida há mais de LIMIAR_MINUTOS sem reply enviado // e que não estão em handoff humano ativo const pendentes = await query(` SELECT DISTINCT ON (el.chat_id) el.chat_id, el.created_at AS ultima_msg_em, el.payload->>'body' AS ultima_msg, el.payload->>'pushName' AS push_name, el.payload->>'author' AS author, EXTRACT(EPOCH FROM (NOW() - el.created_at)) / 60 AS minutos_aguardando FROM nw_event_logs el WHERE el.direction = 'in' AND el.event = 'message.new' AND el.created_at > NOW() - INTERVAL '2 hours' AND el.created_at < NOW() - ($1 || ' minutes')::INTERVAL -- sem resposta da IA depois desta mensagem AND NOT EXISTS ( SELECT 1 FROM nw_auto_replies ar WHERE ar.chat_id = el.chat_id AND ar.status = 'sent' AND ar.created_at >= el.created_at ) -- não está em handoff humano ativo AND NOT EXISTS ( SELECT 1 FROM nw_handoff h WHERE h.chat_id = el.chat_id AND h.human_until > NOW() ) -- sem Human API pendente para este chat (operador já está sendo acionado) AND NOT EXISTS ( SELECT 1 FROM human_api_requests har WHERE har.chat_id = el.chat_id AND har.status = 'pending' ) ORDER BY el.chat_id, el.created_at DESC `, [LIMIAR_MINUTOS]) if (!pendentes.length) return for (const p of pendentes) { // Dedup por chat_id — não repete alerta para o mesmo chat em DEDUP_MINUTOS const jaAlertado = await queryOne(` SELECT id FROM mensagens_internas WHERE setor = 'atendimento' AND remetente = 'Monitor IA' AND chat_id = $1 AND created_at > NOW() - ($2 || ' minutes')::INTERVAL LIMIT 1 `, [p.chat_id, DEDUP_MINUTOS]) if (jaAlertado) continue const mins = Math.round(Number(p.minutos_aguardando)) const preview = (p.ultima_msg || '').slice(0, 80) // Monta identificador legível: nome > número formatado > UUID curto let clienteId = p.push_name?.trim() || '' if (!clienteId && p.author) { const num = p.author.replace('@s.whatsapp.net', '').replace('@c.us', '') if (/^\d+$/.test(num)) { clienteId = `+${num.replace(/^55(\d{2})(\d{4,5})(\d{4})$/, '55 ($1) $2-$3')}` } } if (!clienteId) clienteId = p.chat_id.slice(0, 8) + '…' await execute(` INSERT INTO mensagens_internas (tenant_id, setor, remetente, chat_id, mensagem) VALUES (1, 'atendimento', 'Monitor IA', $1, $2) `, [ p.chat_id, `⚠️ Sem resposta há ${mins} min — ${clienteId}\n"${preview}"`, ]) console.log(`[monitor-wpp] ⚠️ ${clienteId} (${p.chat_id}) sem resposta há ${mins}min`) } } catch (err) { console.error('[monitor-wpp] Erro:', err.message) } } /** * Retorna os chats atualmente pendentes (usado pelo endpoint de admin). */ async function getPendentes(limitMinutos = LIMIAR_MINUTOS) { return query(` SELECT DISTINCT ON (el.chat_id) el.chat_id, el.created_at AS ultima_msg_em, el.payload->>'body' AS ultima_msg, el.payload->>'pushName' AS push_name, el.payload->>'author' AS author, ROUND(EXTRACT(EPOCH FROM (NOW() - el.created_at)) / 60) AS minutos_aguardando, (SELECT ar2.reply FROM nw_auto_replies ar2 WHERE ar2.chat_id = el.chat_id ORDER BY ar2.created_at DESC LIMIT 1 ) AS ultima_reply_ia, (SELECT ar2.status FROM nw_auto_replies ar2 WHERE ar2.chat_id = el.chat_id ORDER BY ar2.created_at DESC LIMIT 1 ) AS ultima_reply_status FROM nw_event_logs el WHERE el.direction = 'in' AND el.event = 'message.new' AND el.created_at > NOW() - INTERVAL '2 hours' AND el.created_at < NOW() - ($1 || ' minutes')::INTERVAL AND NOT EXISTS ( SELECT 1 FROM nw_auto_replies ar WHERE ar.chat_id = el.chat_id AND ar.status = 'sent' AND ar.created_at >= el.created_at ) AND NOT EXISTS ( SELECT 1 FROM nw_handoff h WHERE h.chat_id = el.chat_id AND h.human_until > NOW() ) AND NOT EXISTS ( SELECT 1 FROM human_api_requests har WHERE har.chat_id = el.chat_id AND har.status = 'pending' ) ORDER BY el.chat_id, el.created_at DESC `, [limitMinutos]) } function startMonitorJob() { console.log(`[monitor-wpp] Job iniciado — verifica a cada ${INTERVALO_MS / 60000}min`) checarMsgsSemResposta() timer = setInterval(checarMsgsSemResposta, INTERVALO_MS) } function stopMonitorJob() { if (timer) { clearInterval(timer); timer = null } } module.exports = { startMonitorJob, stopMonitorJob, getPendentes }