The docs still described the state before the last five commits: the
insurance spec called for a `@Cron` literal and a manual mark-as-sent
mutation, PLAN.md had step 12 as "NOT STARTED", and README's module and
route lists predated seven modules.
- MASS_EMAIL_NOTIFICATIONS.md: new "Send flags", "API surface" and
"Scheduled runs" sections; "Cron (future)" removed — it exists. The
flags table says which flags apply where, and why a debug renewal send
must skip both the RenewalNotice row and `lastSuccessfulAt`.
- INSURANCE_FEATURES_SPEC.md: §1 BUILT note listing the three places the
build diverged from the spec; §1.1 and §1.4 marked superseded in place
rather than deleted, so the reasoning stays readable.
- PLAN.md: step 12 renewal emails DONE with the divergences; status
paragraph rewritten.
- README.md: current module/route lists, plus a "Scheduled jobs" section —
a reader cloning this repo had no way to know the API sends mail on a
timer.
- DEPLOY_AND_MIGRATIONS.md: the cadence lives in app_settings and survives
an image rollback, and the servicios sweep has no multi-replica lock.
- RESUME.md: session record for the whole notificaciones arc.
- RENEWAL_NOTICES.md: pointer that this is the legacy record, not what
shipped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
NOTIFICATION_ADMIN_EMAILS made "add Beto to the summaries" a redeploy —
the wrong unit of work for a list that changes when office staff change.
Adds `app_settings`, a key/value table for the configuration staff must
be able to change without a deploy, and `SettingsService`, which resolves
every key db -> env -> default and reports which of the three a value
came from. That ladder is what makes the move safe: a deployment behaves
exactly as before until somebody saves in the UI, and the screen can say
"this is still coming from the deployment" rather than implying somebody
chose it.
- new ability `setting:manage` (ADMIN) — deliberately above
`notification:send`, since redirecting the audit summaries is how
someone would quietly stop them being read
- GET/PUT /notifications/settings/admin-emails; read is open to any
logged-in user so the UI can display the list, write is gated
- resolved per job, not cached at boot, or we would reintroduce exactly
the restart-to-apply behaviour being removed
- a saved empty list means "nobody" and does NOT fall through to the env,
or clearing the field would keep mailing the people just removed
Credentials stay in env — see the model doc for where the line is drawn.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The stack env is assembled from Gitea repo secrets by the deploy
workflows' `env_data` block — there is no .env file on the host for the
app stack. SES was in neither, so `MailService` came up unconfigured on
every deployment and, with NODE_ENV=production killing the stdout dev
fallback, every notification and renewal aviso failed.
Wire SES_REGION / SES_FROM / SES_FROM_NAME / SES_ACCESS_KEY /
SES_SECRET_KEY / SES_CONFIGURATION_SET / NOTIFICATION_ADMIN_EMAILS
through both galactus and cubex. No `_GALACTUS` suffix: one SES identity
serves every deployment.
Kept out of the required-secrets preflight — mail is not needed to boot,
and failing a deploy over it would be wrong. Preflight warns instead,
since the failure is otherwise invisible until someone clicks "Ejecutar".
Also corrects the comments added in the previous commit, which claimed
these belonged in a host env file rather than in CI secrets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
Replaces the four legacy PHP scripts under email.notifications/send*.php
with a single NestJS module. Four jobs (outstanding payments, payment
confirmations, account-status alerts with day-of-week gates, trust
payment confirmations) share one MailService modelled on StorageService:
env-driven SES client, null fallback in dev with console logging, refuses
to send in production when unconfigured.
Schema adds email_notification_log (every attempt, sent/failed/skipped)
and account_status_history (one row per threshold hit, Job 3). Enums
encode the legacy wire shape so external log scrapers keep parsing
notificationType keys verbatim.
Web adds /notificaciones with four trigger cards, a flags panel, and a
paginated log browser. New notification:send ability gates all four
endpoints at MANAGER, matching the renewal:send trust tier.