import type { Pool } from 'pg'; import { prisma } from '../config/database.js'; import { emailService } from './email/email.service.js'; import { getTenantOwnerEmails, getUserEmailById } from '../utils/memberships.js'; import { env } from '../config/env.js'; import { getContribuyenteEmailPreferences } from './notification-preferences.service.js'; import type { DocumentoSubidoData } from './email/templates/documento-subido.js'; /** * Notifica a los destinatarios relevantes cuando se sube una declaración * o un documento extra. Destinatarios: * - Owners activos del despacho (getTenantOwnerEmails) * - Supervisor del contribuyente (entidades_gestionadas.supervisor_user_id), * si existe y no coincide con un owner ya incluido * * El uploader mismo SE EXCLUYE (no tiene sentido notificarle su propia acción). * * Fire-and-forget: el caller hace `.catch()` y esta función no re-lanza. * Fail-soft: si SMTP no está configurado, los envíos se loguean a consola * vía el transport de @horux/core. */ export async function notifyDocumentoSubido(params: { pool: Pool; tenantId: string; contribuyenteId: string | null; subidoPor: string; kind: DocumentoSubidoData['kind']; declaracion?: DocumentoSubidoData['declaracion']; extra?: DocumentoSubidoData['extra']; }): Promise { const { pool, tenantId, contribuyenteId, subidoPor } = params; // 1. Datos del contribuyente (desde BD tenant). Sin contribuyenteId no hay // subject informativo ni supervisor — skip. if (!contribuyenteId) return; // Respeta preferencias de notificación del contribuyente. Si el user // desactivó `documento_subido` para este contribuyente, no enviar. const prefs = await getContribuyenteEmailPreferences(pool, contribuyenteId); if (!prefs.documento_subido) return; const { rows } = await pool.query<{ rfc: string; nombre: string; supervisor_user_id: string | null; }>( `SELECT c.rfc, eg.nombre, eg.supervisor_user_id FROM contribuyentes c JOIN entidades_gestionadas eg ON eg.id = c.entidad_id WHERE c.entidad_id = $1`, [contribuyenteId.replace(/[^a-f0-9-]/gi, '')], ); if (rows.length === 0) return; const contrib = rows[0]; // 2. Recipients. Owners primero; luego supervisor si aplica. const owners = await getTenantOwnerEmails(tenantId); const recipients = new Set(owners); if (contrib.supervisor_user_id) { const supervisorEmail = await getUserEmailById(contrib.supervisor_user_id); if (supervisorEmail) recipients.add(supervisorEmail); } // Excluir al uploader: no notificarle su propia acción. recipients.delete(subidoPor.toLowerCase()); recipients.delete(subidoPor); if (recipients.size === 0) return; // 3. Datos del despacho (para mostrar en el body). const tenant = await prisma.tenant.findUnique({ where: { id: tenantId }, select: { nombre: true }, }); // 4. Link al sistema. Usa FRONTEND_URL del env. const link = `${env.FRONTEND_URL}/documentos`; await emailService.sendDocumentoSubido(Array.from(recipients), { kind: params.kind, subidoPor, contribuyenteRfc: contrib.rfc, contribuyenteNombre: contrib.nombre, despachoNombre: tenant?.nombre, declaracion: params.declaracion, extra: params.extra, link, }); }