diff --git a/PLAN.md b/PLAN.md index d72ffde..c173f2c 100644 --- a/PLAN.md +++ b/PLAN.md @@ -155,7 +155,7 @@ Given the amount of near-duplicate/overlapping data across snapshot tables (mult - **Renewal notification emails — DONE** (2026-08-01, extended 08-02). A sweep that mails the customer 30 days before expiry, 15 days before, and 7 days after, mapping onto `RenewalNotice.generation` 1/2/3 with **no schema change**. Sending is **Amazon SES** (`@aws-sdk/client-sesv2`, mirroring `StorageService`'s optional-client/degrade-don't-crash pattern). The letter body is the *existing* `aviso-renovacion` report; `@@unique([policyId, generation])` is already-in-place idempotency, so a re-run cannot double-send. Volume ≈260 mails/month, and **815 of the 893 policyholders (91%) have an email**. **Three things came out differently from the spec.** (a) The manual mark-as-sent mutation was **dropped on purpose** — a button that marks a notice sent without sending anything lets the list claim a customer was told when they were not. `POST /renewals/send` replaced it: sending from the list *is* the marking, and the report's `enviadas` total becomes real the same way. (b) The send history 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 from [`docs/MASS_EMAIL_NOTIFICATIONS.md`](docs/MASS_EMAIL_NOTIFICATIONS.md). `RenewalNotice` stays *gating* state; the log is *history*. (c) The `@Cron("0 6 * * *")` literal the spec called for lasted one day: both this sweep and the servicios jobs now take their cadence from `NotificationScheduleService`, stored in `app_settings` and reinstalled on save — no redeploy. Defaults preserve the old behaviour (pólizas 06:00 daily, servicios off). **Both halves live on one screen.** `/notificaciones` has Servicios and Pólizas tabs over the one log; `/renovaciones` is an alias onto the Pólizas tab. The send flags (`debug` in particular) sit in the shell above the tabs and govern both — before that there was no way to test a renewal aviso without mailing a real customer. A debug send diverts the mail, skips the `RenewalNotice` upsert **and** does not advance the sweep's `lastSuccessfulAt`; all three are needed together, or a test run silently narrows tomorrow's window and drops the letters it only pretended to send. - **Still blocked in production:** the `SES_*` variables are wired through the deploy workflow but unset in Gitea, so a production send fails loudly rather than going out. + **Production status:** the `SES_*` Gitea secrets were created 2026-08-02, clearing the last blocker — but the feature has not shipped yet (master is well past the newest tag) and nothing has confirmed that `SES_FROM` is a verified SES identity or that the account is out of the sandbox. Run the first sweep with `debug` on. See [`docs/BACKLOG.md`](docs/BACKLOG.md) §0. - **Liquidación batch workflow** — ~70% already built (`liquidated`/`liquidationNumber`/`liquidationDate` are wired through DTOs, list filter, stats, form and detail page); only the *batch* print-and-mark step is missing, against a live pending set of 226 policies. Adds a ramo-parameterized pending report plus `POST /policies/liquidate-batch` under a new MANAGER `policy:liquidate` ability. Parameterized by ramo, not MULT-only — legacy `TABLA LIQUIDA MF` served `MULT`, `INCENDIO` and `M EMPR` alike. - **Certificate / "Solicitud Atlas"** — renders from the same `format: "letter"` machinery `aviso-renovacion` uses, then reaches customers as an extension of the step-8/9 replication (PDF generated here, pushed to MinIO, pointer replicated), **not** as a new public surface in this repo. Half-blocked: "Solicitud" has zero referent in the legacy system and normally means an *application form*, a different artifact from a certificate. - **Carrier API integration (ANA Seguros + GMX)** — shape only (`CarrierConnector` + an import-review queue rather than direct `Policy` writes, matching how step 11's OCR results are routed). Carrier research done 2026-07-27: **the two carriers are one company** — both belong to **Grupo Valore** (ANA writes autos, GMX writes daños, which is exactly this database's `AUTO`/`LICENCIAS` vs `MULT`/`INCENDIO`/`M_EMPR` split), so it is one commercial relationship, not two. **ANA has a real live SOAP service** (`server.anaseguros.com.mx/ananetws/service.asmx`, ASP.NET `.asmx`) with a published operation list — catalogs, `CalculaValor`/`CalculaMSI`, `ValidaSerie`, `RecuperaCotizacion`, `Transaccion`. **GMX publishes no machine interface at all**, only human agent portals. ⚠️ **Critical mismatch:** every ANA operation serves *new-business quoting/issuance*, not "list the policies where I am agent of record" — so if the ask is inbound portfolio sync, no evidence exists that either carrier sells it. Blocked on one phone call to Grupo Valore ((55) 5480-4000) for credentials + a direction answer, not on further research. ("GDMX" in the meeting notes was a typo for `GMX` — confirmed 2026-07-27.) diff --git a/RESUME.md b/RESUME.md index 3a5333f..2bc788c 100644 --- a/RESUME.md +++ b/RESUME.md @@ -685,9 +685,13 @@ Implementation notes worth keeping: - Wire shapes of the four jobs are byte-for-byte the legacy PHP responses, quirks included (Job 1 reports `result`, not `request`). -**Open:** `SES_*` is wired through the deploy workflow but **unset in Gitea**, -so production sends fail loudly rather than going out. The 78 policyholders -with no email are logged as `SKIPPED_NO_EMAIL` but there is still no printable -worklist for them, and the notice body is English-only (`Customer` carries no -language preference) — the same three questions §1 of the insurance spec -opened. +**Open:** the `SES_*` Gitea secrets were created 2026-08-02, so the feature is +no longer blocked — but it has not shipped (master is well past the newest tag) +and two things nobody has checked decide whether mail leaves the building: +`SES_FROM` must be a verified identity in `SES_REGION`, and the AWS account +must be out of the SES sandbox, which otherwise restricts delivery to verified +recipients and would fail a real sweep while looking correctly configured. Run +the first sweep with `debug` on. Still open beyond that: the 78 policyholders +with no email are logged as `SKIPPED_NO_EMAIL` but have no printable worklist, +and the notice body is English-only (`Customer` carries no language +preference). diff --git a/docs/BACKLOG.md b/docs/BACKLOG.md index 4bb99e5..7b97c5e 100644 --- a/docs/BACKLOG.md +++ b/docs/BACKLOG.md @@ -38,21 +38,33 @@ Plus three backup-pipeline fixes that have never reached prod (`860d483`, `567b033`, `898cf48` — the last prod run went green through the whole chain and died on the final step wanting `deploy/.env.prod`). -> ### ⚠️ `SES_*` is unset in Gitea, and the renewal sweep is on by default +> ### `SES_*` secrets created in Gitea 2026-08-02 — unblocked, unverified > -> The variables are wired through both deploy workflows and the app stack, but -> the secrets were never set. The production image runs `NODE_ENV=production`, -> which disables the stdout dev fallback, so every send fails and logs -> `FAILED`. +> The variables were wired through both deploy workflows and the app stack but +> had never been set. **They now exist.** What that clears: the production +> image runs `NODE_ENV=production`, which disables the stdout dev fallback, so +> a blank config made every send fail and log `FAILED` — and the pólizas sweep +> defaults to **enabled, 06:00 America/Tijuana**, so the failure would have +> repeated nightly. > -> The pólizas sweep defaults to **enabled, 06:00 America/Tijuana**. Deploying -> current master with SES still blank means a sweep that fails every single -> night, silently except for the log. +> **Not yet confirmed, and the first deploy is what confirms it:** > -> **Before cutting `v1.0.7`:** either set the Gitea secrets, or disable the -> pólizas schedule at `/notificaciones` → "Programación de envíos" first. -> See [`MASS_EMAIL_NOTIFICATIONS.md`](MASS_EMAIL_NOTIFICATIONS.md) and -> [`DEPLOY_AND_MIGRATIONS.md`](DEPLOY_AND_MIGRATIONS.md). +> 1. **Names match.** The preflight checks `SES_REGION`, `SES_FROM`, +> `SES_ACCESS_KEY`, `SES_SECRET_KEY` and warns by name if any is blank — +> read that warning on the next run. No `_GALACTUS` suffix on any of them; +> one SES identity serves every deployment. +> 2. **`SES_FROM` is a verified identity in `SES_REGION`.** An unverified +> sender is rejected per-send, which looks identical to a missing config in +> the log. +> 3. **The AWS account is out of the SES sandbox.** This is the one that would +> hurt: in sandbox, SES only delivers to *verified* recipients, so a renewal +> sweep across 815 policyholders would fail almost every send while the +> config looks entirely correct. Check before letting a real sweep run. +> +> Until 2 and 3 are confirmed, run the first sweep with `debug` on — it diverts +> every recipient to the override inbox, and on the pólizas side it also leaves +> the avisos pending, so nothing is consumed by a failed test. See +> [`MASS_EMAIL_NOTIFICATIONS.md`](MASS_EMAIL_NOTIFICATIONS.md) "Send flags". Also outstanding on the deploy path: every pre-existing database still needs its one-time `prisma migrate resolve --applied 0000_init`. diff --git a/docs/DEPLOY_AND_MIGRATIONS.md b/docs/DEPLOY_AND_MIGRATIONS.md index fe0be4b..0bf4031 100644 --- a/docs/DEPLOY_AND_MIGRATIONS.md +++ b/docs/DEPLOY_AND_MIGRATIONS.md @@ -335,5 +335,10 @@ backup does them (see `deploy/scripts/pre-migrate-backup.mjs`): which has no DB lock (the pólizas one does). Keep it single-replica. - `SES_*` is optional to deploy — the preflight only warns — but the production image sets `NODE_ENV=production`, which disables the stdout dev fallback. A - blank SES config therefore makes every send fail and log `FAILED`. As of - 2026-08-02 these secrets are still unset in Gitea. + blank SES config therefore makes every send fail and log `FAILED`. The + secrets were created 2026-08-02; the preflight warning on the next run is + what confirms the names are right. Two things it cannot check: that + `SES_FROM` is a **verified identity** in `SES_REGION`, and that the account + is **out of the SES sandbox** (in sandbox, delivery is restricted to verified + recipients, which would fail a real sweep while looking correctly + configured). diff --git a/docs/INSURANCE_FEATURES_SPEC.md b/docs/INSURANCE_FEATURES_SPEC.md index 0c49d8e..f624973 100644 --- a/docs/INSURANCE_FEATURES_SPEC.md +++ b/docs/INSURANCE_FEATURES_SPEC.md @@ -797,10 +797,13 @@ No collision with the abilities proposed in `RECEIPT_CAPTURE_SPEC.md` ## Open questions to take back to Jorge (collected) **§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. **Still - unanswered in production**: the `SES_*` variables are wired through the - deploy workflow but unset in Gitea, so production sends fail loudly. +- ~~Which SES region + verified identity/configuration set, and whether to + reuse existing IAM credentials or create a scoped `ses:SendEmail` user.~~ + **Answered in practice 2026-08-02** — the Gitea secrets were created. Two + things the deploy preflight cannot verify and that decide whether mail + actually goes out: `SES_FROM` must be a verified identity in `SES_REGION`, + and the account must be out of the SES sandbox (which restricts delivery to + verified recipients). See [`BACKLOG.md`](BACKLOG.md) §0. - The 78 policyholders with no email: skip silently, or produce a print worklist? Currently they are **logged as `SKIPPED_NO_EMAIL`** in `email_notification_log` — visible in "Registro de envíos", but not yet a