Files
App-Estoque-LiberiKids/scripts/enviar-alertas-parcelas.js
2025-11-29 21:31:52 -03:00

327 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* Script de Envio Automático de Alertas de Vencimento
*
* Este script deve ser executado diariamente às 09:00 (horário de Brasília)
* via cron job para enviar alertas de parcelas vencendo.
*
* Cron: 0 9 * * * /usr/bin/node /caminho/para/enviar-alertas-parcelas.js
*/
const { createClient } = require('@supabase/supabase-js');
const axios = require('axios');
// Configurações do Supabase
const SUPABASE_URL = process.env.SUPABASE_URL || 'https://ydhzylfnpqlxnzfcclla.supabase.co';
const SUPABASE_KEY = process.env.SUPABASE_SERVICE_KEY || process.env.SUPABASE_ANON_KEY;
if (!SUPABASE_KEY) {
console.error('❌ SUPABASE_KEY não configurado!');
process.exit(1);
}
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY);
// Função para formatar data no padrão brasileiro
function formatarDataBR(data) {
const d = new Date(data);
return d.toLocaleDateString('pt-BR', { timeZone: 'America/Sao_Paulo' });
}
// Função para formatar moeda
function formatarMoeda(valor) {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(valor);
}
// Função para calcular dias entre datas
function calcularDiasEntre(data1, data2) {
const umDia = 24 * 60 * 60 * 1000;
const d1 = new Date(data1);
const d2 = new Date(data2);
return Math.round((d2 - d1) / umDia);
}
// Função para enviar mensagem via Evolution API
async function enviarWhatsApp(telefone, mensagem, evolutionConfig) {
try {
const url = `${evolutionConfig.apiUrl}/message/sendText/${evolutionConfig.instanceName}`;
const response = await axios.post(url, {
number: telefone,
textMessage: {
text: mensagem
}
}, {
headers: {
'apikey': evolutionConfig.apiKey,
'Content-Type': 'application/json'
}
});
return { success: true, data: response.data };
} catch (error) {
console.error(`❌ Erro ao enviar WhatsApp para ${telefone}:`, error.message);
return { success: false, error: error.message };
}
}
// Função para gerar PIX via Mercado Pago
async function gerarPix(parcela, mercadoPagoToken) {
try {
const response = await axios.post('https://api.mercadopago.com/v1/payments', {
transaction_amount: parseFloat(parcela.valor),
description: `Parcela ${parcela.numero_parcela} - Venda #${parcela.venda_id}`,
payment_method_id: 'pix',
payer: {
email: 'cliente@liberi.com.br',
identification: {
type: 'CPF',
number: '00000000000'
}
}
}, {
headers: {
'Authorization': `Bearer ${mercadoPagoToken}`,
'Content-Type': 'application/json'
}
});
return {
success: true,
qrCode: response.data.point_of_interaction?.transaction_data?.qr_code,
qrCodeBase64: response.data.point_of_interaction?.transaction_data?.qr_code_base64,
paymentId: response.data.id
};
} catch (error) {
console.error(`❌ Erro ao gerar PIX para parcela ${parcela.id}:`, error.message);
return { success: false, error: error.message };
}
}
// Função principal
async function enviarAlertasVencimento() {
console.log('\n🕐 Iniciando envio de alertas de vencimento...');
console.log(`⏰ Horário: ${new Date().toLocaleString('pt-BR', { timeZone: 'America/Sao_Paulo' })}\n`);
try {
// 1. Buscar configurações WhatsApp
const { data: configData } = await supabase
.from('configuracoes')
.select('chave, valor')
.in('chave', [
'whatsapp_primeiro_alerta_dias',
'whatsapp_segundo_alerta_dias',
'whatsapp_alerta_apos_vencimento_dias',
'whatsapp_primeiro_alerta_ativo',
'whatsapp_segundo_alerta_ativo',
'whatsapp_alerta_apos_vencimento_ativo',
'whatsapp_primeiro_alerta_mensagem',
'whatsapp_segundo_alerta_mensagem',
'whatsapp_alerta_apos_vencimento_mensagem',
'evolution_api_url',
'evolution_instance_name',
'evolution_api_key',
'mercadopago_access_token'
]);
const config = {};
configData?.forEach(item => {
config[item.chave] = item.valor;
});
// Configurações padrão
const primeiroAlertaDias = parseInt(config.whatsapp_primeiro_alerta_dias) || 3;
const segundoAlertaDias = parseInt(config.whatsapp_segundo_alerta_dias) || 0;
const alertaAposVencimentoDias = parseInt(config.whatsapp_alerta_apos_vencimento_dias) || 3;
const primeiroAlertas = config.whatsapp_primeiro_alerta_ativo === 'true';
const segundoAlertas = config.whatsapp_segundo_alerta_ativo === 'true';
const alertaAposVencimento = config.whatsapp_alerta_apos_vencimento_ativo === 'true';
const evolutionConfig = {
apiUrl: config.evolution_api_url,
instanceName: config.evolution_instance_name,
apiKey: config.evolution_api_key
};
const mercadoPagoToken = config.mercadopago_access_token;
console.log('📋 Configurações carregadas:');
console.log(` - Primeiro alerta: ${primeiroAlertaDias} dias antes (${primeiroAlertas ? 'ATIVO' : 'INATIVO'})`);
console.log(` - Segundo alerta: ${segundoAlertaDias} dias antes (${segundoAlertas ? 'ATIVO' : 'INATIVO'})`);
console.log(` - Alerta pós-vencimento: ${alertaAposVencimentoDias} dias após (${alertaAposVencimento ? 'ATIVO' : 'INATIVO'})\n`);
// 2. Buscar parcelas pendentes
const { data: parcelas, error } = await supabase
.from('venda_parcelas')
.select(`
*,
vendas (
id_venda,
cliente_id,
clientes (
nome_completo,
whatsapp
)
)
`)
.eq('status', 'pendente')
.order('data_vencimento', { ascending: true });
if (error) {
throw error;
}
if (!parcelas || parcelas.length === 0) {
console.log(' Nenhuma parcela pendente encontrada.\n');
return;
}
console.log(`📦 ${parcelas.length} parcela(s) pendente(s) encontrada(s)\n`);
const hoje = new Date();
hoje.setHours(0, 0, 0, 0);
let alertasEnviados = 0;
let erros = 0;
// 3. Processar cada parcela
for (const parcela of parcelas) {
const dataVencimento = new Date(parcela.data_vencimento);
dataVencimento.setHours(0, 0, 0, 0);
const diasParaVencimento = calcularDiasEntre(hoje, dataVencimento);
const cliente = parcela.vendas?.clientes;
if (!cliente || !cliente.whatsapp) {
console.log(`⚠️ Parcela ${parcela.numero_parcela} (Venda #${parcela.vendas?.id_venda}): Cliente sem WhatsApp`);
continue;
}
let deveEnviar = false;
let tipoAlerta = '';
let mensagemTemplate = '';
// Verificar qual tipo de alerta enviar
if (diasParaVencimento === primeiroAlertaDias && primeiroAlertas) {
deveEnviar = true;
tipoAlerta = 'primeiro_alerta';
mensagemTemplate = config.whatsapp_primeiro_alerta_mensagem ||
'Olá {cliente}! 👋\n\nLembramos que você tem uma parcela no valor de {valor} com vencimento {quando}.\n\nAgradecemos a atenção!';
} else if (diasParaVencimento === segundoAlertaDias && segundoAlertas) {
deveEnviar = true;
tipoAlerta = 'segundo_alerta';
mensagemTemplate = config.whatsapp_segundo_alerta_mensagem ||
'Olá {cliente}! 👋\n\nSua parcela de {valor} vence {quando}.\n\n📱 Gerando PIX para facilitar o pagamento...';
} else if (diasParaVencimento < 0 && Math.abs(diasParaVencimento) === alertaAposVencimentoDias && alertaAposVencimento) {
deveEnviar = true;
tipoAlerta = 'alerta_apos_vencimento';
mensagemTemplate = config.whatsapp_alerta_apos_vencimento_mensagem ||
'Olá {cliente}! 👋\n\nIdentificamos que a parcela {parcela} no valor de {valor} venceu {quando}.\n\nPor favor, regularize o pagamento.';
}
if (!deveEnviar) {
continue;
}
// Preparar dados da mensagem
const nomeCliente = cliente.nome_completo.split(' ')[0];
const valorFormatado = formatarMoeda(parcela.valor);
const dataVencFormatada = formatarDataBR(parcela.data_vencimento);
let quando = '';
if (diasParaVencimento > 0) {
quando = diasParaVencimento === 1 ? 'amanhã' : `em ${diasParaVencimento} dias (${dataVencFormatada})`;
} else if (diasParaVencimento === 0) {
quando = 'hoje';
} else {
quando = `${Math.abs(diasParaVencimento)} dias (${dataVencFormatada})`;
}
// Substituir variáveis na mensagem
let mensagem = mensagemTemplate
.replace(/{cliente}/g, nomeCliente)
.replace(/{valor}/g, valorFormatado)
.replace(/{quando}/g, quando)
.replace(/{parcela}/g, `${parcela.numero_parcela}/${parcela.vendas?.parcelas || '?'}`);
// Se for no dia do vencimento, gerar PIX
if (tipoAlerta === 'segundo_alerta' && mercadoPagoToken) {
console.log(`🔄 Gerando PIX para parcela ${parcela.numero_parcela}...`);
const pixResult = await gerarPix(parcela, mercadoPagoToken);
if (pixResult.success && pixResult.qrCode) {
mensagem += `\n\n📱 *PIX Copia e Cola:*\n\`\`\`${pixResult.qrCode}\`\`\``;
// Atualizar parcela com dados do PIX
await supabase
.from('venda_parcelas')
.update({
pix_payment_id: pixResult.paymentId,
pix_qr_code: pixResult.qrCode,
pix_qr_code_base64: pixResult.qrCodeBase64
})
.eq('id', parcela.id);
}
}
// Enviar mensagem
console.log(`📤 Enviando ${tipoAlerta} para ${cliente.nome_completo} (${cliente.whatsapp})...`);
const resultado = await enviarWhatsApp(cliente.whatsapp, mensagem, evolutionConfig);
if (resultado.success) {
alertasEnviados++;
console.log(` ✅ Enviado com sucesso!\n`);
// Registrar no histórico
await supabase
.from('mensagens_whatsapp')
.insert({
telefone_cliente: cliente.whatsapp,
cliente_nome: cliente.nome_completo,
mensagem: mensagem,
tipo: 'enviada',
status: 'enviada'
});
} else {
erros++;
console.log(` ❌ Falha no envio: ${resultado.error}\n`);
}
// Pequeno delay entre mensagens
await new Promise(resolve => setTimeout(resolve, 2000));
}
// Resumo
console.log('\n' + '='.repeat(50));
console.log('📊 RESUMO DO ENVIO');
console.log('='.repeat(50));
console.log(`✅ Alertas enviados: ${alertasEnviados}`);
console.log(`❌ Erros: ${erros}`);
console.log(`📦 Total de parcelas verificadas: ${parcelas.length}`);
console.log('='.repeat(50) + '\n');
} catch (error) {
console.error('\n💥 Erro fatal:', error);
process.exit(1);
}
}
// Executar
enviarAlertasVencimento()
.then(() => {
console.log('✅ Script finalizado com sucesso!');
process.exit(0);
})
.catch((error) => {
console.error('💥 Erro fatal:', error);
process.exit(1);
});