Files

181 lines
7.5 KiB
JavaScript

'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