chore: sincroniza projeto para gitea

This commit is contained in:
Tiago
2025-11-29 21:31:52 -03:00
parent 33d8645eb4
commit 7e7a0f8867
129 changed files with 24999 additions and 6757 deletions

View File

@@ -0,0 +1,260 @@
#!/usr/bin/env node
/**
* Script para Enviar Alertas Atrasados
*
* Este script envia manualmente alertas que não foram enviados
* (para quando o cron não estava configurado)
*/
const { createClient } = require('@supabase/supabase-js');
const axios = require('axios');
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
function question(query) {
return new Promise(resolve => rl.question(query, resolve));
}
// Configurações
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);
function formatarDataBR(data) {
const d = new Date(data);
return d.toLocaleDateString('pt-BR', { timeZone: 'America/Sao_Paulo' });
}
function formatarMoeda(valor) {
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: 'BRL'
}).format(valor);
}
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) {
return { success: false, error: error.message };
}
}
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'
}
}, {
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) {
return { success: false, error: error.message };
}
}
async function main() {
console.log('\n🔔 ENVIO MANUAL DE ALERTAS ATRASADOS\n');
console.log('Este script enviará alertas que não foram enviados automaticamente.\n');
try {
// Buscar configurações
const { data: configData } = await supabase
.from('configuracoes')
.select('chave, valor')
.in('chave', [
'evolution_api_url',
'evolution_instance_name',
'evolution_api_key',
'mercadopago_access_token'
]);
const config = {};
configData?.forEach(item => {
config[item.chave] = item.valor;
});
const evolutionConfig = {
apiUrl: config.evolution_api_url,
instanceName: config.evolution_instance_name,
apiKey: config.evolution_api_key
};
const mercadoPagoToken = config.mercadopago_access_token;
// Buscar parcelas pendentes com vencimento hoje ou passado
const hoje = new Date();
hoje.setHours(0, 0, 0, 0);
const { data: parcelas, error } = await supabase
.from('venda_parcelas')
.select(`
*,
vendas (
id_venda,
cliente_id,
clientes (
nome_completo,
whatsapp
)
)
`)
.eq('status', 'pendente')
.lte('data_vencimento', hoje.toISOString())
.order('data_vencimento', { ascending: true });
if (error) throw error;
if (!parcelas || parcelas.length === 0) {
console.log('✅ Não há parcelas vencidas ou vencendo hoje.\n');
rl.close();
return;
}
console.log(`📦 Encontradas ${parcelas.length} parcela(s) vencida(s) ou vencendo hoje:\n`);
parcelas.forEach((parcela, index) => {
const cliente = parcela.vendas?.clientes;
console.log(`${index + 1}. ${cliente?.nome_completo || 'N/A'} - Parcela ${parcela.numero_parcela}`);
console.log(` Valor: ${formatarMoeda(parcela.valor)}`);
console.log(` Vencimento: ${formatarDataBR(parcela.data_vencimento)}`);
console.log(` WhatsApp: ${cliente?.whatsapp || 'N/A'}`);
console.log('');
});
const resposta = await question('Deseja enviar alertas + PIX para essas parcelas? (s/N): ');
if (!resposta.match(/^[Ss]$/)) {
console.log('\n❌ Operação cancelada.\n');
rl.close();
return;
}
console.log('\n🚀 Iniciando envio...\n');
let enviados = 0;
let erros = 0;
for (const parcela of parcelas) {
const cliente = parcela.vendas?.clientes;
if (!cliente || !cliente.whatsapp) {
console.log(`⚠️ Parcela ${parcela.numero_parcela}: Cliente sem WhatsApp`);
erros++;
continue;
}
const nomeCliente = cliente.nome_completo.split(' ')[0];
const valorFormatado = formatarMoeda(parcela.valor);
// Gerar PIX
console.log(`📱 Gerando PIX para ${cliente.nome_completo}...`);
const pixResult = await gerarPix(parcela, mercadoPagoToken);
let mensagem = `Olá ${nomeCliente}! 👋\n\n`;
mensagem += `Você tem uma parcela no valor de ${valorFormatado} `;
mensagem += `com vencimento em ${formatarDataBR(parcela.data_vencimento)}.\n\n`;
if (pixResult.success && pixResult.qrCode) {
mensagem += `📱 *PIX Copia e Cola:*\n\`\`\`${pixResult.qrCode}\`\`\`\n\n`;
// Atualizar parcela com 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);
console.log(` ✅ PIX gerado`);
} else {
mensagem += `Entre em contato para obter forma de pagamento.\n\n`;
console.log(` ⚠️ Não foi possível gerar PIX`);
}
mensagem += `Agradecemos! 😊\n*Liberi Kids - Moda Infantil* 👗👕`;
// Enviar mensagem
console.log(`📤 Enviando para ${cliente.whatsapp}...`);
const resultado = await enviarWhatsApp(cliente.whatsapp, mensagem, evolutionConfig);
if (resultado.success) {
enviados++;
console.log(` ✅ Enviado com sucesso!\n`);
// Registrar 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(` ❌ Erro: ${resultado.error}\n`);
}
// Delay entre mensagens
await new Promise(resolve => setTimeout(resolve, 2000));
}
console.log('\n' + '='.repeat(50));
console.log('📊 RESUMO');
console.log('='.repeat(50));
console.log(`✅ Alertas enviados: ${enviados}`);
console.log(`❌ Erros: ${erros}`);
console.log(`📦 Total processado: ${parcelas.length}`);
console.log('='.repeat(50) + '\n');
} catch (error) {
console.error('\n💥 Erro:', error);
} finally {
rl.close();
}
}
main();