diff --git a/resources/MEGA-CLAVEDERECUPERACION-hdrdevs.txt b/resources/MEGA-CLAVEDERECUPERACION-hdrdevs.txt new file mode 100644 index 0000000..29ad92c --- /dev/null +++ b/resources/MEGA-CLAVEDERECUPERACION-hdrdevs.txt @@ -0,0 +1 @@ +mO9PXuuYR05od8cMVCIXEw \ No newline at end of file diff --git a/server/package-lock.json b/server/package-lock.json index c2f81a9..26839a2 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -6648,38 +6648,6 @@ "url": "https://opencollective.com/mongoose" } }, - "node_modules/mongoose/node_modules/gaxios": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/mongoose/node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "license": "Apache-2.0", - "optional": true, - "peer": true, - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/mongoose/node_modules/mongodb": { "version": "6.10.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", @@ -6731,56 +6699,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/mongoose/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/mongoose/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT", - "optional": true, - "peer": true - }, - "node_modules/mongoose/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause", - "optional": true, - "peer": true - }, - "node_modules/mongoose/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/mpath": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", diff --git a/server/src/Models/Notifications/Notifications.Adapter.donweb.ts b/server/src/Models/Notifications/Notifications.Adapter.donweb.ts index 60eff06..11e7345 100644 --- a/server/src/Models/Notifications/Notifications.Adapter.donweb.ts +++ b/server/src/Models/Notifications/Notifications.Adapter.donweb.ts @@ -3,13 +3,37 @@ import { INotificationsAdapter, NotificationDataEmail } from "./Notifications.In export class NotificationsDonWebAdapter implements INotificationsAdapter { async send(data: NotificationDataEmail): Promise { - const dataEmail = { + if (data.context && data.substitutions) { + throw new Error("No se puede enviar un email con context y substitutions al mismo tiempo."); + } + + if (!data.templateId && !data.message) { + throw new Error("Debe indicarse el contenido del email mediante message o templateId."); + } + + const dataEmail: Record = { from: "norepply@turnosxpress.com.ar", to: data.email, - subject: data.subject, - html: data.message, }; + if (data.templateId) { + dataEmail.templateID = data.templateId; + } else { + dataEmail.html = data.message; + } + + if (data.subject) { + dataEmail.subject = data.subject; + } + + if (data.context) { + dataEmail.context = data.context; + } + + if (data.substitutions) { + dataEmail.substitutions = data.substitutions; + } + const apiConfig = { method: "post", maxBodyLength: Infinity, diff --git a/server/src/Models/Notifications/Notifications.Interface.ts b/server/src/Models/Notifications/Notifications.Interface.ts index 2e42768..08d2bf2 100644 --- a/server/src/Models/Notifications/Notifications.Interface.ts +++ b/server/src/Models/Notifications/Notifications.Interface.ts @@ -6,8 +6,11 @@ export enum NOTIFICATION_TYPES { export type NotificationDataEmail = { email: string; - subject: string; - message: string; + subject?: string; + message?: string; + templateId?: string; + context?: Record; + substitutions?: Record; }; export type NotificationDataWap = { diff --git a/server/src/Models/Users/Users.ts b/server/src/Models/Users/Users.ts index 34a5ea8..5cbfd73 100644 --- a/server/src/Models/Users/Users.ts +++ b/server/src/Models/Users/Users.ts @@ -351,6 +351,28 @@ class UsersManager implements IUsersManager { .substring(0, 6); } + private async sendWelcomeEmail(email: string, firstName?: string): Promise { + try { + await NotificationsManager.sendEmail({ + email: cleanEmail(email), + templateId: "68139f23f3e003139b08b4f0", + context: { + username: firstName || cleanEmail(email), + }, + }); + } catch (e) { + if (axios.isAxiosError(e)) { + console.error("Error enviando email de bienvenida:", { + status: e.response?.status, + data: e.response?.data, + }); + return; + } + + console.error("Error enviando email de bienvenida:", e); + } + } + public async signUp(data: SignUpParams): Promise { // Destructure email and password from the data object const { @@ -406,12 +428,13 @@ class UsersManager implements IUsersManager { const newUser = await this.users.create(newUserData); - const signUpUrl = "https://turnosxpress.com.ar/landing/login/verification"; - await NotificationsManager.sendEmail({ email: cleanEmail(email), - subject: "TurnosXpress :: Bienvenido/a", - message: `Hola ${firstName}, Gracias por registrarte en turnosXpress. Primero debes activar tu cuenta ingresando en ${signUpUrl} . Tu código de verificación es: ${verificationCode}`, + templateId: "6a85dd644ea98ccfcd05bdc7", + context: { + username: firstName || cleanEmail(email), + verificationcode: verificationCode, + }, }); return newUser; @@ -459,8 +482,11 @@ class UsersManager implements IUsersManager { await NotificationsManager.sendEmail({ email: cleanEmail(data.email), - subject: "TurnosXpress :: Recuperación de cuenta", - message: `Hola ${user.firstName}, Te enviamos un código de verificación para recuperar tu cuenta. Tu código de verificación es: ${code}. Ingresa https://turnosxpress.com.ar/landing/recover-account/verify para establecer una nueva clave`, + templateId: "6a85c7ad96475f2825015277", + context: { + username: user.firstName || cleanEmail(data.email), + verificationcode: code, + }, }); return; @@ -515,10 +541,16 @@ class UsersManager implements IUsersManager { throw new Error("El código de verificación es incorrecto"); } + const wasVerificated = Boolean(userTest.verificated); + userTest.verificated = true; await userTest.save(); + if (!wasVerificated) { + await this.sendWelcomeEmail(userTest.email, userTest.firstName); + } + try { const freePlan = await PlansList.plans.findOne({ price: 0 }); if (freePlan) { @@ -724,11 +756,17 @@ class UsersManager implements IUsersManager { }); if (userCheck) { + const wasVerificated = Boolean(userCheck.verificated); + userCheck.external_id = googleUserId; userCheck.external_service = "google"; userCheck.verificated = true; await userCheck.save(); + if (!wasVerificated) { + await this.sendWelcomeEmail(userCheck.email, userCheck.firstName); + } + const subscription = await PlanSubscriptionsList.getSubscriptionByUser({ sessionUser: userCheck.id, }); @@ -770,6 +808,8 @@ class UsersManager implements IUsersManager { throw new Error("Error creating user"); } + await this.sendWelcomeEmail(newUser.email, newUser.firstName); + const subscription = await PlanSubscriptionsList.getSubscriptionByUser({ sessionUser: newUser.id, }); @@ -821,11 +861,17 @@ class UsersManager implements IUsersManager { }); if (userCheck) { + const wasVerificated = Boolean(userCheck.verificated); + userCheck.external_id = googleData.id; userCheck.external_service = "google"; userCheck.verificated = true; await userCheck.save(); + if (!wasVerificated) { + await this.sendWelcomeEmail(userCheck.email, userCheck.firstName); + } + const subscription = await PlanSubscriptionsList.getSubscriptionByUser({ sessionUser: userCheck.id, }); @@ -867,6 +913,8 @@ class UsersManager implements IUsersManager { throw new Error("Error creating user"); } + await this.sendWelcomeEmail(newUser.email, newUser.firstName); + const subscription = await PlanSubscriptionsList.getSubscriptionByUser({ sessionUser: newUser.id, });