feat(policy-ocr): set policyTypeId and insuranceProviderId on confirm
The BACKLOG claimed this was blocked on incomplete `policy_types` rows.
Querying the dev database says otherwise: AUTO (1316 policies) and LICENCIAS
(306) are both live and healthy, so ANA's two faces were never blocked at
all. Three separate things had been conflated.
What the parser now emits is a NAME, not an id -- it is a pure function over
text and must not reach for the database:
ANA AUTOMOBILE -> AUTO
ANA DRIVER'S POLICY -> LICENCIAS
GMX (both documents) -> MULT
`resolveLookups()` turns that into a foreign key at confirm, and does the
same for the carrier off the parser's provider code. It resolves, never
creates: a missing `policy_types` row means a human deleted it, and silently
recreating it would undo that with no record. An explicit `policyTypeId` /
`insuranceProviderId` on the confirm payload always wins.
GMX is MULT rather than INCENDIO because the caratula's own header reads
"Multiple Policy / Home" and the especificación is "PVL Hogar" -- one product,
two artifacts. MULT is the live row carrying 769 of them; INCENDIO is fire-only
and no policy in the book has ever used it.
The parser's provider code is not the carrier's row name, so PROVIDER_ROW_NAME
maps ANA onto "ANA SEGUROS", which is where the office's 738 ANA policies
already are.
--- the actual defect underneath -----------------------------------------
`policies.policyTypeId`, `policies.insuranceProviderId` and
`claims.adjusterId` are all ON DELETE SET NULL, and the lookups screen deleted
unconditionally. So deleting a lookup row returned 200 and silently blanked
the field on every row referencing it -- no error, nothing in the UI. That is
how M_EMPR disappeared and left 5 policies with no ramo, found months later
only by querying.
All three deletes now refuse while the row is in use, naming it and the count
("El tipo de póliza «M_EMPR» está en uso por 5 póliza(s)"). The schema-level
`onDelete: Restrict` the spec once recommended is deliberately not used: a raw
FK error is not something the operator can act on.
`20260815160000_policy_type_repair` cleans up what already happened:
- restores M_EMPR and re-points its 5 policies, scoped to
`policyTypeId IS NULL AND legacySourceTable = 'm_empr'` so it can never
claim a policy blanked for some other reason
- merges the duplicate "ANA" carrier (1 policy) into "ANA SEGUROS" (738).
OCR is about to start assigning the carrier automatically and two rows
would keep splitting the book. Written as joins, not subqueries, so both
statements are no-ops when either row is absent -- a subquery form would
resolve to NULL and blank the carrier off every ANA policy.
- does NOT restore INCENDIO. It is the other row the migration would have
produced, but the legacy INCENDIO table has 1 row that never loaded, so
the type has zero policies and restoring it would only put a dead option
in the type picker.
Verified by running the repair against the real broken dev data inside a
transaction and rolling back: 5 orphans -> 0, ANA/ANA SEGUROS -> one row with
739, and a second run in the same transaction changes nothing. The DDL half
matches `prisma migrate diff` exactly.
186 tests pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+28
-13
@@ -12,8 +12,8 @@ carries the reasoning. Close an item *there* as well as here, or the two drift.
|
||||
**Verified against dev at compile time** (re-run before trusting the numbers):
|
||||
|
||||
```
|
||||
policy_types: AUTO, LICENCIAS, MULT
|
||||
policies NULL policyTypeId: 5
|
||||
policy_types: AUTO, LICENCIAS, MULT (+ M_EMPR after 20260815160000)
|
||||
policies NULL policyTypeId: 5 (0 after 20260815160000)
|
||||
policies pending liquidación: 226
|
||||
customers: 1536
|
||||
last tag: v1.0.6 (2026-08-02 02:06 UTC) — 14 commits, 5 migrations behind HEAD
|
||||
@@ -98,16 +98,31 @@ in the same phone call — (55) 5480-4000.
|
||||
|
||||
## 2. Live data defects — open, and confirmed open today
|
||||
|
||||
### 2.1 `policy_types` is missing `INCENDIO` and `M_EMPR`, and 5 policies are orphaned
|
||||
### 2.1 ~~`policy_types` missing rows + 5 orphaned policies~~ — FIXED 2026-08-15
|
||||
|
||||
`policyTypeId` is `String?` with a plain relation, so Prisma's default is
|
||||
`SetNull`. The spec's recommended `onDelete: Restrict` was **never applied**.
|
||||
Five `m_empr` policies lost their ramo; four of them are pending liquidación
|
||||
and are invisible to every ramo-filtered query — including the pending report
|
||||
§2 is supposed to produce.
|
||||
`SetNull`, and `removePolicyType()` had no in-use guard — deleting a lookup row
|
||||
returned 200 and silently blanked the ramo on every policy using it. That is
|
||||
what happened to `M_EMPR` and its 5 `m_empr` policies.
|
||||
|
||||
Fix alongside the liquidación work (3.1), since it distorts that feature's own
|
||||
report. Source: INSURANCE "Two defects found while verifying this spec".
|
||||
Closed by `20260815160000_policy_type_repair` plus the guard in
|
||||
`policies.service.ts`:
|
||||
|
||||
- `M_EMPR` restored and the 5 policies re-pointed at it, scoped to
|
||||
`policyTypeId IS NULL AND legacySourceTable = 'm_empr'` so it cannot claim a
|
||||
policy blanked for some other reason. Idempotent; verified against dev inside
|
||||
a rolled-back transaction.
|
||||
- **`INCENDIO` deliberately not recreated.** The legacy `INCENDIO` table has
|
||||
1 row and it never loaded, so the type has zero policies — restoring it would
|
||||
only add a dead option to the type picker.
|
||||
- Deleting an in-use policy type, carrier or adjuster now **refuses** with the
|
||||
name and the count. `claims.adjusterId` had the identical `SET NULL` trap and
|
||||
is guarded too. `onDelete: Restrict` at the schema level was not applied —
|
||||
the application guard gives a Spanish message the operator can act on, where
|
||||
a raw FK error would not.
|
||||
- The duplicate `ANA` carrier row (1 policy) was merged into `ANA SEGUROS`
|
||||
(738), since OCR now assigns the carrier automatically and two rows would
|
||||
keep splitting the book.
|
||||
|
||||
### 2.2 ≤41 MULT second settlements were dropped in migration
|
||||
|
||||
@@ -172,10 +187,10 @@ Each of these is a known, deliberate stopping point rather than a bug.
|
||||
all; reading the separate receipt and pairing it to its certificate is what
|
||||
would let `postPremium` stop being a manual tick. A.N.A. prints its premium
|
||||
on the face, so this is a GMX-only gap.
|
||||
- **No `policyTypeId` from OCR.** A.N.A.'s two faces are distinguishable in the
|
||||
parser (automobile vs driver's policy) and the platform has a `PolicyType`
|
||||
discriminator, but confirm never sets one — partly because the `policy_types`
|
||||
rows are themselves incomplete (INSURANCE §live defects).
|
||||
- **No `insuranceProviderId` beyond the two OCR carriers.** Confirm resolves
|
||||
the parser's provider to an `insurance_providers` row by name, so GMX and
|
||||
A.N.A. land correctly; a policy typed in by hand still gets whatever the
|
||||
operator picks.
|
||||
- **No versioning.** A re-issued policy arrives as a new certificate with the
|
||||
same number and confirm updates the existing row. Nothing records that this
|
||||
is the 2027 issue of that policy.
|
||||
|
||||
Reference in New Issue
Block a user