diff --git a/apps/api/src/policy-ocr/parsers/policy-parser.spec.ts b/apps/api/src/policy-ocr/parsers/policy-parser.spec.ts index ada16a2..361fa58 100644 --- a/apps/api/src/policy-ocr/parsers/policy-parser.spec.ts +++ b/apps/api/src/policy-ocr/parsers/policy-parser.spec.ts @@ -310,7 +310,13 @@ describe("parsePolicy / GMX especificación (PVL Hogar)", () => { expect(p.agentName).toBeNull(); expect(p.netPremium).toBeNull(); expect(p.total).toBeNull(); - expect(p.notes.join(" ")).toMatch(/no trae vigencia, agente ni prima/i); + // The note must tell the reviewer to key them in — those three are + // captured by hand on this layout — and must say what silently breaks if + // the vigencia is left empty. + const notes = p.notes.join(" "); + expect(notes).toMatch(/no trae vigencia, agente ni prima/i); + expect(notes).toMatch(/captúrelos a mano/i); + expect(notes).toMatch(/avisos de renovación/i); }); it("takes the currency from the printed limits, not from the M.N. sublimits", () => { diff --git a/apps/api/src/policy-ocr/parsers/policy-parser.ts b/apps/api/src/policy-ocr/parsers/policy-parser.ts index 17b6055..1109303 100644 --- a/apps/api/src/policy-ocr/parsers/policy-parser.ts +++ b/apps/api/src/policy-ocr/parsers/policy-parser.ts @@ -603,9 +603,15 @@ function parseGmxEspecificacion(page: OcrPage): ParsedPolicy { const { coverages, currency } = parseEspecificacionCoverages(lines, notes); // These are absent by design on this document, not failures to read. Say so - // explicitly, or the reviewer reads four empty fields as a broken parse. + // explicitly, or the reviewer reads three empty fields as a broken parse. + // + // The renewal consequence is spelled out rather than left implied: a Policy + // confirmed with a null `policyTo` never matches the renewals window query + // (`renewals.service.ts`, `policyTo: { gte, lte }`), so it drops out of the + // renewal notices silently and stays out. Nothing downstream errors. notes.push( - "la especificación PVL no trae vigencia, agente ni prima; esos datos están en la carátula de la póliza", + "la especificación PVL no trae vigencia, agente ni prima; captúrelos a mano " + + "(sin vigencia la póliza no entra en los avisos de renovación)", ); return { diff --git a/docs/POLICY_OCR.md b/docs/POLICY_OCR.md index 611e42b..e65122e 100644 --- a/docs/POLICY_OCR.md +++ b/docs/POLICY_OCR.md @@ -171,8 +171,22 @@ Three things about the especificación are worth knowing before touching it: above it (`efectuados.`, `Y CADA PÉRDIDA.`), and without it coverages get named after the last word of the preceding prose. - **Vigencia, agente and prima are absent by design**, not unread. The parser - says so in a note, so a reviewer seeing four empty fields does not read it - as a broken parse. The caratula is the document that carries them. + says so in a note, so a reviewer seeing three empty fields does not read it + as a broken parse. + + **These three are keyed in by hand** — confirmed 2026-08-14 with Luz, who + handles GMX policies at the office. The review screen already has editable + inputs for all three, and `postPremium` enables off the *typed* premium, so + a hand-entered prima posts to the ledger exactly like a parsed one. No code + change was needed to support this; it is a process decision, recorded here + because the parser's own note now instructs the reviewer accordingly. + + > **A blank vigencia is silently permanent.** `Policy.policyTo` is nullable + > and the renewals window query filters `policyTo: { gte, lte }` + > (`renewals.service.ts`), so a policy confirmed without one **never matches + > and never gets a renewal notice** — no error, no warning, and nothing + > later notices. This is why the parser's note names the consequence instead + > of just listing the missing fields. An excluded catastrophic risk is recorded as excluded **in the risk label** (`Terremoto o erupción volcánica — Sección Edificio: EXCLUIDO`) with a null