import mongoose, { Model } from "mongoose"; import { INotificationJobDocument, NotificationJobType } from "./NotificationJob.Adapter.Mongoose.js"; const { Schema, model, models } = mongoose; interface AppointmentDocument { _id: unknown; companyId: unknown; serviceId?: unknown; clientId?: unknown; userId?: unknown; start: Date; } interface ClientDocument { _id: unknown; userId?: unknown; firstName?: string; lastName?: string; email?: string; } interface UserDocument { _id: unknown; email?: string; firstName?: string; lastName?: string; } interface ServiceDocument { _id: unknown; name?: string; } interface CompanyDocument { _id: unknown; name?: string; ownerId?: unknown; } export interface ResolvedNotificationContent { email?: string; phoneNumber?: string; userId?: string; companyOwnerId?: string; subject: string; message: string; emailTemplateId?: string; emailContext?: Record; } const appointmentSchema = new Schema( { companyId: { type: Schema.Types.ObjectId, required: true, ref: "Companie" }, serviceId: { type: Schema.Types.ObjectId, required: false, ref: "Service" }, clientId: { type: Schema.Types.ObjectId, required: false, ref: "Client" }, userId: { type: Schema.Types.ObjectId, required: false, ref: "User" }, start: { type: Date, required: true }, }, { strict: false } ); const clientSchema = new Schema( { userId: { type: Schema.Types.ObjectId, required: false, ref: "User" }, firstName: { type: String, required: false }, lastName: { type: String, required: false }, email: { type: String, required: false }, }, { strict: false } ); const userSchema = new Schema( { email: { type: String, required: false }, firstName: { type: String, required: false }, lastName: { type: String, required: false }, }, { strict: false } ); const serviceSchema = new Schema( { name: { type: String, required: false }, }, { strict: false } ); const companySchema = new Schema( { name: { type: String, required: false }, ownerId: { type: Schema.Types.ObjectId, required: false, ref: "User" }, }, { strict: false } ); const Appointment = (models.Appointment || model("Appointment", appointmentSchema)) as Model; const Client = (models.Client || model("Client", clientSchema)) as Model; const User = (models.User || model("User", userSchema)) as Model; const Service = (models.Service || model("Service", serviceSchema)) as Model; const Company = (models.Company || model("Company", companySchema)) as Model; function compactName(...parts: Array): string { return parts.filter(Boolean).join(" ").trim(); } function formatDateTime(value?: Date): string { if (!value) return "fecha pendiente"; return new Intl.DateTimeFormat("es-AR", { dateStyle: "short", timeStyle: "short", timeZone: "America/Argentina/Buenos_Aires", }).format(value); } export function buildNotificationText(params: { type: NotificationJobType; appointment?: AppointmentDocument | null; client?: ClientDocument | null; service?: ServiceDocument | null; company?: CompanyDocument | null; }): Pick { const companyName = params.company?.name || "TurnosXpress"; const serviceName = params.service?.name || "tu turno"; const appointmentDate = formatDateTime(params.appointment?.start); switch (params.type) { case "creation": return { subject: `Alta de turno en ${companyName}`, message: `Tu turno para ${serviceName} en ${companyName} fue confirmado para el ${appointmentDate}.`, }; case "reminder": return { subject: `Recordatorio de turno en ${companyName}`, message: `Te recordamos tu turno para ${serviceName} en ${companyName} el ${appointmentDate}.`, }; case "update": return { subject: `Turno actualizado en ${companyName}`, message: `Tu turno para ${serviceName} en ${companyName} fue actualizado. Nueva fecha: ${appointmentDate}.`, }; case "cancellation": return { subject: `Turno cancelado en ${companyName}`, message: `Tu turno para ${serviceName} en ${companyName} del ${appointmentDate} fue cancelado.`, }; } } export async function resolveNotificationContent(job: INotificationJobDocument): Promise { if ((job.type === "creation" || job.type === "reminder" || job.type === "update" || job.type === "cancellation") && job.payload) { const { email, phoneNumber, userId, companyOwnerId } = job.payload; const subject = job.channel === "email" ? job.payload.emailSubject : job.channel === "system" ? job.payload.systemSubject : job.payload.subject; const message = job.channel === "whatsapp" ? job.payload.wapMessage : job.channel === "email" ? job.payload.emailMessage : job.payload.systemMessage; const fallbackSubject = subject || job.payload.subject; const fallbackMessage = message || job.payload.message; if (fallbackSubject?.trim() && fallbackMessage?.trim()) { return { email: email?.trim(), phoneNumber: phoneNumber?.trim(), userId: userId ? String(userId) : undefined, companyOwnerId: companyOwnerId ? String(companyOwnerId) : undefined, subject: fallbackSubject.trim(), message: fallbackMessage.trim(), emailTemplateId: job.payload.emailTemplateId?.trim(), emailContext: job.payload.emailContext, }; } } const appointment = await Appointment.findOne({ _id: job.appointmentId }).lean().exec(); const client = await Client.findOne({ _id: job.clientId }).lean().exec(); const company = await Company.findOne({ _id: job.companyId }).lean().exec(); const service = appointment?.serviceId ? await Service.findOne({ _id: appointment.serviceId }).lean().exec() : null; const userId = client?.userId || appointment?.userId || company?.ownerId; const user = userId ? await User.findOne({ _id: userId }).lean().exec() : null; const text = buildNotificationText({ type: job.type, appointment, client, service, company }); const email = client?.email || user?.email; return { email: email?.trim(), userId: userId ? String(userId) : undefined, subject: text.subject.trim(), message: text.message.trim(), }; }