docs: record notificaciones as built, flags global, schedules editable
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>
This commit is contained in:
@@ -199,6 +199,35 @@ recycling backfill that consulted `UTILSEG` would merge unrelated people.
|
||||
|
||||
## 1. Renewal notification emails
|
||||
|
||||
> **BUILT — 2026-08-01, extended 2026-08-02.** `apps/api/src/renewals/`
|
||||
> (sweep, `sendOne`, the `scheduled_job_states` lock) plus
|
||||
> `apps/api/src/mail/` (SES). Web: the **Pólizas** tab of `/notificaciones`;
|
||||
> `/renovaciones` is an alias that lands on it. Ability `renewal:send`
|
||||
> (MANAGER), as specced.
|
||||
>
|
||||
> Three things in the sections below were **superseded**, each noted inline:
|
||||
>
|
||||
> - **§1.1** — the `@Cron("0 6 * * *")` literal is gone. Both this sweep and
|
||||
> the servicios jobs take their cadence from `NotificationScheduleService`,
|
||||
> which stores it in `app_settings` and reinstalls the job on save. The
|
||||
> default is still 06:00 daily, so behaviour is unchanged until an operator
|
||||
> edits it. See [`MASS_EMAIL_NOTIFICATIONS.md`](MASS_EMAIL_NOTIFICATIONS.md),
|
||||
> "Scheduled runs".
|
||||
> - **§1.4 manual mark-as-sent — dropped, deliberately.** Sending from the
|
||||
> list is what marks a notice sent; there is no way to claim a letter went
|
||||
> out when no mail was sent. `RenewalNoticeChannel.MAIL` still exists for a
|
||||
> future paper path, but nothing writes it.
|
||||
> - **The send log is not renewal-specific.** Every attempt — including the
|
||||
> failures and no-email skips a `RenewalNotice` row cannot represent — also
|
||||
> writes `email_notification_log` as `RENEWAL_NOTICE` / `POLICIES`, shared
|
||||
> with the four bulk jobs. `RenewalNotice` stays the *gating* state; the log
|
||||
> is *history*.
|
||||
>
|
||||
> Also added 2026-08-02: the platform-wide `debug` flag reaches this path. A
|
||||
> debug send diverts the mail, skips the `RenewalNotice` upsert **and** does
|
||||
> not advance `lastSuccessfulAt` — see that doc's "Send flags" for why all
|
||||
> three are required together.
|
||||
|
||||
### What Jorge asked for
|
||||
|
||||
Automatic notice to the customer at **30 days before expiry, 15 days before,
|
||||
@@ -223,6 +252,13 @@ and 7 days after** — replacing the manual monthly run of the legacy
|
||||
|
||||
### 1.1 The scheduler
|
||||
|
||||
> **Superseded — the cadence is operator-editable, not a literal.**
|
||||
> `RenewalsService` registers its handler with `NotificationScheduleService`
|
||||
> in `onModuleInit`; that service compiles the stored
|
||||
> `{hour, minute, weekdays}` to a cron expression and installs it in
|
||||
> `SchedulerRegistry`. Default `0 6 * * *` / `America/Tijuana`, i.e. exactly
|
||||
> what the literal below did. The rest of this section still holds.
|
||||
|
||||
Add `@nestjs/schedule`. One `@Cron` job, daily, early morning local time.
|
||||
|
||||
```
|
||||
@@ -302,6 +338,12 @@ traced back to the notice that caused it. (`notes` stays free-text for staff.)
|
||||
|
||||
### 1.4 Manual mark-as-sent
|
||||
|
||||
> **Not built, and deliberately so.** A button that marks a notice sent
|
||||
> without sending anything is a button that lets the list claim a customer
|
||||
> was told when they were not — the exact failure the log exists to make
|
||||
> visible. `POST /renewals/send` replaced it: sending *is* the marking.
|
||||
> Revisit only when a real paper-mail workflow exists to record.
|
||||
|
||||
The `aviso-renovacion` doc comment (`reports.registry.ts:617-621`) already
|
||||
anticipates this: staff who *mail* a paper notice need to record it.
|
||||
`RenewalNoticeChannel` (`MAIL` | `EMAIL`) exists for exactly this distinction.
|
||||
@@ -321,12 +363,19 @@ customer to stop the mail.
|
||||
|
||||
### API surface
|
||||
|
||||
As built (the `/policies/:id/renewal-notices` mark-as-sent mutation was
|
||||
dropped — see §1.4):
|
||||
|
||||
| Method | Route | Ability |
|
||||
|---|---|---|
|
||||
| `POST` | `/policies/:id/renewal-notices` | `renewal:send` |
|
||||
| `POST` | `/renewals/sweep` (manual trigger of the cron body) | `renewal:send` |
|
||||
| `POST` | `/renewals/sweep` (manual trigger of the scheduled body; body `{ debug? }`) | `renewal:send` |
|
||||
| `POST` | `/renewals/send` (one notice; body `{ policyId, generation, debug? }`) | `renewal:send` |
|
||||
| `GET` | `/renewals/pending?days=` (what the next sweep would send) | read (AuthenticatedGuard) |
|
||||
|
||||
The cadence itself is edited through the notifications module
|
||||
(`GET`/`PUT /notifications/settings/schedule[/:kind]`, `setting:manage`),
|
||||
because one editor covers both sweeps.
|
||||
|
||||
### Abilities (new)
|
||||
|
||||
| Ability | Min role | Notes |
|
||||
@@ -706,9 +755,8 @@ equivalent is `InsuranceProvider`, which today holds only a name.
|
||||
|
||||
## Build sequencing
|
||||
|
||||
1. **§1 renewal emails** — highest value, schema already ready, no blocker
|
||||
beyond the SES sending account. ≈260 mails/month against a 91%-reachable
|
||||
policyholder base.
|
||||
1. ~~**§1 renewal emails**~~ — **DONE 2026-08-01/02.** See §1's BUILT note.
|
||||
≈260 mails/month against a 91%-reachable policyholder base.
|
||||
2. **§2 liquidación batch** — small, builds on fields already wired. Do the two
|
||||
defect fixes (missing `policy_types` rows + FK `ON DELETE RESTRICT`) as part
|
||||
of it, since both distort its own report.
|
||||
@@ -739,11 +787,15 @@ No collision with the abilities proposed in `RECEIPT_CAPTURE_SPEC.md`
|
||||
|
||||
## Open questions to take back to Jorge (collected)
|
||||
|
||||
**§1 — renewal emails**
|
||||
**§1 — renewal emails** (feature built; these three are still open)
|
||||
- Which SES region + verified identity/configuration set, and whether to reuse
|
||||
existing IAM credentials or create a scoped `ses:SendEmail` user.
|
||||
existing IAM credentials or create a scoped `ses:SendEmail` user. **Still
|
||||
unanswered in production**: the `SES_*` variables are wired through the
|
||||
deploy workflow but unset in Gitea, so production sends fail loudly.
|
||||
- The 78 policyholders with no email: skip silently, or produce a print
|
||||
worklist? (Recommend the worklist.)
|
||||
worklist? Currently they are **logged as `SKIPPED_NO_EMAIL`** in
|
||||
`email_notification_log` — visible in "Registro de envíos", but not yet a
|
||||
printable worklist. (Recommend the worklist.)
|
||||
- Spanish or English notice body?
|
||||
|
||||
**§2 — liquidación**
|
||||
|
||||
Reference in New Issue
Block a user