services/api/src/modules/notifications/notification.templates.js
Overview
Este módulo centraliza la construcción de correos transaccionales de notificaciones. Recibe un eventType y un payload, y devuelve un objeto con subject, html y text. notification.service.js lo consume antes de resolver al destinatario y enviar el mensaje al proveedor de correo.
La plantilla compartida usa el branding de Somos Plano, un encabezado compacto y un footer de seguridad. También separa el asset de marca de las URLs de acción, para no mezclar env.emailAssetsUrl con env.appUrl.
[!NOTE] Este archivo no envía correos. Solo construye contenido y valida datos mínimos por evento.
How it works
flowchart TD
Entry[buildNotificationEmail] -->|ACCOUNT_DELETION_REQUESTED| Deletion[deletionRequestedTemplate]
Entry -->|ACCOUNT_RESTORATION_LINK_RESENT| Resend[restorationLinkResentTemplate]
Entry -->|ACCOUNT_RESTORED| Restored[accountRestoredTemplate]
Entry -->|ACCOUNT_ANONYMIZED| Anonymized[accountAnonymizedTemplate]
Entry -->|ACCOUNT_PURGE_WARNING| PurgeWarning[accountPurgeWarningTemplate]
Entry -->|ACCOUNT_PURGED| Purged[accountPurgedTemplate]
Entry -->|PASSWORD_RESET_REQUESTED| PasswordReset[passwordResetRequestedTemplate]
Entry -->|PASSWORD_CREATED| PasswordCreated[passwordCreatedTemplate]
Entry -->|EMAIL_VERIFICATION_REQUESTED| EmailVerification[emailVerificationRequestedTemplate]
Entry -->|WELCOME| Welcome[welcomeTemplate]
Entry -->|LISTING_PERFORMANCE_DIGEST| Digest[listingPerformanceDigestTemplate]
Deletion --> Layout[emailLayout]
Resend --> Layout
Restored --> Layout
Anonymized --> Layout
PurgeWarning --> Layout
Purged --> Layout
PasswordReset --> Layout
PasswordCreated --> Layout
EmailVerification --> Layout
Welcome --> Layout
Digest --> Layout
Layout --> Header[buildBrandHeaderHtml]
Layout --> Logo[resolveEmailLogoUrl]
Layout --> Escape[escapeHtml]
Layout --> Date[formatDate]
Digest --> Metrics[metricsList]
Digest --> Sentiment[renderSentimentBlock]
Digest --> ListingBlock[listingBlock]
Digest --> Greeting[greetingLine]
buildNotificationEmail actúa como fábrica principal. Si el evento tiene plantilla, arma el correo y aplica validaciones previas. Si el evento no existe aquí, devuelve null. El layout común luego normaliza la marca, el footer y las acciones.
La URL del logo se resuelve de forma explícita. Si EMAIL_ASSETS_URL está vacío o malformado, el encabezado omite la imagen. Si la URL contiene un subpath, ese subpath se preserva exactamente.
Key components
{
"title": "buildNotificationEmail",
"content": "F\u00e1brica principal del m\u00f3dulo. Selecciona la plantilla adecuada, valida URLs obligatorias y devuelve el contenido listo para env\u00edo o null cuando el eventType no tiene plantilla.",
"type": "function",
"filePath": "services/api/src/modules/notifications/notification.templates.js",
"badges": [
"public"
]
}
{
"title": "emailLayout",
"content": "Construye el HTML y el texto plano compartidos por todas las plantillas. Inserta el encabezado, el cuerpo, las acciones y el footer de seguridad.",
"type": "function",
"filePath": "services/api/src/modules/notifications/notification.templates.js",
"badges": [
"public"
]
}
{
"title": "resolveEmailLogoUrl",
"content": "Resuelve la URL absoluta del logo de email a partir de env.emailAssetsUrl. Devuelve cadena vac\u00eda cuando la configuraci\u00f3n no es usable.",
"type": "function",
"filePath": "services/api/src/modules/notifications/notification.templates.js",
"badges": [
"public"
]
}
Data & fields
Salida común del layout
El layout compartido devuelve siempre la misma forma. El HTML y el texto plano salen juntos, para que el correo tenga versión visual y alternativa.
[
{
"name": "subject",
"type": "string",
"required": true,
"description": "Asunto final del correo."
},
{
"name": "html",
"type": "string",
"required": true,
"description": "Contenido HTML completo del mensaje."
},
{
"name": "text",
"type": "string",
"required": true,
"description": "Versión en texto plano del mismo mensaje."
}
]
Contrato compartido de emailLayout
Este contrato define la forma del contenido que cada plantilla inyecta en el layout. Las acciones se filtran por url, así que un botón sin enlace no se renderiza.
[
{
"name": "heading",
"type": "string",
"required": true,
"description": "Título principal del correo."
},
{
"name": "bodyHtml",
"type": "string",
"required": true,
"description": "Fragmento HTML que se inserta bajo el título."
},
{
"name": "bodyText",
"type": "string",
"required": true,
"description": "Texto plano equivalente al cuerpo HTML."
},
{
"name": "actions",
"type": "array<object>",
"description": "Botones de acción opcionales mostrados al centro.",
"children": [
{
"name": "label",
"type": "string",
"required": true,
"description": "Texto visible del botón y del fallback textual."
},
{
"name": "url",
"type": "string",
"required": true,
"description": "Destino del botón."
}
]
}
]
Campos usados por buildNotificationEmail
El payload cambia según el evento. El factory valida algunos campos y deja otros como opcionales, porque el propio layout ignora acciones sin URL.
[
{
"name": "eventType",
"type": "string",
"required": true,
"description": "Clave del evento de notificación."
},
{
"name": "payload",
"type": "object",
"description": "Datos de la plantilla. Los campos obligatorios dependen de eventType.",
"children": [
{
"name": "restorationUrl",
"type": "string",
"description": "Obligatoria para ACCOUNT_DELETION_REQUESTED y ACCOUNT_RESTORATION_LINK_RESENT."
},
{
"name": "resetUrl",
"type": "string",
"description": "Obligatoria para PASSWORD_RESET_REQUESTED."
},
{
"name": "verificationUrl",
"type": "string",
"description": "Obligatoria para EMAIL_VERIFICATION_REQUESTED."
},
{
"name": "accountRole",
"type": "string",
"description": "Usada por WELCOME. Las variantes actuales son creator y seeker."
},
{
"name": "displayName",
"type": "string",
"description": "Nombre visible usado en LISTING_PERFORMANCE_DIGEST."
},
{
"name": "listingsUrl",
"type": "string",
"description": "Obligatoria para LISTING_PERFORMANCE_DIGEST."
},
{
"name": "activityUrl",
"type": "string",
"description": "Obligatoria para LISTING_PERFORMANCE_DIGEST."
},
{
"name": "leadsUrl",
"type": "string",
"description": "Obligatoria para LISTING_PERFORMANCE_DIGEST."
},
{
"name": "unsubscribeUrl",
"type": "string",
"description": "Enlace opcional para dejar de recibir Tu Plano Digest."
},
{
"name": "listings",
"type": "array<object>",
"description": "Bloques por anuncio usados por LISTING_PERFORMANCE_DIGEST.",
"children": [
{
"name": "title",
"type": "string",
"description": "Título del anuncio."
},
{
"name": "periodTotals",
"type": "object",
"description": "Métricas del anuncio en el periodo."
}
]
},
{
"name": "moreListingsCount",
"type": "number",
"description": "Cantidad de anuncios adicionales resumidos en el digest."
}
]
}
]
Campos internos de apoyo
Estos valores no forman parte del contrato público, pero sí explican el comportamiento del módulo.
EMAIL_LOGO_PATH[badge:read-only]string— Ruta relativa fija del isotipo de email.BRAND_LOGO_SIZE[badge:read-only]number— Tamaño cuadrado del logo en el header.EXPORT_ACTION_LABEL[badge:read-only]string— Etiqueta reutilizada para la acción de exportación.WELCOME_ROLE_COPY[badge:read-only]object— Copia específica paracreatoryseeker.SENTIMENT_LABELS[badge:read-only]object— Traducción visible depositive,neutral,negativeynone.
Behaviors
Helpers compartidos
resolveEmailLogoUrl— Verificaenv.emailAssetsUrly construye la URL final del logo. Devuelve''si el valor no sirve.buildBrandHeaderHtml— Renderiza el encabezado de marca con<table role="presentation">. Usa logo eSOMOS PLANOcomo texto real.escapeHtml— Escapa&,<,>,", y'en texto y URLs insertadas en HTML.formatDate— Formatea fechas válidas comoes-MXconAmerica/Mexico_City. Devuelve''para fechas inválidas.emailLayout— Construye el documento HTML completo y su equivalente en texto plano. Añade footer y acciones filtradas.metricsList— Convierte pares etiqueta-valor en una lista HTML y otra textual.greetingLine— UsadisplayNamerecortado. Si está vacío, devuelve soloHola.formatSentimentPercentages— Calcula porcentajes enteros a partir de positivos, neutrales y negativos.renderSentimentBlock— Muestra un mensaje vacío cuando no hay comentarios. No inventa porcentajes.listingBlock— Agrupa métricas y sentimiento de un anuncio individual.
Plantillas de cuenta y seguridad
deletionRequestedTemplate— Notifica la solicitud de eliminación. Incluye restauración y exportación.restorationLinkResentTemplate— Explica que el enlace anterior ya no sirve. Solo conserva el nuevo enlace.accountRestoredTemplate— Confirma que la cuenta volvió a estar activa.accountAnonymizedTemplate— Explica la anonimización pública tras 30 días. Mantiene la exportación.accountPurgeWarningTemplate— Advierte sobre la eliminación definitiva en 7 días.accountPurgedTemplate— Marca el cierre definitivo del proceso de retención.passwordResetRequestedTemplate— Entrega el enlace para restablecer la contraseña.passwordCreatedTemplate— Informa que ya existe una contraseña para la cuenta.emailVerificationRequestedTemplate— Pide verificar el correo dentro de 24 horas.welcomeTemplate— Confirma la verificación y añade copy segúnaccountRole. Usaenv.appUrlcomo destino final.
[!IMPORTANT]
buildNotificationEmailexige URLs específicas en varios eventos. Si faltan, lanzaINVALID_NOTIFICATION_PAYLOAD.
Digest de actividad
listingPerformanceDigestTemplate— Construye el correoTu Plano Digest · Últimos 15 días.- Usa
hasPeriodActivitypara decidir entre resumen de actividad o recomendaciones. - Usa
listings,totalsyperiodTotalspara mostrar métricas agregadas. - Añade
unsubscribeUrlsolo cuando existe. - Incluye botones hacia
listingsUrl,activityUrlyleadsUrl.
Gotchas & edge cases
[!WARNING] Si
EMAIL_ASSETS_URLestá vacío, el correo sigue construyéndose. Solo desaparece el<img>del logo.
[!WARNING] Si
EMAIL_ASSETS_URLestá malformado, el módulo registraINVALID_EMAIL_ASSETS_URLy omite la imagen. No falla en silencio.
[!WARNING] La URL del logo conserva subpaths completos. Un join incorrecto perdería la parte final del origen.
[!CAUTION]
buildNotificationEmaildevuelvenullparaeventTypeno soportados. No intenta fabricar un correo genérico.
[!CAUTION] El header no usa
flexnigrid. Usa tabla y celdas alineadas al centro. Ese patrón está fijo en la plantilla.
[!NOTE] El correo siempre incluye footer de seguridad. Ese texto vive en
emailLayout, no en cada plantilla.
Related
services/api/src/modules/notifications/notification.service.js— ConsumebuildNotificationEmail, resuelve destinatario y llama asendNotificationEmail.services/api/src/modules/notifications/notification.client.js— Entrega el mensaje al proveedor configurado.services/api/src/modules/notifications/notification.repository.js— Persiste el estado del outbox antes y después del envío.services/api/src/modules/notifications/notification.events.js— Registra eventos de dominio y delega la entrega.services/api/src/config/env.js— Proveeenv.emailAssetsUrlyenv.appUrl, que este módulo usa para branding y enlaces.