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>
150 lines
7.4 KiB
Markdown
150 lines
7.4 KiB
Markdown
# 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 2–3 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.
|