Files

163 lines
5.7 KiB
JavaScript

'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 }