Files
rmancinasandClaude Opus 5 6331481f82 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>
2026-08-02 12:43:19 -07:00

150 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Insurance Renewal Notices ("Atlas" reports)
Staff refer to this report in the UI as "the Atlas report", but **Atlas
isn't a report — it's a carrier**: `ATLAS, S.A.` is one of the insurance
companies (`COMP` column) SEGUROS brokers policies for, alongside
`QUALITAS, S.A.` and others. The legacy frontend (`SEGUROS 16.mdb`) never
parameterized carrier or coverage tier in its renewal-notice report — it
cloned the entire report + query chain once per carrier per coverage
variant instead. This doc explains that clone pattern and the underlying
workflow so the new platform can replace ~40 cloned Access objects with
one parameterized feature.
## Why this needed extra tooling
`objects.json`/`LEGACY_DATABASES_OBJECTS.md` (see `migration/catalog_objects.py`)
only capture report *names* — DAO's catalog interface doesn't expose a
report's `RecordSource` or control layout, only the full Access object
model does, and that model refused to load here
(`"The Visual Basic for Applications project in the database is corrupt"`,
a common failure mode for old .mdb files opened in a newer Access build).
The workaround: `Application.SaveAsText(acReport, name, path)` exports a
report's complete design as plain text without touching the VBA project.
The raw (binary-blob-stripped) exports for the ATLAS renewal reports are
committed in [`migration/legacy_report_defs/`](../migration/legacy_report_defs/):
- `AMPL_R_RENEW_X_MES_NEW_ATLAS_13.txt` — Auto/Amplia (full coverage)
- `AMPL_RENEW_X_MES_NEW_ATLAS_2013.txt` — Auto/Amplia, alternate batch
- `RC_RENEW_X_MES_NEW_ATLAS_13.txt` — Auto/RC (liability only)
- `RCR_RENEW_X_MES_NEWATLAS_2013.txt` — Auto/RC, renewal-of-renewal variant
- `LIC_RENEW_X_VENCE_ATLAS_2013.txt` — Driver's-license insurance
(`PrtDevMode`/`PrtMip`/`OleData`/`GUID` binary properties — printer
settings and object GUIDs, no business meaning — were stripped so the
files are readable text instead of multi-hundred-KB hex dumps.)
## The report chain
Each report is bound to a query that layers 23 other queries, filtered to
one carrier, with two typed parameters staff fill in every run:
```
Report: AMPL R RENEW X MES NEW ATLAS 13
RecordSource -> Query: AMPL R RENEW CALC ATLAS 13
FROM [AMPL R CALC VIG], [AMPL R MENS] (in-force calc view + installment schedule)
WHERE COMP = "ATLAS, S.A."
AND DatePart("m",[HASTA]) = [TECLEE MES DE VENCIMIENTO (1 A 12)] -- typed param
AND DatePart("yyyy",[HASTA]) = [TECLEE AÑO DE VENCIMIENTO (1999)] -- typed param
```
```
Report: LIC RENEW X VENCE ATLAS 2013
RecordSource -> Query of the SAME NAME (query and report share a name)
FROM [LIC MENS], LIC INNER JOIN DATGRAL ... INNER JOIN [VIGENT CASA] ...
WHERE DatePart("m",[hasta]) = [TECLEE MES DE VENCIMIENTO 1 A 12]
AND DatePart("yyyy",[hasta]) = [TECLE AÑO VENCIMIENTO (1999)]
AND LIC.COMP = "ATLAS, S.A."
```
Staff pick a line of business, type the expiry month + year, and the
report prints one notice per matching policy for that carrier that month.
On screen the report is captioned **"AVISO DE RENOVACION"** (auto lines)
or **"R E N E W A L N O T I C E"** (license-insurance line). Every page
prints the notice **twice** (identical top-half/bottom-half sections) —
one copy to mail, one for the office file.
## The multi-notice (reminder) workflow
Renewal reminders escalate through **three generations**, each its own
report clone, with a matching `CONTROL ...` companion report (a
send/checklist log):
| Generation | Report suffix | Control/log report |
|---|---|---|
| 1st notice | `RENEW` / (bare) | `CONTROL <LOB> RENEW X MES` |
| 2nd notice | `RENEW2` | `CONTROL <LOB> RENEW2 X MES` (or `X MES` sibling) |
| 3rd notice | `RENEW3` | `CONTROL <LOB> RENEW3 X MES` |
This pattern repeats per line of business: `AMPL`/`AMPL R` (auto full
coverage), `RC`/`RC R` (auto liability), `LIC` (driver's license), `RCR`,
`MF`/`MF2`/`MF3` (home/multi-risk), `MCA2`, `ME`, `INCEN` (fire) — none of
it is visible from the table schema alone, only from the report/query
names (see `docs/LEGACY_DATABASES_OBJECTS.md`, "What the Reports actually
reveal").
## What's hardcoded vs. what's real policy data
The extracted designs show the letter body mixes two very different kinds
of content:
1. **Per-policy data**, pulled live from the query: customer id, policy
number, vehicle (make/model/body/engine), expiry date.
2. **Static label text baked into the report design**, re-typed by hand
every time a batch was cloned for a new rate or carrier — e.g. (from
`AMPL_R_RENEW_X_MES_NEW_ATLAS_13.txt`):
- `"COLLISION DEDUCTIBLE $ 500.00 Dls. THEFT DEDUCTIBLE $ 1000.00 Dls. ..."`
- `"New Renewal annual Premium $ 365.25 Dls."`
- `"Total Annual Premium $ 405.25 Dls"`
- the whole CSL/medical-coverage recommendation and rental-car upsell
paragraphs
None of those dollar figures are formulas — they're literal text, which is
*why* there are so many near-duplicate reports: a new coverage tier or
rate meant cloning the whole report and hand-editing the labels, rather
than changing a parameter.
The underlying **data these figures should come from already exists** on
the source tables and is preserved (unmapped-but-captured) in
`Policy.coveragesJson` after migration — confirmed against
`docs/LEGACY_DATABASES.md`'s table appendix:
| Legacy column | Sanitized `coveragesJson` key | Meaning |
|---|---|---|
| `COBERTURA` | `cobertura` | Coverage days/territory tier (30/40/50/365) |
| `CSL LIMITE` | `csl_limite` | Combined single limit (liability) |
| `GASTOS MEDICO` | `gastos_medico` | Medical coverage amount |
| `SERVICIO ADICIONAL` | `servicio_adicional` (LICENCIAS: `servicio_adiconal`, a source typo) | Add-on service flag |
| `PROPIEDADES` | `propiedades` | Property-damage coverage amount |
| `PERSONAS` | `personas` | Per-person liability amount |
(`Policy.netPremium`/`total`/`currency` are already first-class columns —
see `packages/database/prisma/schema.prisma`.)
**Migration implication:** a rebuilt renewal notice should render these
from data (one parameterized template), not from report design text. See
`RenewalNotice` in `schema.prisma` and the `aviso-renovacion` entry in
`apps/api/src/reports/reports.registry.ts` for the first cut at this.
> **Built 2026-08-01/02.** The three generations above are now
> `RenewalNotice.generation` 1/2/3, mailed by `apps/api/src/renewals/` on an
> operator-editable cadence (default 06:00 daily) and driven from the
> **Pólizas** tab of `/notificaciones`. The `CONTROL … X MES` companion
> reports have no equivalent and need none: every attempt — sent, failed, or
> skipped for a missing address — lands in `email_notification_log`. See
> [`MASS_EMAIL_NOTIFICATIONS.md`](MASS_EMAIL_NOTIFICATIONS.md) and
> [`INSURANCE_FEATURES_SPEC.md`](INSURANCE_FEATURES_SPEC.md) §1. This document
> stays a record of the **legacy** report chain, not of what shipped.
## Caveats
- Only the ATLAS variants were extracted verbatim; the QUALITAS and
"generic" (no-carrier-suffix) clones weren't pulled but are presumed
structurally identical modulo the `COMP` filter and hardcoded figures.
- `coveragesJson` key names above are derived from
`migration/extract.py`'s `sanitize_column_name` (lowercase,
non-alphanumeric → `_`) applied to the *source* column names in
`docs/LEGACY_DATABASES.md`, not verified against a live migrated
database (no staged output was present in this environment). Confirm
against real data before wiring a template to these keys.