'use strict' const cron = require('node-cron') const { query, execute } = require('../database/postgres') const RaffleResult = require('../models/RaffleResult') const Ticket = require('../models/Ticket') const WhatsappCore = require('../services/whatsappService') const BASE_URL = process.env.APP_URL || 'http://localhost:4001' function startWorker() { console.log('⏳ Worker de Sorteio Automático iniciado (node-cron)') // ── A cada minuto: sorteio + liberar reservas + alerta expiração ─────────── cron.schedule('* * * * *', async () => { try { await Ticket.releaseExpiredReservations() await _alertReservationExpiring() const raffles = await query("SELECT id, total_numeros, end_at FROM raffles WHERE status = 'active'") for (const raffle of raffles) { let shouldDraw = false let reason = '' if (raffle.end_at && new Date() >= new Date(raffle.end_at)) { shouldDraw = true reason = 'Data limite atingida' } if (!shouldDraw) { const totalSold = await Ticket.countByRaffle(raffle.id) if (totalSold >= raffle.total_numeros) { shouldDraw = true reason = 'Lotação máxima atingida (100% vendido)' } } if (shouldDraw) { console.log(`🤖 [Worker] Iniciando sorteio automático #${raffle.id} - Motivo: ${reason}`) try { const result = await RaffleResult.draw(raffle.id, '127.0.0.1') console.log(`🎉 [Worker] Sorteio #${raffle.id} finalizado! Ganhador: Ticket #${result.winner_ticket_id} (Número ${result.winner_number})`) } catch (drawError) { if (drawError.message === 'Nenhum ticket vendido para este sorteio') { console.log(`⚠️ [Worker] Sorteio #${raffle.id} ignorado: nenhum ticket vendido.`) } else { console.error(`❌ [Worker] Erro ao executar sorteio #${raffle.id}:`, drawError.message) } } } } } catch (e) { console.error('❌ [Worker] Falha na checagem cron (minuto):', e.message) } }) // ── A cada 10 minutos: notificação "Últimas Horas" ───────────────────────── cron.schedule('*/10 * * * *', async () => { try { await _alertLastHours() } catch (e) { console.error('❌ [Worker] Falha na checagem last_hours:', e.message) } }) } // ─── Alerta "Últimas Horas" ─────────────────────────────────────────────────── // Dispara 1× por sorteio cujo end_at está entre agora e +2h, // e que ainda não foi notificado (last_hours_notif_sent IS NULL). async function _alertLastHours() { const rows = await query( `SELECT id, titulo FROM raffles WHERE status = 'active' AND end_at IS NOT NULL AND end_at BETWEEN NOW() AND NOW() + INTERVAL '2 hours' AND last_hours_notif_sent IS NULL` ) for (const raffle of (rows ?? [])) { // Marca antes de enviar — evita duplos em caso de erro parcial await execute(`UPDATE raffles SET last_hours_notif_sent = NOW() WHERE id = $1`, [raffle.id]) const users = await query( `SELECT DISTINCT u.id, u.telefone, u.cpf, u.nome FROM users u WHERE u.telefone IS NOT NULL AND u.whatsapp_optin = TRUE AND u.cpf NOT IN ( SELECT DISTINCT t.usuario_cpf FROM tickets t WHERE t.raffle_id = $1 AND t.status = 'sold' )`, [raffle.id] ) const link = `${BASE_URL}/sorteio` for (const u of (users ?? [])) { WhatsappCore.sendNotification({ userId: u.id, phone: u.telefone, type: 'last_hours', userCpf: u.cpf, variables: { raffleName: raffle.titulo, link }, }).catch(() => {}) } console.log(`⏰ [Worker] last_hours rifa #${raffle.id} — ${(users ?? []).length} notificação(ões) enfileirada(s)`) } } // ─── Alerta "Reserva Expirando" ─────────────────────────────────────────────── // Reservas com reserved_until entre 50s e 70s no futuro (≈ 1 min restante). async function _alertReservationExpiring() { const rows = await query( `SELECT DISTINCT t.usuario_telefone, t.usuario_cpf, t.usuario_nome, o.pix_copy_paste, o.qr_code_url, r.titulo as raffle_titulo FROM tickets t JOIN orders o ON o.id = t.order_id JOIN raffles r ON r.id = t.raffle_id WHERE t.status = 'reserved' AND o.status = 'pending' AND t.usuario_telefone IS NOT NULL AND t.reserved_until BETWEEN NOW() + INTERVAL '50 seconds' AND NOW() + INTERVAL '70 seconds'` ) for (const t of (rows ?? [])) { WhatsappCore.sendNotification({ userId: null, phone: t.usuario_telefone, type: 'reservation_expiring', userCpf: t.usuario_cpf, variables: { raffleName: t.raffle_titulo, link: t.qr_code_url || `${BASE_URL}/area-do-usuario`, }, }).catch(() => {}) } if ((rows ?? []).length > 0) { console.log(`⚠️ [Worker] reservation_expiring: ${rows.length} alerta(s) enviado(s)`) } } module.exports = { startWorker }