feat: implement comprehensive notification engine with policies, preferences, and automated dispatching services

This commit is contained in:
2026-07-21 18:53:11 -03:00
parent 40090cdec5
commit 10ca449f88
82 changed files with 10249 additions and 171 deletions
@@ -0,0 +1,200 @@
import mongoose from "mongoose";
import { buildNotificationText, resolveNotificationContent } from "../NotificationContentResolver.js";
describe("NotificationContentResolver", () => {
it("builds appointment creation subject and message as Alta de turno", () => {
const result = buildNotificationText({
type: "creation",
appointment: {
_id: "appointment-1",
companyId: "company-1",
start: new Date("2026-07-22T15:30:00.000Z"),
},
service: { _id: "service-1", name: "Corte" },
company: { _id: "company-1", name: "Barber Shop" },
});
expect(result.subject).toContain("Alta de turno");
expect(result.subject).toContain("Barber Shop");
expect(result.message).toContain("Corte");
expect(result.message).toContain("confirmado");
});
it("builds meaningful appointment reminder subject and message", () => {
const result = buildNotificationText({
type: "reminder",
appointment: {
_id: "appointment-1",
companyId: "company-1",
start: new Date("2026-07-22T15:30:00.000Z"),
},
service: { _id: "service-1", name: "Corte" },
company: { _id: "company-1", name: "Barber Shop" },
});
expect(result.subject).toContain("Recordatorio");
expect(result.subject).toContain("Barber Shop");
expect(result.message).toContain("Corte");
expect(result.message).toContain("Barber Shop");
expect(result.subject.trim()).not.toBe("");
expect(result.message.trim()).not.toBe("");
});
it("uses cancellation snapshot payload without requiring appointment lookup", async () => {
const result = await resolveNotificationContent({
appointmentId: "deleted-appointment-1",
clientId: "client-1",
companyId: "company-1",
channel: "email",
type: "cancellation",
payload: {
email: "client@example.com",
phoneNumber: "5491112345678",
userId: "user-1",
companyOwnerId: "owner-1",
subject: "Turno cancelado en Barber Shop",
message: "Tu turno para Corte fue cancelado.",
emailSubject: "Email cancellation subject",
emailMessage: "Email cancellation body.",
wapMessage: "WAP cancellation body.",
systemSubject: "System cancellation subject",
systemMessage: "System cancellation body.",
},
} as any);
expect(result).toEqual({
email: "client@example.com",
phoneNumber: "5491112345678",
userId: "user-1",
companyOwnerId: "owner-1",
subject: "Email cancellation subject",
message: "Email cancellation body.",
});
});
it("uses creation snapshot payload content for email jobs", async () => {
const result = await resolveNotificationContent({
appointmentId: "appointment-1",
clientId: "client-1",
companyId: "company-1",
channel: "email",
type: "creation",
payload: {
email: "client@example.com",
phoneNumber: "5491112345678",
userId: "user-1",
companyOwnerId: "owner-1",
subject: "Turno reservado en Barber Shop",
message: "Generic creation body.",
emailSubject: "Email Alta subject",
emailMessage: "Email Alta body.",
wapMessage: "WAP Alta body.",
systemSubject: "System Alta subject",
systemMessage: "System Alta body.",
},
} as any);
expect(result).toEqual({
email: "client@example.com",
phoneNumber: "5491112345678",
userId: "user-1",
companyOwnerId: "owner-1",
subject: "Email Alta subject",
message: "Email Alta body.",
});
});
it("uses creation snapshot payload content for WhatsApp jobs", async () => {
const result = await resolveNotificationContent({
appointmentId: "appointment-1",
clientId: "client-1",
companyId: "company-1",
channel: "whatsapp",
type: "creation",
payload: {
phoneNumber: "5491112345678",
subject: "Generic subject",
message: "Generic message.",
emailMessage: "Email Alta body.",
wapMessage: "WAP Alta body.",
},
} as any);
expect(result.message).toBe("WAP Alta body.");
});
it("uses reminder payload content without requiring appointment lookup", async () => {
const appointmentLookup = jest.spyOn(mongoose.models.Appointment, "findOne");
const result = await resolveNotificationContent({
appointmentId: "appointment-1",
clientId: "client-1",
companyId: "company-1",
channel: "whatsapp",
type: "reminder",
payload: {
email: "client@example.com",
phoneNumber: "5491112345678",
userId: "user-1",
companyOwnerId: "owner-1",
subject: "Generic reminder subject",
message: "Generic reminder body.",
emailSubject: "Email reminder subject",
emailMessage: "Email reminder body.",
wapMessage: "WAP reminder body.",
systemSubject: "System reminder subject",
systemMessage: "System reminder body.",
},
} as any);
expect(appointmentLookup).not.toHaveBeenCalled();
expect(result).toEqual({
email: "client@example.com",
phoneNumber: "5491112345678",
userId: "user-1",
companyOwnerId: "owner-1",
subject: "Generic reminder subject",
message: "WAP reminder body.",
});
});
it("uses WhatsApp cancellation payload content for WhatsApp jobs", async () => {
const result = await resolveNotificationContent({
appointmentId: "deleted-appointment-1",
clientId: "client-1",
companyId: "company-1",
channel: "whatsapp",
type: "cancellation",
payload: {
phoneNumber: "5491112345678",
companyOwnerId: "owner-1",
subject: "Generic subject",
message: "Generic message.",
emailMessage: "Email cancellation body.",
wapMessage: "WAP cancellation body.",
},
} as any);
expect(result.message).toBe("WAP cancellation body.");
});
it("uses system cancellation payload content for system jobs", async () => {
const result = await resolveNotificationContent({
appointmentId: "deleted-appointment-1",
clientId: "client-1",
companyId: "company-1",
channel: "system",
type: "cancellation",
payload: {
userId: "user-1",
subject: "Generic subject",
message: "Generic message.",
systemSubject: "System cancellation subject",
systemMessage: "System cancellation body.",
},
} as any);
expect(result.subject).toBe("System cancellation subject");
expect(result.message).toBe("System cancellation body.");
});
});