'use strict' const Notification = require('../models/Notification') const WhatsappCore = require('./whatsappService') const { query } = require('../database/postgres') const BASE_URL = process.env.APP_URL || 'http://localhost:4001' class NotificationService { // ── Ganhador ─────────────────────────────────────────────────────────────── static async notifyWinner(user_cpf, userName, userPhone, raffleName, ticketNumber) { try { await Notification.create({ user_cpf, title: '🏆 Parabéns! Você ganhou!', message: `Seu ticket #${ticketNumber} foi sorteado no prêmio "${raffleName}". Clique aqui para reivindicar.`, type: 'win', }) if (userPhone?.length >= 10) { await WhatsappCore.sendNotification({ userId: null, phone: userPhone, type: 'winner', userCpf: user_cpf, variables: { nome: userName || 'Cliente', numero: ticketNumber, raffleName, link: `${BASE_URL}/meus-premios` }, }).catch(console.error) } } catch (err) { console.error('[Notificações] notifyWinner:', err.message) } } // ── Boas-vindas ──────────────────────────────────────────────────────────── static async notifyWelcome(userId, userName, userPhone, userCpf) { try { if (userPhone?.length >= 10) { await WhatsappCore.sendNotification({ userId, phone: userPhone, type: 'welcome', userCpf, variables: { nome: userName || 'Cliente', link: `${BASE_URL}/area-do-usuario` }, }).catch(console.error) } } catch (err) { console.error('[Notificações] notifyWelcome:', err.message) } } // ── Pós-sorteio / Resgate ────────────────────────────────────────────────── static async notifyClaimCreated(userPhone, userCpf, userName, raffleName, ticketNumber) { try { if (userPhone?.length >= 10) { await WhatsappCore.sendNotification({ userId: null, phone: userPhone, type: 'claim_received', userCpf, variables: { nome: userName || 'Cliente', raffleName, numero: ticketNumber }, }).catch(console.error) } } catch (err) { console.error('[Notificações] notifyClaimCreated:', err.message) } } static async notifyClaimApproved(userPhone, userCpf, userName, raffleName) { try { if (userPhone?.length >= 10) { await WhatsappCore.sendNotification({ userId: null, phone: userPhone, type: 'claim_approved', userCpf, variables: { nome: userName || 'Cliente', raffleName }, }).catch(console.error) } } catch (err) { console.error('[Notificações] notifyClaimApproved:', err.message) } } static async notifyClaimDelivered(userPhone, userCpf, userName, raffleName) { try { if (userPhone?.length >= 10) { await WhatsappCore.sendNotification({ userId: null, phone: userPhone, type: 'claim_delivered', userCpf, variables: { nome: userName || 'Cliente', raffleName }, }).catch(console.error) } } catch (err) { console.error('[Notificações] notifyClaimDelivered:', err.message) } } // ── Clube de Benefícios ──────────────────────────────────────────────────── // Todas as notificações de clube estão em services/clubNotificationService.js // clubController.js usa ClubNotificationService diretamente. // ── Redefinição de senha ─────────────────────────────────────────────────── static async notifyPasswordReset(userId, userName, userPhone, userCpf, link) { try { if (!userPhone || userPhone.length < 10) return await WhatsappCore.sendNotification({ userId, phone: userPhone, type: 'password_reset', userCpf, variables: { nome: userName || 'Cliente', link }, }).catch(console.error) } catch (err) { console.error('[Notificações] notifyPasswordReset:', err.message) } } // ── Avisos de segurança ──────────────────────────────────────────────────── static async notifySecurityLogin(userId, userName, userPhone, userCpf) { try { if (!userPhone || userPhone.length < 10) return const now = new Date() const data = now.toLocaleDateString('pt-BR') const hora = now.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) await WhatsappCore.sendNotification({ userId, phone: userPhone, type: 'security_login', userCpf, variables: { nome: userName || 'Cliente', data, hora }, }).catch(console.error) } catch (err) { console.error('[Notificações] notifySecurityLogin:', err.message) } } // ── Queda de preço (broadcast) ───────────────────────────────────────────── static async notifyPriceDrop(raffleId, raffleName, newPrice) { try { // Todos os usuários opt-in com telefone que ainda não compraram este sorteio const rows = await query( `SELECT DISTINCT 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' )`, [raffleId] ) const link = `${BASE_URL}/sorteio` for (const u of (rows ?? [])) { WhatsappCore.sendNotification({ userId: null, phone: u.telefone, type: 'price_drop', userCpf: u.cpf, variables: { raffleName, novo_valor: Number(newPrice).toFixed(2), link }, }).catch(() => {}) } console.log(`[Notificações] price_drop enviado para ${(rows ?? []).length} usuário(s) — rifa #${raffleId}`) } catch (err) { console.error('[Notificações] notifyPriceDrop:', err.message) } } // ── Queda de preço localizada (reservas pendentes de pagamento) ──────────── static async notifyPriceDropLocal(raffleId, raffleName, newPrice) { try { const rows = await query( `SELECT DISTINCT t.usuario_telefone, t.usuario_cpf, t.usuario_nome FROM tickets t JOIN orders o ON o.id = t.order_id WHERE t.raffle_id = $1 AND t.status = 'reserved' AND o.status = 'pending' AND t.usuario_telefone IS NOT NULL`, [raffleId] ) const link = `${BASE_URL}/area-do-usuario` for (const t of (rows ?? [])) { WhatsappCore.sendNotification({ userId: null, phone: t.usuario_telefone, type: 'price_drop_local', userCpf: t.usuario_cpf, variables: { raffleName, novo_valor: Number(newPrice).toFixed(2), link }, }).catch(() => {}) } console.log(`[Notificações] price_drop_local enviado para ${(rows ?? []).length} reserva(s) — rifa #${raffleId}`) } catch (err) { console.error('[Notificações] notifyPriceDropLocal:', err.message) } } } module.exports = NotificationService