feat: agregar soporte para plantillas de correo electrónico y contexto en el envío de notificaciones

This commit is contained in:
2026-08-20 11:38:50 -03:00
parent e4441fa476
commit b6399d28fc
13 changed files with 174 additions and 11 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ MP_SUBSCRIPTION_FIND_URL = https://api.mercadopago.com/preapproval/
MP_ACCESS_TOKEN = APP_USR-8753424836448507-121412-731599daea1ffa0b278b5dcc78aa1ec8-1611134648
MP_BACK_URL = https://turnosxpress.com.ar/landing/dashboard
MP_CHECK_PAYMENT_MINUTES = 1
MP_WEBHOOK_URL = https://9300-186-132-200-176.ngrok-free.app/mercadopago/webhook
MP_WEBHOOK_URL = https://30e1-186-132-147-13.ngrok-free.app/mercadopago/webhook
DEFAULT_BOT_ADMIN_PORT = 3005
@@ -204,6 +204,8 @@ export interface AppointmentNotificationIntent {
message: string;
checkClient: IClientDocument;
companyCheck: ICompanyDocument;
emailTemplateId?: string;
emailContext?: Record<string, unknown>;
}
export interface IAppointment {
+87 -1
View File
@@ -287,6 +287,23 @@ class AppointmentManager implements IAppointmentsManager {
return parseInt(`${process.env.CANCELLATION_TIME}`) || 24;
}
private formatAppointmentDuration(startHour: number, endHour: number): string {
const durationHours = endHour >= startHour ? endHour - startHour : endHour + 24 - startHour;
const durationMinutes = Math.round(durationHours * 60);
const hours = Math.floor(durationMinutes / 60);
const minutes = durationMinutes % 60;
if (hours > 0 && minutes > 0) {
return `${hours} ${hours === 1 ? "hora" : "horas"} y ${minutes} minutos`;
}
if (hours > 0) {
return `${hours} ${hours === 1 ? "hora" : "horas"}`;
}
return `${minutes} minutos`;
}
private validateCancellationTime(aTime: number | undefined, start: string | Date): boolean {
let cancellationTime = this.getCancellationTime(aTime);
@@ -2168,6 +2185,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
companyOwnerId: string;
companyName: string;
emailMessage: string;
emailTemplateId?: string;
emailContext?: Record<string, unknown>;
wapMessage: string;
}): Promise<void> {
const systemSubject = `Turno cancelado en ${data.companyName}`;
@@ -2187,6 +2206,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
message: data.emailMessage,
emailSubject: "TurnosXpress :: Turno cancelado",
emailMessage: data.emailMessage,
emailTemplateId: data.emailTemplateId,
emailContext: data.emailContext,
wapMessage: data.wapMessage,
systemSubject,
systemMessage: data.emailMessage,
@@ -2206,8 +2227,12 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
appointmentStart: Date;
emailMessage: string;
wapMessage: string;
emailTemplateId?: string;
emailContext?: Record<string, unknown>;
reminderEmailMessage: string;
reminderWapMessage: string;
reminderEmailTemplateId?: string;
reminderEmailContext?: Record<string, unknown>;
}): Promise<void> {
const systemSubject = `Turno reservado en ${data.companyName}`;
@@ -2220,6 +2245,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
message: data.emailMessage,
emailSubject: "TurnosXpress :: Alta de turno",
emailMessage: data.emailMessage,
emailTemplateId: data.emailTemplateId,
emailContext: data.emailContext,
wapMessage: data.wapMessage,
systemSubject,
systemMessage: data.emailMessage,
@@ -2277,6 +2304,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
companyName: data.companyName,
appointmentStart: data.appointmentStart,
reminderEmailMessage: reminderEmailContent.message,
reminderEmailTemplateId: reminderEmailContent.emailTemplateId,
reminderEmailContext: reminderEmailContent.emailContext,
reminderWapMessage: reminderWapContent.message,
};
@@ -2301,6 +2330,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
await this.createCreationNotificationJobs({
...notificationJobData,
emailMessage: emailContent.message,
emailTemplateId: emailContent.emailTemplateId,
emailContext: emailContent.emailContext,
wapMessage: wapContent.message,
});
}
@@ -2317,6 +2348,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
appointmentStart: Date;
reminderEmailMessage: string;
reminderWapMessage: string;
reminderEmailTemplateId?: string;
reminderEmailContext?: Record<string, unknown>;
}): Promise<void> {
const reminderSystemSubject = `Recordatorio de turno en ${data.companyName}`;
@@ -2335,6 +2368,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
message: data.reminderEmailMessage,
emailSubject: "TurnosXpress :: Recordatorio",
emailMessage: data.reminderEmailMessage,
emailTemplateId: data.reminderEmailTemplateId,
emailContext: data.reminderEmailContext,
wapMessage: data.reminderWapMessage,
systemSubject: reminderSystemSubject,
systemMessage: data.reminderEmailMessage,
@@ -2511,6 +2546,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
companyOwnerId: String(companyCheck.ownerId),
companyName: companyCheck.name,
emailMessage: emailContent.message,
emailTemplateId: emailContent.emailTemplateId,
emailContext: emailContent.emailContext,
wapMessage: wapContent.message,
});
}
@@ -2705,6 +2742,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
}
let wapMessage = "";
let emailTemplateId: string | undefined;
let emailContext: Record<string, unknown> | undefined;
if (data.type == APPOINTMENT_NOTIFICATION_TYPE.REMINDER) {
if (companyCheck.templateWapNotifId) {
@@ -2748,6 +2787,19 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
wapMessage = wapMessage.replaceAll("<---ORGANIZACION--->", companyCheck.name);
wapMessage = wapMessage.replaceAll("<---BR--->", "\r\n");
emailTemplateId = "6a86ef631898c6948d0f5308";
emailContext = {
username: ClientsManager.getClientFullName(checkClient),
nombre_organizacion: companyCheck.name,
fecha_turno: dayjs(checkAppointment.start).format("DD/MM/YYYY"),
hora_turno: dayjs(checkAppointment.start).format("HH:mm") + "hs.",
nombre_servicio: checkService.name,
nombre_profesional: UsersManager.getUserFullName(checkEmployeeUser),
duracion: this.formatAppointmentDuration(checkAppointment.startHour, checkAppointment.endHour),
precio: formatCurrency(checkAppointment.price),
direccion: CompaniesManager.getCompanyAddress(companyCheck),
};
} else if (data.type == APPOINTMENT_NOTIFICATION_TYPE.CREATION) {
const templateId = data.channel === "email"
? companyCheck.templateEmailAltaId || companyCheck.templateWapAltaId
@@ -2797,6 +2849,19 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
wapMessage = wapMessage.replaceAll("<---ORGANIZACION--->", companyCheck.name);
wapMessage = wapMessage.replaceAll("<---BR--->", "\r\n");
emailTemplateId = "6a86f55e3edd051e8a04c711";
emailContext = {
username: ClientsManager.getClientFullName(checkClient),
nombre_organizacion: companyCheck.name,
fecha_turno: dayjs(checkAppointment.start).format("DD/MM/YYYY"),
hora_turno: dayjs(checkAppointment.start).format("HH:mm") + "hs.",
nombre_servicio: checkService.name,
nombre_profesional: UsersManager.getUserFullName(checkEmployeeUser),
duracion: this.formatAppointmentDuration(checkAppointment.startHour, checkAppointment.endHour),
precio: formatCurrency(checkAppointment.price),
direccion: CompaniesManager.getCompanyAddress(companyCheck),
};
} else if (data.type == APPOINTMENT_NOTIFICATION_TYPE.CANCELLATION || data.type == APPOINTMENT_NOTIFICATION_TYPE.UPDATE) {
const isUpdate = data.type == APPOINTMENT_NOTIFICATION_TYPE.UPDATE;
const templateId = data.channel === "email"
@@ -2852,12 +2917,29 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
wapMessage = wapMessage.replaceAll("<---PRECIO--->", formatCurrency(checkAppointment.price));
wapMessage = wapMessage.replaceAll("<---ORGANIZACION--->", companyCheck.name);
wapMessage = wapMessage.replaceAll("<---BR--->", "\r\n");
emailTemplateId = isUpdate
? "6a86febf6713e0bdd104c7ca"
: "6a86f77d376a7371170b21c5";
emailContext = {
username: ClientsManager.getClientFullName(checkClient),
nombre_organizacion: companyCheck.name,
fecha_turno: dayjs(checkAppointment.start).format("DD/MM/YYYY"),
hora_turno: dayjs(checkAppointment.start).format("HH:mm") + "hs.",
nombre_servicio: checkService.name,
nombre_profesional: UsersManager.getUserFullName(checkEmployeeUser),
duracion: this.formatAppointmentDuration(checkAppointment.startHour, checkAppointment.endHour),
precio: formatCurrency(checkAppointment.price),
direccion: CompaniesManager.getCompanyAddress(companyCheck),
};
}
return {
message: wapMessage,
checkClient: checkClient,
companyCheck: companyCheck,
emailTemplateId,
emailContext,
};
}
@@ -2876,7 +2958,7 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
}
}
const { message, checkClient, companyCheck } = await this.tryToSendNotification({
const { message, checkClient, companyCheck, emailTemplateId, emailContext } = await this.tryToSendNotification({
...data,
channel: "email",
});
@@ -2913,6 +2995,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
email: clientEmail,
subject: subjectEmail,
message: message,
templateId: emailTemplateId,
context: emailContext,
});
}
@@ -2964,6 +3048,8 @@ public async updateAppointment(data: UpdateAppointmentParams): Promise<void> {
message: emailContent.message,
emailSubject: isCreation ? "TurnosXpress :: Alta de turno" : "TurnosXpress :: Recordatorio",
emailMessage: emailContent.message,
emailTemplateId: emailContent.emailTemplateId,
emailContext: emailContent.emailContext,
wapMessage: wapContent.message,
systemSubject,
systemMessage: emailContent.message,
@@ -23,6 +23,8 @@ export interface NotificationJobPayload {
message?: string;
emailSubject?: string;
emailMessage?: string;
emailTemplateId?: string;
emailContext?: Record<string, unknown>;
wapMessage?: string;
systemSubject?: string;
systemMessage?: string;
@@ -7,8 +7,10 @@ import { ApiVoidResult } from "../../Models/Api.VoidResult.type";
interface SendEmailByContentParams {
systemToken: string;
email: string;
subject: string;
message: string;
subject?: string;
message?: string;
templateId?: string;
context?: Record<string, unknown>;
}
interface SendWapByContentParams {
@@ -29,6 +31,8 @@ export class SendEmailByContentController extends Controller {
email: requestBody.email,
subject: requestBody.subject,
message: requestBody.message,
templateId: requestBody.templateId,
context: requestBody.context,
});
this.setStatus(200);