feat(notificaciones): configuración de notificaciones por rol
- Nueva tabla tenant notification_role_preferences para guardar (email_type, role, enabled). - Migración 051 aplicada a todos los tenants. - Backend expone endpoint /notificaciones con matriz de preferencias por rol. - Filtrado por rol en documento_subido, weekly_update, subscription_expiring, alertas_nuevas y recordatorio_proximo. - Frontend rediseñado como tabla notificación × rol con toggles inmediatos.
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { prisma } from '../../config/database.js';
|
||||
import { prisma, tenantDb } from '../../config/database.js';
|
||||
import * as mpService from './mercadopago.service.js';
|
||||
import { emailService } from '../email/email.service.js';
|
||||
import { auditLog } from '../../utils/audit.js';
|
||||
import { getTenantOwnerEmail } from '../../utils/memberships.js';
|
||||
import { getTenantOwnerEmail, getTenantOwnerEmails } from '../../utils/memberships.js';
|
||||
import { filterRecipientsByRole } from '../notification-preferences.service.js';
|
||||
import { isDespachoPaidPlan, permiteOverage, type DespachoPricePhase } from '@horux/shared';
|
||||
import { despachoPlanTieneDualidadDb, getPrecioDespachoDb } from '../plan-catalogo.service.js';
|
||||
import {
|
||||
@@ -1191,7 +1192,7 @@ export async function sendExpiryReminders(): Promise<{ sent: number; resetOnly:
|
||||
{ status: 'trial_expired', currentPeriodEnd: { gte: oneDayAgo } },
|
||||
],
|
||||
},
|
||||
include: { tenant: { select: { nombre: true, rfc: true } } },
|
||||
include: { tenant: { select: { nombre: true, rfc: true, databaseName: true } } },
|
||||
});
|
||||
|
||||
let sent = 0;
|
||||
@@ -1235,33 +1236,48 @@ export async function sendExpiryReminders(): Promise<{ sent: number; resetOnly:
|
||||
|
||||
// Hay algo que avisar.
|
||||
try {
|
||||
const ownerEmail = await getTenantOwnerEmail(sub.tenantId);
|
||||
if (!ownerEmail) {
|
||||
// Para suscripciones de pago, respeta preferencia 'subscription_expiring' del rol owner.
|
||||
// Para trials siempre avisa al owner (no depende de preferencias de notificación informativa).
|
||||
const isTrialFlow = sub.status === 'trial' || sub.status === 'trial_expired';
|
||||
let emailsToNotify: string[] = [];
|
||||
|
||||
if (isTrialFlow) {
|
||||
const ownerEmail = await getTenantOwnerEmail(sub.tenantId);
|
||||
if (ownerEmail) emailsToNotify = [ownerEmail];
|
||||
} else {
|
||||
const pool = await tenantDb.getPool(sub.tenantId, sub.tenant.databaseName);
|
||||
const ownerEmails = await getTenantOwnerEmails(sub.tenantId);
|
||||
const recipientsWithRole = ownerEmails.map(email => ({ email, role: 'owner' as const }));
|
||||
emailsToNotify = await filterRecipientsByRole(pool, 'subscription_expiring', recipientsWithRole);
|
||||
}
|
||||
|
||||
if (emailsToNotify.length === 0) {
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const isTrialFlow = sub.status === 'trial' || sub.status === 'trial_expired';
|
||||
if (isTrialFlow) {
|
||||
if (bucket === 0) {
|
||||
await emailService.sendTrialExpired(ownerEmail, {
|
||||
nombre: sub.tenant.nombre,
|
||||
despachoNombre: sub.tenant.nombre,
|
||||
});
|
||||
for (const ownerEmail of emailsToNotify) {
|
||||
if (isTrialFlow) {
|
||||
if (bucket === 0) {
|
||||
await emailService.sendTrialExpired(ownerEmail, {
|
||||
nombre: sub.tenant.nombre,
|
||||
despachoNombre: sub.tenant.nombre,
|
||||
});
|
||||
} else {
|
||||
await emailService.sendTrialReminder(ownerEmail, {
|
||||
nombre: sub.tenant.nombre,
|
||||
despachoNombre: sub.tenant.nombre,
|
||||
diasRestantes: Math.max(0, daysUntil),
|
||||
wizardCompleto: true,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await emailService.sendTrialReminder(ownerEmail, {
|
||||
await emailService.sendSubscriptionExpiring(ownerEmail, {
|
||||
nombre: sub.tenant.nombre,
|
||||
despachoNombre: sub.tenant.nombre,
|
||||
diasRestantes: Math.max(0, daysUntil),
|
||||
wizardCompleto: true,
|
||||
plan: sub.plan,
|
||||
expiresAt: sub.currentPeriodEnd.toLocaleDateString('es-MX', { dateStyle: 'long' }),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
await emailService.sendSubscriptionExpiring(ownerEmail, {
|
||||
nombre: sub.tenant.nombre,
|
||||
plan: sub.plan,
|
||||
expiresAt: sub.currentPeriodEnd.toLocaleDateString('es-MX', { dateStyle: 'long' }),
|
||||
});
|
||||
}
|
||||
|
||||
await prisma.subscription.update({
|
||||
|
||||
Reference in New Issue
Block a user