124 lines
4.4 KiB
JavaScript
124 lines
4.4 KiB
JavaScript
'use strict'
|
||
|
||
/**
|
||
* reativacaoClientes.js — WPP-09
|
||
*
|
||
* Job que roda periodicamente (padrão: 1× por dia às 10h)
|
||
* para re-engajar clientes que não fazem pedidos há N dias.
|
||
*
|
||
* Fluxo:
|
||
* 1. Lista clientes inativos (sem pedido nos últimos 60 dias)
|
||
* 2. Para cada um, envia mensagem via NewWhats
|
||
* 3. Registra envio na tabela wpp_reativacao_log
|
||
*
|
||
* Ativação: chamado em server.js via setInterval ou node-cron
|
||
*/
|
||
|
||
const { query, execute, queryOne } = require('../database/postgres')
|
||
|
||
const DIAS_INATIVO = 60 // dias sem pedido para considerar inativo
|
||
const BATCH_SIZE = 20 // máx clientes por execução
|
||
const INTERVALO_MS = 24 * 60 * 60 * 1000 // 24h
|
||
const JANELA_REATIVACAO = 30 // dias entre reativações do mesmo cliente
|
||
|
||
async function getConfig() {
|
||
try {
|
||
const rows = await query(`SELECT key, value FROM plugin_configs WHERE plugin_id = 'newwhats'`)
|
||
const cfg = {}
|
||
for (const r of rows) cfg[r.key] = r.value
|
||
return cfg
|
||
} catch { return {} }
|
||
}
|
||
|
||
async function enviarWhatsApp(motorUrl, integKey, phone, mensagem) {
|
||
if (!motorUrl || !integKey) return false
|
||
try {
|
||
const res = await fetch(`${motorUrl}/api/ext/v1/inbox/55${phone}/send`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json', 'x-nw-key': integKey },
|
||
body: JSON.stringify({ text: mensagem }),
|
||
})
|
||
return res.ok
|
||
} catch { return false }
|
||
}
|
||
|
||
async function runReativacao() {
|
||
console.log('[reativacao] Iniciando job de reativação de clientes...')
|
||
|
||
const cfg = await getConfig()
|
||
if (!cfg.newwhats_url || !cfg.integration_key) {
|
||
console.warn('[reativacao] Motor não configurado — job pulado')
|
||
return
|
||
}
|
||
|
||
if (cfg.auto_reply_mode === 'off') {
|
||
console.log('[reativacao] auto_reply_mode=off — job pulado')
|
||
return
|
||
}
|
||
|
||
try {
|
||
// Lista clientes inativos que ainda não foram reativados recentemente
|
||
const clientes = await query(
|
||
`SELECT c.id AS cliente_id, c.nome, ct.numero AS telefone,
|
||
MAX(p.created_at) AS ultimo_pedido
|
||
FROM clientes c
|
||
JOIN cliente_telefones ct ON ct.cliente_id = c.id AND ct.aceita_whatsapp = true
|
||
LEFT JOIN pedidos p ON p.cliente_id = c.id
|
||
WHERE c.status = 'ativo'
|
||
AND c.id NOT IN (
|
||
SELECT cliente_id FROM wpp_reativacao_log
|
||
WHERE enviada_em > NOW() - ($1 || ' days')::INTERVAL
|
||
AND cliente_id IS NOT NULL
|
||
)
|
||
GROUP BY c.id, c.nome, ct.numero
|
||
HAVING MAX(p.created_at) < NOW() - ($2 || ' days')::INTERVAL
|
||
OR MAX(p.created_at) IS NULL
|
||
ORDER BY ultimo_pedido ASC NULLS FIRST
|
||
LIMIT $3`,
|
||
[JANELA_REATIVACAO, DIAS_INATIVO, BATCH_SIZE]
|
||
)
|
||
|
||
console.log(`[reativacao] ${clientes.length} clientes inativos encontrados`)
|
||
|
||
for (const cl of clientes) {
|
||
const nome = cl.nome?.split(' ')[0] || 'Cliente'
|
||
const telefone = cl.telefone.replace(/\D/g, '')
|
||
const mensagem = `Olá, *${nome}*! 👋\nFaz um tempo que não vemos você por aqui no *Alemão Conveniências*.\n\nTemos novidades e promoções esperando por você! Bora fazer um pedido? 😊\n\nDigite *OI* para ver nossas ofertas do dia.`
|
||
|
||
const enviado = await enviarWhatsApp(cfg.newwhats_url, cfg.integration_key, telefone, mensagem)
|
||
if (enviado) {
|
||
await execute(
|
||
`INSERT INTO wpp_reativacao_log (cliente_id, chat_id, mensagem) VALUES ($1,$2,$3)`,
|
||
[cl.cliente_id, `55${telefone}@s.whatsapp.net`, mensagem]
|
||
)
|
||
console.log(`[reativacao] ✅ ${nome} (${telefone})`)
|
||
} else {
|
||
console.warn(`[reativacao] ⚠ Falha ao enviar para ${telefone}`)
|
||
}
|
||
|
||
// Pausa entre envios para não sobrecarregar
|
||
await new Promise(r => setTimeout(r, 2000))
|
||
}
|
||
|
||
console.log('[reativacao] Job concluído')
|
||
} catch (err) {
|
||
console.error('[reativacao] Erro inesperado:', err.message)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Inicia o job com agendamento automático.
|
||
* @param {number} intervalMs — intervalo em ms (padrão 24h)
|
||
* @param {number} initialDelayMs — atraso inicial antes do primeiro disparo (padrão 2min)
|
||
*/
|
||
function startReativacaoJob(intervalMs = INTERVALO_MS, initialDelayMs = 2 * 60 * 1000) {
|
||
console.log(`[reativacao] Job agendado — intervalo: ${Math.round(intervalMs / 3600000)}h, delay inicial: ${initialDelayMs / 1000}s`)
|
||
|
||
setTimeout(() => {
|
||
runReativacao()
|
||
setInterval(runReativacao, intervalMs)
|
||
}, initialDelayMs)
|
||
}
|
||
|
||
module.exports = { startReativacaoJob, runReativacao }
|