Initial commit - Horux Despachos NL
This commit is contained in:
110
apps/api/src/services/notification-preferences.service.ts
Normal file
110
apps/api/src/services/notification-preferences.service.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { Pool } from 'pg';
|
||||
|
||||
/**
|
||||
* Tipos de correos informativos cuyo envío puede desactivarse por
|
||||
* contribuyente. NO incluye correos transaccionales críticos
|
||||
* (welcome, password-reset, payment-*) — esos siempre se envían.
|
||||
*
|
||||
* Estado de implementación:
|
||||
* - documento_subido: ✅ implementado (notify-upload.service.ts)
|
||||
* - weekly_update: ⏳ pendiente (job es tenant-wide hoy)
|
||||
* - subscription_expiring: ⏳ pendiente (no es per-contribuyente hoy)
|
||||
* - recordatorio_fiscal: ⏳ placeholder para futuras alertas
|
||||
*/
|
||||
export const EMAIL_TYPES = [
|
||||
'documento_subido',
|
||||
'weekly_update',
|
||||
'subscription_expiring',
|
||||
'recordatorio_fiscal',
|
||||
] as const;
|
||||
|
||||
export type EmailType = (typeof EMAIL_TYPES)[number];
|
||||
|
||||
export type EmailPreferences = Record<EmailType, boolean>;
|
||||
|
||||
/**
|
||||
* Default: todo activado. Si el JSONB en BD viene vacío o falta una
|
||||
* key, asumimos `true` para preservar el comportamiento previo.
|
||||
*/
|
||||
function applyDefaults(raw: Partial<Record<string, unknown>>): EmailPreferences {
|
||||
const out = {} as EmailPreferences;
|
||||
for (const t of EMAIL_TYPES) {
|
||||
out[t] = raw[t] === false ? false : true;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function sanitizeUuid(id: string): string {
|
||||
return id.replace(/[^a-f0-9-]/gi, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Lee las preferencias de un contribuyente. Devuelve defaults (todo
|
||||
* activado) si no hay fila o la columna está vacía.
|
||||
*/
|
||||
export async function getContribuyenteEmailPreferences(
|
||||
pool: Pool,
|
||||
contribuyenteId: string,
|
||||
): Promise<EmailPreferences> {
|
||||
const safeId = sanitizeUuid(contribuyenteId);
|
||||
const { rows } = await pool.query<{ email_preferences: Record<string, unknown> | null }>(
|
||||
`SELECT email_preferences FROM contribuyentes WHERE entidad_id = $1`,
|
||||
[safeId],
|
||||
);
|
||||
const raw = rows[0]?.email_preferences ?? {};
|
||||
return applyDefaults(raw);
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza las preferencias de un contribuyente. Solo persiste las
|
||||
* keys conocidas (filtra extras maliciosos). Merge sobre la columna
|
||||
* existente (no sobreescribe keys no enviadas).
|
||||
*/
|
||||
export async function setContribuyenteEmailPreferences(
|
||||
pool: Pool,
|
||||
contribuyenteId: string,
|
||||
partial: Partial<EmailPreferences>,
|
||||
): Promise<EmailPreferences> {
|
||||
const safeId = sanitizeUuid(contribuyenteId);
|
||||
const merged: Record<string, boolean> = {};
|
||||
for (const t of EMAIL_TYPES) {
|
||||
if (t in partial) merged[t] = partial[t] === true;
|
||||
}
|
||||
|
||||
await pool.query(
|
||||
`UPDATE contribuyentes
|
||||
SET email_preferences = COALESCE(email_preferences, '{}'::jsonb) || $2::jsonb
|
||||
WHERE entidad_id = $1`,
|
||||
[safeId, JSON.stringify(merged)],
|
||||
);
|
||||
|
||||
return getContribuyenteEmailPreferences(pool, contribuyenteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lee preferencias para múltiples contribuyentes en una sola query.
|
||||
* Útil para la UI de `/configuracion/notificaciones` que lista todos.
|
||||
*/
|
||||
export async function getEmailPreferencesPorContribuyente(
|
||||
pool: Pool,
|
||||
): Promise<Array<{ contribuyenteId: string; rfc: string; nombre: string; preferences: EmailPreferences }>> {
|
||||
const { rows } = await pool.query<{
|
||||
entidad_id: string;
|
||||
rfc: string;
|
||||
nombre: string;
|
||||
email_preferences: Record<string, unknown> | null;
|
||||
}>(
|
||||
`SELECT c.entidad_id, c.rfc, e.nombre, c.email_preferences
|
||||
FROM contribuyentes c
|
||||
JOIN entidades_gestionadas e ON e.id = c.entidad_id
|
||||
WHERE e.active = true
|
||||
ORDER BY e.nombre`,
|
||||
);
|
||||
|
||||
return rows.map(r => ({
|
||||
contribuyenteId: r.entidad_id,
|
||||
rfc: r.rfc,
|
||||
nombre: r.nombre,
|
||||
preferences: applyDefaults(r.email_preferences ?? {}),
|
||||
}));
|
||||
}
|
||||
Reference in New Issue
Block a user