feat(notificaciones): one send log across servicios and pólizas
Renewal avisos left behind only a `RenewalNotice` row, whose sole job is gating: a row with `sentAt` drops the policy off the pending list. It cannot represent a failed send or a customer with no address, so the Pólizas tab had no "Registro de envíos" to show and a sent notice simply vanished from the list. Renewals now write `email_notification_log` — the same table the four bulk jobs write — as `RENEWAL_NOTICE` / `POLICIES`, with rows for failures and no-email skips too. `RenewalNotice` keeps its gating role unchanged; the two are complementary, not redundant. - extend `EmailNotificationType` (+RENEWAL_NOTICE) and `EmailNotificationServicio` (+POLICIES); `level` now carries the aviso generation on renewal rows, so every reader must branch on the type first (`notificationLevelLabel()` is the one place that lives) - backfill emailed notices (`channel = 'EMAIL'`) into the log; MAIL-channel rows are legacy printed letters and are deliberately left out - extract `NotificationLogService`/`NotificationLogModule` as the single writer, so a feature that sends mail records it without pulling the bulk-job pipelines into its module - `GET /notifications/log` and `/stats` take a comma-separated `servicio` list; each tab reads its own slice. This also fixes the "Omitidos" view, which mapped to no filter at all and showed every row - share one `NotificationLogPanel` between both tabs - pass SES_* / NOTIFICATION_ADMIN_EMAILS through the galactus compose, which was missing them entirely — mail is runtime config, not a CI secret, and the prod image sets NODE_ENV=production so a blank config fails loudly instead of falling back to stdout Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+67
@@ -0,0 +1,67 @@
|
||||
-- Fold insurance renewal avisos into the one notification log.
|
||||
--
|
||||
-- Until now `renewal_notices` was the only record a renewal send left
|
||||
-- behind, and its sole job is gating: a row with `sentAt` removes the
|
||||
-- policy from the pending list. It cannot represent a failed send, a
|
||||
-- customer with no email, or a second attempt — so the Pólizas tab of
|
||||
-- /notificaciones had no "Registro de envíos" to show.
|
||||
--
|
||||
-- Extending the two enums lets `RenewalsService` write the same
|
||||
-- `email_notification_log` rows the four bulk jobs write. `renewal_notices`
|
||||
-- keeps its gating role unchanged.
|
||||
|
||||
-- AlterEnum: EmailNotificationType += RENEWAL_NOTICE
|
||||
ALTER TABLE `email_notification_log`
|
||||
MODIFY `notificationType` ENUM('OUTSTANDING_PAYMENT', 'PAYMENT_CONFIRMATION', 'ACCOUNT_STATUS', 'TRUST_PAYMENT_CONFIRMATION', 'RENEWAL_NOTICE') NOT NULL;
|
||||
|
||||
-- AlterEnum: EmailNotificationServicio += POLICIES
|
||||
ALTER TABLE `email_notification_log`
|
||||
MODIFY `servicio` ENUM('CUSTOMERS', 'TRUST', 'POLICIES') NOT NULL;
|
||||
|
||||
-- Backfill: every renewal notice this platform actually emailed.
|
||||
--
|
||||
-- Scope is deliberately `channel = 'EMAIL' AND sentAt IS NOT NULL`. MAIL-
|
||||
-- channel rows are printed letters carried over from the legacy book — they
|
||||
-- were never emails, and inventing log rows for them would misreport the
|
||||
-- send history. Emailed rows carry a real recipient (resolved through the
|
||||
-- policy's customer) and a real timestamp; the only fields we cannot
|
||||
-- recover are the rendered body and the exact subject, so `bodySnapshot`
|
||||
-- stays empty and the subject is reconstructed from the same two templates
|
||||
-- `renderRenewalEmail()` uses (generation 3 = expired wording).
|
||||
--
|
||||
-- Rows whose customer has no email are skipped: `customerEmail` is NOT NULL
|
||||
-- and a blank recipient would be a lie. Their `renewal_notices` row still
|
||||
-- gates the pending list exactly as before.
|
||||
INSERT INTO `email_notification_log` (
|
||||
`id`, `sendDate`, `notificationType`, `level`, `servicio`,
|
||||
`customerId`, `customerName`, `customerEmail`, `subject`,
|
||||
`bodyRequestUrl`, `bodySnapshot`, `debug`,
|
||||
`providerMessageId`, `providerResponse`, `status`, `error`
|
||||
)
|
||||
SELECT
|
||||
UUID(),
|
||||
rn.`sentAt`,
|
||||
'RENEWAL_NOTICE',
|
||||
rn.`generation`,
|
||||
'POLICIES',
|
||||
c.`id`,
|
||||
c.`name`,
|
||||
c.`email`,
|
||||
CASE WHEN rn.`generation` = 3
|
||||
THEN CONCAT('Póliza vencida: ', p.`policyNumber`)
|
||||
ELSE CONCAT('Aviso de renovación: póliza ', p.`policyNumber`)
|
||||
END,
|
||||
NULL,
|
||||
'',
|
||||
FALSE,
|
||||
rn.`providerMessageId`,
|
||||
'backfill:20260802120000',
|
||||
'SENT',
|
||||
NULL
|
||||
FROM `renewal_notices` rn
|
||||
JOIN `policies` p ON p.`id` = rn.`policyId`
|
||||
JOIN `customers` c ON c.`id` = p.`customerId`
|
||||
WHERE rn.`channel` = 'EMAIL'
|
||||
AND rn.`sentAt` IS NOT NULL
|
||||
AND c.`email` IS NOT NULL
|
||||
AND c.`email` <> '';
|
||||
Reference in New Issue
Block a user