import type { OcrPage } from "../../statements/ocr/ocr.provider";
import {
  detectPolicyProvider,
  parsePolicy,
  type ParsedCoverage,
} from "./policy-parser";

/**
 * Verbatim excerpts of what the GMX portal's translation PDF actually
 * rendered through pdftotext — same convention as the statement parser
 * tests, where invented-clean input would test nothing because clean input
 * is not the failure mode.
 */
function page(text: string): OcrPage {
  return { text, words: [], confidence: 0.95 };
}

/** Coverages keyed by their risk label, so an assertion names the coverage
 *  it is about instead of an array index that shifts when one is added. */
const byRisk = (p: ReturnType<typeof parsePolicy>): Record<string, ParsedCoverage> =>
  Object.fromEntries(p.coverages.map((c) => [c.risk, c]));

describe("detectPolicyProvider", () => {
  it("claims GMX from the brand wordmark on the letterhead", () => {
    expect(
      detectPolicyProvider(
        "Grupo Mexicano de Seguros, S.A. de C.V.\nTecoyotitla 412, Edificio GMX",
      ),
    ).toBe("GMX");
  });

  it("claims GMX from the 'gmx.com.mx' footer URL", () => {
    expect(detectPolicyProvider("JUNTOS EL RIESGO ES MENOR\nwww.gmx.com.mx")).toBe("GMX");
  });
});

describe("parsePolicy / GMX", () => {
  // Verbatim text extracted from ~/Downloads/HC_Folio_000767_Traduccion.pdf via
  // `pdftotext -layout`. Two pages joined by "\n\n".
  const GMX_FULL = page(
    "Multiple Policy\nHome\n" +
      "Policy 007-037-07005947-0000-02 in accordance with the enclosed clauses, to insurance:\n" +
      "Insured JON ASHLEY STRABALA\n" +
      "Additional insured VIVIAN\n" +
      "Legal address BONAMPACK No. EXT26 No.INT 0 COL. Punta Bandera, Tijuana, Baja California, C.P. 22550\n" +
      "ZIP 22550 Income Tax No. XEXX-010101-000\n" +
      "Broker (1176) Jorge Humberto Cuadros\n" +
      "Term 12 months\n" +
      "From 19/07/2026\n" +
      "To 19/07/2027 at twelve hours (noon) Mexico City time.\n" +
      "Currency DOLARES Premium payment CONTADO\n" +
      "Free translation from the Spanish Insurance contract. The English text is just copy given by courtesy. In case of a dispute, the Spanish will prevail over the English version.\n" +
      "Agreed clauses:\n" +
      "•The insured and GMX Hereby declared...\n" +
      "From the above, the present contract shall not be considered under the condition mentioned within article 36-B from the Insurance Companies General Law. Therefore it shall not be required its registration before the Comision National de Seguros y Fianzas.\n" +
      "July 23, 2026\n" +
      "Authority sign.\n" +
      "Grupo Mexicano de Seguros, S.A. de C.V.\n" +
      "Tecoyotitla 412, Edificio GMX\n" +
      "JUNTOS EL RIESGO ES MENOR\n" +
      "www.gmx.com.mx\n\n" +
      "Risk Insured Amount Deductible Loss Participation\n" +
      "Building $350,000.00 Not applies Not applies\n" +
      "Contents $60,000.00 Not applies Not applies\n" +
      "ADDITIONAL RISK\n" +
      "Risk Insured Amount Deductible Loss Participation\n" +
      "Debris removal Building $35,000.00 Not applies Not applies\n" +
      "Debris removal Contents $6,000.00 Not applies Not applies\n" +
      "Outdoors Constructions $10,000.00 5% 10%\n" +
      "Coverage Extention Covered Not applies Not applies\n" +
      "All Risk Covered Not applies Not applies\n" +
      "Earthquake and/or volcanic eruption Covered 2% of the sum insured for each damage structure 20%\n" +
      "Extra Expenses $41,000.00 Not applies Not applies\n" +
      "Robbery with violence $10,000.00 Not applies Not applies\n" +
      "Jewerly $3,900.00 Not applies Not applies\n" +
      "Electronic Equipment $10,000.00 Not applies Not applies\n" +
      "Glasses $10,000.00 Not applies Not applies\n" +
      "Tenant $200,000.00 Not applies Not applies\n" +
      "Family $200,000.00 Not applies Not applies\n" +
      "Family $200,000.00 Not applies Not applies\n" +
      "Domestic workers $7,010.00 Not applies Not applies\n" +
      "VALUES ADDED, HOME GMX",
  );

  it("extracts the policy number, insured name, broker, dates, and currency", () => {
    const p = parsePolicy(GMX_FULL);
    expect(p.provider).toBe("GMX");
    expect(p.policyNumber).toBe("007-037-07005947-0000-02");
    expect(p.insuredName).toBe("JON ASHLEY STRABALA");
    expect(p.additionalInsured).toBe("VIVIAN");
    expect(p.agentName).toBe("Jorge Humberto Cuadros");
    expect(p.policyFrom?.toISOString().slice(0, 10)).toBe("2026-07-19");
    expect(p.policyTo?.toISOString().slice(0, 10)).toBe("2027-07-19");
    expect(p.policyDate?.toISOString().slice(0, 10)).toBe("2026-07-23");
    expect(p.currency).toBe("USD");
    expect(p.zip).toBe("22550");
    expect(p.legalAddress).toContain("BONAMPACK");
    expect(p.premiumPayment).toBe("CONTADO");
  });

  it("extracts every coverage row off the second page table", () => {
    const p = parsePolicy(GMX_FULL);
    const byName = Object.fromEntries(p.coverages.map((c) => [c.risk, c]));
    expect(byName.Building?.insuredAmount).toBe(350000);
    expect(byName.Contents?.insuredAmount).toBe(60000);
    expect(byName["Debris removal Building"]?.insuredAmount).toBe(35000);
    expect(byName["Outdoors Constructions"]?.insuredAmount).toBe(10000);
    expect(byName["Outdoors Constructions"]?.deductible).toBe("5%");
    expect(byName["Outdoors Constructions"]?.lossParticipation).toBe("10%");
    // Free-text coverage cells kept verbatim (the policy form surfaces them
    // as observations, not as numbers).
    expect(byName["Earthquake and/or volcanic eruption"]?.insuredAmount).toBeNull();
    expect(byName["Earthquake and/or volcanic eruption"]?.deductible).toContain("2%");
    expect(byName["Earthquake and/or volcanic eruption"]?.lossParticipation).toBe("20%");
    expect(byName["All Risk"]?.insuredAmount).toBeNull();
    expect(p.coverages.length).toBeGreaterThan(10);
  });

  it("names the product MULT for confirm to resolve", () => {
    // The caratula's own header reads "Multiple Policy / Home". MULT is the
    // legacy discriminator for that multi-line home policy; INCENDIO is
    // fire-only and no policy in the book has ever used it.
    expect(parsePolicy(GMX_FULL).policyTypeName).toBe("MULT");
  });

  it("leaves premium fields null on the certificate page and notes it", () => {
    const p = parsePolicy(GMX_FULL);
    expect(p.netPremium).toBeNull();
    expect(p.total).toBeNull();
    expect(p.policyFee).toBeNull();
    expect(p.notes.join(" ")).toMatch(/prima/i);
  });

  it("still parses when the broker parens are missing", () => {
    const p = parsePolicy(
      page(
        "Insured JON ASHLEY STRABALA\nBroker Jorge Humberto Cuadros\n" +
          "From 19/07/2026\nTo 19/07/2027\nCurrency DOLARES\n" +
          "Grupo Mexicano de Seguros",
      ),
    );
    expect(p.agentName).toBe("Jorge Humberto Cuadros");
  });

  it("rejects a page that carries no GMX signal at all", () => {
    const p = parsePolicy(page("Random unrelated document with no policy data."));
    expect(p.provider).toBe("");
    expect(p.notes.join(" ")).toContain("no se reconoció el proveedor");
  });

  it("captures the deductible / loss-participation columns verbatim as strings", () => {
    const p = parsePolicy(GMX_FULL);
    const eq = p.coverages.find((c) => c.risk === "Earthquake and/or volcanic eruption");
    expect(eq).toBeDefined();
    const eqTyped = eq as ParsedCoverage;
    expect(eqTyped.deductible).toContain("sum insured");
    expect(eqTyped.lossParticipation).toBe("20%");
  });
});

/**
 * The second GMX document family: the Spanish PVL "especificación" the office
 * receives as `…-CondicionesParticulares.pdf`. Verbatim excerpts from
 * `007_LGS-HGMX_07006957_01_0-CondicionesParticulares.pdf` through
 * `pdftotext -layout`, indentation included — the column positions and the
 * blank lines between blocks are what the parser reads, so a cleaned-up
 * fixture would test nothing.
 */
describe("parsePolicy / GMX especificación (PVL Hogar)", () => {
  const HEADER =
    "                    ESPECIFICACIÓN QUE SE ADHIERE Y FORMA PARTE INTEGRANTE DE LA PÓLIZA\n" +
    "                                           07-037-07006957-00000-01\n" +
    "\n";

  const GMX_ESPEC = page(
    HEADER +
      "\n" +
      "                                            Nombre del asegurado EMMER . KATHLEEN\n" +
      "\n" +
      "                                          Tipo Persona Asegurada Propietario\n" +
      "\n" +
      "                                               Ubicación del riesgo LOS PELICANOS ESTE NO. 98 Col. LAS GAVIOTAS PLAYAS\n" +
      "                                                                    DE ROSARITO BAJA CALIFORNIA 22713\n" +
      "\n" +
      "                                      Características del Inmueble Casa Tipo constructivo Combinado: Macizo y Madera.\n" +
      "                                                                   Consta de 2 pisos incluyendo sótanos y planta baja.\n" +
      "\n" +
      "                                             -500 mts.cuerpo agua SI\n" +
      "\n" +
      "                                              Asegurado Adicional\n" +
      "\n" +
      "PVL Hogar - GMX Seguros                                                     Página: 1 de 10\n" +
      HEADER +
      "\n" +
      " SECCIÓN INCENDIO EDIFICIO Y CONTENIDOS\n" +
      "\n" +
      " EDIFICIO\n" +
      "\n" +
      " Límite Máximo de Responsabilidad:\n" +
      " $200,000.00 USD\n" +
      "\n" +
      " Quedan amparados los muros de contención y bardas, así como puertas y portones, hasta un sublimite de $ 50,000.00 M.N. o su\n" +
      " equivalente en dólares americanos, o hasta el 10% de la suma asegurada de la sección de Edificio, lo que resulte menor.\n" +
      "\n" +
      "\n" +
      " CONTENIDOS\n" +
      "\n" +
      " Límite Máximo de Responsabilidad:\n" +
      " $20,000.00 USD\n" +
      "\n" +
      "    2. Terremoto o erupción volcánica: Sección Edificio EXCLUIDO, Sección Contenidos EXCLUIDO\n" +
      "\n" +
      "    3. Fenómenos hidrometeorológicos: Sección Edificio $200,000.00 USD, Sección Contenidos $20,000.00 USD\n" +
      "\n" +
      " Riesgos adicionales.\n" +
      "\n" +
      " Remoción de escombros\n" +
      "\n" +
      "    Límite Máximo de Responsabilidad:\n" +
      "    Edificio\n" +
      "    $20,000.00 USD\n" +
      "    Contenidos\n" +
      "    $2,000.00 USD\n" +
      "\n" +
      " Gastos extraordinarios para casa habitación\n" +
      "\n" +
      " En caso de siniestro por los riesgos cubiertos en esta póliza, GMX Seguros pagará la renta de casa o departamento, casa de\n" +
      " huéspedes u hotel cuando se asegure el inmueble, así como los gastos de mudanza, seguro de transporte del menaje de casa y\n" +
      " efectuados.\n" +
      "\n" +
      "    Límite Máximo de Responsabilidad:\n" +
      "    $22,000.00 USD\n" +
      "    Periodo de indemnización: 4 meses.\n" +
      "\n" +
      " Bienes a la Intemperie:\n" +
      "\n" +
      "\n" +
      "     5 POR CIENTO SOBRE SUMA ASEGURADA, 20 PORCIENTO DE PARTICIPACIÓN A CARGO DEL ASEGURADO DE TODA\n" +
      "     Y CADA PÉRDIDA.\n" +
      "\n" +
      "\n" +
      "     Límite Máximo de Responsabilidad: $10,000.00 USD\n" +
      "\n" +
      " DEDUCIBLES:\n" +
      "\n" +
      " El procedimiento que se seguirá para la aplicación de deducibles en caso de que la póliza cuente con cláusula inflacionaria en todas\n" +
      " y/o en algunas de sus coberturas será como sigue:\n" +
      "\n" +
      " Fenómenos hidrometeorológicos\n" +
      " Zona: A2\n" +
      " Deducible\n" +
      "     Edificio: 1 POR CIENTO SOBRE SUMA ASEGURADA\n" +
      "     Coaseguro:\n" +
      "     Zona 1: (INTERIOR) Participación a cargo del asegurado del 10% de toda y cada pérdida.\n" +
      "     Zona 2: Participación a cargo del asegurado del 10% de toda y cada pérdida.\n" +
      "\n" +
      "     Deducible\n" +
      "     Contenidos: 1 POR CIENTO SOBRE SUMA ASEGURADA\n" +
      "\n" +
      " II.- SECCIÓN DIVERSOS MISCELÁNEOS\n" +
      "\n" +
      " ROBO DE CONTENIDOS\n" +
      "\n" +
      " Límite de Responsabilidad:\n" +
      " $4,000.00 USD\n" +
      "\n" +
      "\n" +
      " Deducible:\n" +
      " Sin deducible\n" +
      "\n" +
      "\n" +
      " Sublímites:\n" +
      " Joyas, artículos de oro y plata, armas, relojes, pieles, piedras preciosas montadas, colecciones, obras de arte y demás que por su\n" +
      "\n" +
      "PVL Hogar - GMX Seguros                                                     Página: 7 de 10\n" +
      HEADER +
      "\n" +
      " naturaleza se consideran como objetos de difícil o imposible reposición\n" +
      "\n" +
      "\n" +
      " Límite de Responsabilidad:\n" +
      "\n" +
      " $2,000.00 USD\n" +
      "\n" +
      "\n" +
      " Deducible:\n" +
      " Sin deducible\n" +
      "\n" +
      " Las condiciones generales que forman parte de la presente póliza son las identificadas bajo el nombre:\n" +
      " W_HogarGMX_12.11.2025.pdf\n" +
      "\n" +
      "PVL Hogar - GMX Seguros                                                    Página: 10 de 10\n",
  );

  it("reads a policy number whose groups are not the caratula's widths", () => {
    // 2-3-8-5-2 here vs 3-3-8-4-2 on the English caratula. Pinning the widths
    // reads one family and returns null on the other.
    expect(parsePolicy(GMX_ESPEC).policyNumber).toBe("07-037-07006957-00000-01");
  });

  it("reads the insured, the risk location across its wrapped line, and the ZIP", () => {
    const p = parsePolicy(GMX_ESPEC);
    expect(p.provider).toBe("GMX");
    expect(p.insuredName).toBe("EMMER . KATHLEEN");
    expect(p.legalAddress).toBe(
      "LOS PELICANOS ESTE NO. 98 Col. LAS GAVIOTAS PLAYAS DE ROSARITO BAJA CALIFORNIA 22713",
    );
    expect(p.zip).toBe("22713");
    // The cell is printed but empty on this policy — an empty label must not
    // capture the next line of the form.
    expect(p.additionalInsured).toBeNull();
  });

  it("leaves the fields this document does not carry null, and says so", () => {
    const p = parsePolicy(GMX_ESPEC);
    expect(p.policyFrom).toBeNull();
    expect(p.policyTo).toBeNull();
    expect(p.policyDate).toBeNull();
    expect(p.agentName).toBeNull();
    expect(p.netPremium).toBeNull();
    expect(p.total).toBeNull();
    // 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", () => {
    // The body prose quotes sublimits in pesos ("$ 50,000.00 M.N."); every
    // limit is in USD, and only the limits vote.
    expect(parsePolicy(GMX_ESPEC).currency).toBe("USD");
  });

  it("reads each coverage under its own heading", () => {
    const c = byRisk(parsePolicy(GMX_ESPEC));
    expect(c.EDIFICIO?.insuredAmount).toBe(200000);
    expect(c.CONTENIDOS?.insuredAmount).toBe(20000);
    expect(c["ROBO DE CONTENIDOS"]?.insuredAmount).toBe(4000);
    expect(c["ROBO DE CONTENIDOS"]?.deductible).toBe("Sin deducible");
  });

  it("splits a limit printed under Edificio / Contenidos sub-labels", () => {
    const c = byRisk(parsePolicy(GMX_ESPEC));
    expect(c["Remoción de escombros — Edificio"]?.insuredAmount).toBe(20000);
    expect(c["Remoción de escombros — Contenidos"]?.insuredAmount).toBe(2000);
  });

  it("names a coverage after its heading, not after the wrapped tail of the prose above it", () => {
    // Walking back from the limit hits "efectuados." — short, and the only
    // thing separating it from a heading is that it is not preceded by a
    // blank line.
    const c = byRisk(parsePolicy(GMX_ESPEC));
    expect(c["Gastos extraordinarios para casa habitación"]?.insuredAmount).toBe(22000);
    expect(c["efectuados."]).toBeUndefined();
  });

  it("reads a limit printed on the label's own line", () => {
    const c = byRisk(parsePolicy(GMX_ESPEC));
    expect(c["Bienes a la Intemperie"]?.insuredAmount).toBe(10000);
  });

  it("reads a deductible stated as a sentence above the limit", () => {
    const c = byRisk(parsePolicy(GMX_ESPEC));
    expect(c["Bienes a la Intemperie"]?.deductible).toBe(
      "5 POR CIENTO SOBRE SUMA ASEGURADA, 20 PORCIENTO DE PARTICIPACIÓN A CARGO DEL ASEGURADO DE TODA Y CADA PÉRDIDA.",
    );
  });

  it("never borrows a neighbouring coverage's prose as a deductible", () => {
    // "…o hasta el 10% de la suma asegurada de la sección de Edificio" is a
    // sublimit rule for EDIFICIO, printed two paragraphs above CONTENIDOS.
    const c = byRisk(parsePolicy(GMX_ESPEC));
    expect(c.CONTENIDOS?.deductible).toBeNull();
    expect(c.EDIFICIO?.deductible).toBeNull();
  });

  it("does not read the page-level DEDUCIBLES paragraph as a deductible", () => {
    const p = parsePolicy(GMX_ESPEC);
    expect(
      p.coverages.some((c) => (c.deductible ?? "").includes("cláusula inflacionaria")),
    ).toBe(false);
  });

  it("reads a sublimit block as a sublimit OF the coverage above it", () => {
    // The amount sits after a blank line AND a page break, and the block's
    // own heading ("Sublímites:") names no risk.
    const c = byRisk(parsePolicy(GMX_ESPEC));
    expect(c["ROBO DE CONTENIDOS — sublímite"]?.insuredAmount).toBe(2000);
  });

  it("records an excluded catastrophic risk as excluded, never as zero", () => {
    const p = parsePolicy(GMX_ESPEC);
    const quake = p.coverages.filter((c) => /Terremoto/i.test(c.risk));
    expect(quake).toHaveLength(2);
    for (const c of quake) {
      expect(c.risk).toMatch(/EXCLUIDO/);
      // A coverage insured for $0 and an excluded coverage are the same
      // number and very different facts.
      expect(c.insuredAmount).toBeNull();
    }
  });

  it("attaches the hydrometeorological deductible and coinsurance from its own block", () => {
    const c = byRisk(parsePolicy(GMX_ESPEC));
    const building = c["Fenómenos hidrometeorológicos — Sección Edificio"];
    expect(building?.insuredAmount).toBe(200000);
    expect(building?.deductible).toBe("1 POR CIENTO SOBRE SUMA ASEGURADA");
    expect(building?.lossParticipation).toBe("10%");
    expect(c["Fenómenos hidrometeorológicos — Sección Contenidos"]?.insuredAmount).toBe(20000);
  });

  it("names the same product as the caratula — one policy, two artifacts", () => {
    expect(parsePolicy(GMX_ESPEC).policyTypeName).toBe("MULT");
  });

  it("carries the underwriting context the fields have no home for", () => {
    const notes = parsePolicy(GMX_ESPEC).notes.join(" | ");
    expect(notes).toMatch(/tipo de persona asegurada: Propietario/);
    expect(notes).toMatch(/características del inmueble: Casa/);
    expect(notes).toMatch(/cuerpo de agua/);
    expect(notes).toMatch(/zona catastrófica declarada: A2/);
    expect(notes).toMatch(/W_HogarGMX_12\.11\.2025\.pdf/);
  });
});

/* ------------------------------------------------------------------ ANA */

/**
 * Verbatim `pdftotext -layout` output of the PDFs A.N.A.'s portal produced
 * for three real policies, cut at the end of the risk table (the legal
 * boilerplate and the repeated AGENT COPY below it are not parsed, and the
 * repeats are covered by their own test).
 *
 * The column padding is load-bearing on the driver's policy, which
 * distinguishes SUM INSURED from PREMIUM by horizontal position alone — do
 * not reflow these strings.
 */

const ANA_AUTO_AMPLIA = page(`A.N.A. COMPAÑIA DE SEGUROS SA DE CV
LUIS CABRERA #2033 INT. 201, Col. ZONA URBANA RIO TIJUANA
C.P. 22010 MUNICIPIO DE TIJUANA, BAJA CALIFORNIA
www.anaseguros.com.mx
AUTOMOBILE
                                       ALL CLAIMS MUST BE REPORTED BEFORE LEAVING MEXICO
U.S. CELL PHONES TRY      + 011-52-55-5322-82-66 MEXICAN CELL PHONES 800-911-911-9                                                   SPECIAL POLICY FOR TOURISTS
TOLL-FREE FROM THE U.S.A. 888-335-7072           BELIZE CELL PHONES 00-52-55-5322-8266
WHATSAPP                  + 52-55-80-50-3633
                                                                                                                            No.           700489651
                   ISSUED BY:                               DATE ISSUED                                   TERM OF INSURANCE
                                                                                                                                                                  DAYS
JORGE HUMBERTO CUADROS                                     DAY MONTH YEAR                                DAY    MONTH    YEAR                        TIME
BENITO JUAREZ 25 No.50 INT 38 CENTRO
                                                            04         08      2026 FROM                  07             08            2026          12:01
                                                                                                                                                                     365
ROSARITO, BAJA CALIFORNIA 22710
.                                                70175                                 TO                 07             08            2027          12:01
         DISCOUNT                   PREMIUM                     POLICY FEE                        TAX                     LOCAL TAX                        TOTAL
              -                       298.61                        30.00                        26.29                          0.00                       354.90

    INSURED                  RAY DEAN II AND SUSAN ROCKHOLD
                                                                                                                     LICENSE           P0066762
    ADDRESS                  10308 DONNA AVE                                                                         EMAIL             PROLABSALE@AOL.COM
    CITY & STATE             NORTHRIDGE, CA                                                             91326        TELEPHONE 8184453524
                                                                                                                                                  PAYMENT DEADLINE
  INSURANCE COMPANY                                                              LIEN HOLDER
                                                                                                                                                        IMMEDIATE

      ITEM                   YEAR             MAKE                                      BODY                                    SERIAL No.                     PLATES
VEHICLE                      2017           CHRYSLER                                  PACIFICA                              2C4RC1DG7HR654698                  8BPX206
TRAILER                                                                                                                             .                             .
TOWING                                                                                                                              .                             .
*** VALUE STATED MUST NOT EXCEED MARKET VALUE ***
***VEHICLES THAT HAVE BEEN ACQUIRED AS SALVAGE, REBUILT, OR HAVE BEEN USED PREVIOUSLY AS A TAXI WILL BE CONSIDERED WITH A REDUCED VALUE OF 35% (thirty-five percent), TAKING
AS A BASE THE VALUE OF A SIMILAR NORMAL VEHICLE, THAT IS, ONE THAT HAS NOT BEEN ACQUIRED AS SALVAGE AND ITS PREVIOUS USE HAS NOT BEEN AS A TAXI OR REBUILT. IT WILL BE THE
SOLE OBLIGATION AND RESPONSIBILITY OF THE INSURED TO DECLARATE THIS WHEN ACQUIRING THE POLICY.
SECTION                                     SPECIFICATION OF RISKS                                                                              LIMIT OF LIABILITY
            MATERIAL DAMAGE WITH MANDATORY DEDUCTIBLE                                                            COVERED/EXCLUDED VEHICLE                   8,000.00 DLLS.
    1       DEDUCTIBLE: WITH MINIMUM OF $500.00 ON AUTOS
                                                                                                                                              TRAILER
            (SEDANS, COUPES, CONVERTIBLES AND STATION WAGONS)                                                         COVERED
            AND $500.00 ON ALL OTHERS (PICK UPS, VANS, SUV´s AND MOTOR HOMES).                                                                                  0.00 DLLS.

            TOTAL THEFT WITH MANDATORY DEDUCTIBLE                                                                COVERED/EXCLUDED TOWING
    2       DEDUCTIBLE: WITH MINIMUM OF $1,000.00 ON AUTOS                                                                                                      0.00 DLLS.
            (SEDANS, COUPES, CONVERTIBLES AND STATION WAGONS)                                                         COVERED
            AND $1,000.00 ON ALL OTHERS (PICK UPS, VANS, SUV´s AND MOTOR HOMES).
            LIABILITY FOR PROPERTY DAMAGE TO THIRD PARTIES
    3                                                                                                                                               100,000.00         DLLS.


            BODILY INJURY LIABILITY                                                               PER                               PER
    4                                                                                             PERSON         100,000.00         ACCIDENT        200,000.00         DLLS.

            MEDICAL EXPENSES                                                                      PER                               PER
    5                                                                                             PERSON         5,000.00           ACCIDENT        25,000.00          DLLS.

                                                                                                                 COVERED/EXCLUDED                       PREMIUM
    6       A.N.A.'s LEGAL AID
                                                                                                                      COVERED                             40.00
                                                                                                                 COVERED/EXCLUDED                       PREMIUM
     7      A.N.A.'s ROADSIDE ASSISTANCE
                                                                                                                      COVERED                             40.00
            CATASTROPHIC LIABILITY FOR DEATH OF THIRD                                                                                                   PREMIUM
     8                                                                                                          EXCLUDED
            PARTIES                                                                                                                    DLLS.               0.00
            ELITE OR ELITE PLUS WITH MANDATORY DEDUCTIBLE                                                          COVERED/EXCLUDED
     9     PARTIAL THEFT (LIMIT 0.00 DLLS.WITH DEDUCTIBLE: 0.00 DLLS. PER EVENT)                                                                           0.00
           VANDALISM (LIMIT 0.00 DLLS.WITH DEDUCTIBLE: 0.00 DLLS. PER EVENT)                                           EXCLUDED


ISSUED ONLINE`);

const ANA_AUTO_RC_DIAS = page(`A.N.A. COMPAÑIA DE SEGUROS SA DE CV
LUIS CABRERA #2033 INT. 201, Col. ZONA URBANA RIO TIJUANA
C.P. 22010 MUNICIPIO DE TIJUANA, BAJA CALIFORNIA
www.anaseguros.com.mx
AUTOMOBILE
                                       ALL CLAIMS MUST BE REPORTED BEFORE LEAVING MEXICO
U.S. CELL PHONES TRY      + 011-52-55-5322-82-66 MEXICAN CELL PHONES 800-911-911-9                                                   SPECIAL POLICY FOR TOURISTS
TOLL-FREE FROM THE U.S.A. 888-335-7072           BELIZE CELL PHONES 00-52-55-5322-8266
WHATSAPP                  + 52-55-80-50-3633
                                                                                                                            No.           700487807
                   ISSUED BY:                               DATE ISSUED                                   TERM OF INSURANCE
                                                                                                                                                                         DAYS
JORGE HUMBERTO CUADROS DIARIA                              DAY MONTH YEAR                                DAY    MONTH    YEAR                            TIME
BENITO JUAREZ 25 NO50 INT 38 COL CENTRO
                                                            22         07      2026 FROM                  23             07            2026              12:01
                                                                                                                                                                            3
ROSARITO BAJA CALIFORNIA 22710
(661) 612 12 55                                  70175                                 TO                 26             07            2026              12:01
         DISCOUNT                   PREMIUM                     POLICY FEE                        TAX                     LOCAL TAX                             TOTAL
              -                        10.77                        25.00                         2.86                          0.00                              38.63

    INSURED                  STEPHEN RUPAN SHATAFIAN
                                                                                                                     LICENSE           C1394198
    ADDRESS                  13181 CROSSROADS PARKWAY NORTH STE 300                                                  EMAIL             sshatafian@lee-associates.com


    CITY & STATE             CITY OF INDUSTRY, CA                                                        91746       TELEPHONE 7143221072
                                                                                                                                                     PAYMENT DEADLINE
  INSURANCE COMPANY                                                              LIEN HOLDER
                                                                                                                                                            IMMEDIATE

      ITEM                   YEAR               MAKE                                    BODY                                    SERIAL No.                             PLATES
VEHICLE                      2022               FORD                                   TRANSIT                              1FBAX2CG3NKA69091                          EC46T99
TRAILER                                                                                                                             .                                     .
TOWING                                                                                                                              .                                     .
*** VALUE STATED MUST NOT EXCEED MARKET VALUE ***
***VEHICLES THAT HAVE BEEN ACQUIRED AS SALVAGE, REBUILT, OR HAVE BEEN USED PREVIOUSLY AS A TAXI WILL BE CONSIDERED WITH A REDUCED VALUE OF 35% (thirty-five percent), TAKING
AS A BASE THE VALUE OF A SIMILAR NORMAL VEHICLE, THAT IS, ONE THAT HAS NOT BEEN ACQUIRED AS SALVAGE AND ITS PREVIOUS USE HAS NOT BEEN AS A TAXI OR REBUILT. IT WILL BE THE
SOLE OBLIGATION AND RESPONSIBILITY OF THE INSURED TO DECLARATE THIS WHEN ACQUIRING THE POLICY.
SECTION                                     SPECIFICATION OF RISKS                                                                                LIMIT OF LIABILITY
            MATERIAL DAMAGE WITH MANDATORY DEDUCTIBLE                                                            COVERED/EXCLUDED VEHICLE                              0.00 DLLS.
    1       DEDUCTIBLE: ON AUTOS (SEDANS, COUPES, CONVERTIBLES AND
                                                                                                                                               TRAILER
            STATION WAGONS) AND OTHERS (PICK UPS, VANS,                                                              EXCLUDED
            SUV´s AND MOTOR HOMES).                                                                                                                                    0.00 DLLS.

            TOTAL THEFT WITH MANDATORY DEDUCTIBLE                                                                COVERED/EXCLUDED TOWING
    2       DEDUCTIBLE: ON AUTOS (SEDANS, COUPES, CONVERTIBLES AND                                                                                                     0.00 DLLS.
            STATION WAGONS) AND OTHERS (PICK UPS, VANS,                                                              EXCLUDED
            SUV´s AND MOTOR HOMES).
            LIABILITY FOR PROPERTY DAMAGE TO THIRD PARTIES
    3                                                                                                                                                  100,000.00            DLLS.


            BODILY INJURY LIABILITY                                                               PER                               PER
    4                                                                                             PERSON         100,000.00         ACCIDENT           200,000.00            DLLS.

            MEDICAL EXPENSES                                                                      PER                               PER
    5                                                                                             PERSON         5,000.00           ACCIDENT           25,000.00             DLLS.

                                                                                                                 COVERED/EXCLUDED                            PREMIUM
    6       A.N.A.'s LEGAL AID
                                                                                                                      COVERED                                    2.25
                                                                                                                 COVERED/EXCLUDED                            PREMIUM
     7      A.N.A.'s ROADSIDE ASSISTANCE
                                                                                                                      COVERED                                    2.25
            CATASTROPHIC LIABILITY FOR DEATH OF THIRD                                                                                                        PREMIUM
     8                                                                                                           EXCLUDED
            PARTIES                                                                                                                    DLLS.                     0.00
            ELITE OR ELITE PLUS WITH MANDATORY DEDUCTIBLE                                                          COVERED/EXCLUDED
     9     PARTIAL THEFT (LIMIT 0.00 DLLS.WITH DEDUCTIBLE: 0.00 DLLS. PER EVENT)                                                                                 0.00
           VANDALISM (LIMIT 0.00 DLLS.WITH DEDUCTIBLE: 0.00 DLLS. PER EVENT)                                           EXCLUDED


ISSUED ONLINE`);

const ANA_LICENCIA = page(`A.N.A. COMPAÑIA DE SEGUROS SA DE CV
LUIS CABRERA #2033 INT. 201, Col.4 ZONA URBANA RIO TIJUANA
C.P. 22010 MUNICIPIO DE TIJUANA, BAJA CALIFORNIA
www.anaseguros.com.mx
                                                 DRIVER´S POLICY FOR AUTOMOBILE
                                       ALL CLAIMS MUST BE REPORTED BEFORE LEAVING MEXICO
U.S. CELL PHONES TRY      + 011-52-55-5322-82-66 MEXICAN CELL PHONES 800-911-911-9
                                                                                                                                          SPECIAL POLICY FOR TOURISTS
TOLL-FREE FROM THE U.S.A. 888-335-7072           BELIZE CELL PHONES 00-52-55-5322-8266
WHATSAPP                  + 52-55-80-50-3633                                                                                     No.            700489616
                   ISSUED BY:                              DATE ISSUED & TIME                                 TERM OF INSURANCE
JORGE HUMBERTO CUADROS
                                                                                                                                                                          DAYS
                                                            DAY MONTH YEAR                                   DAY    MONTH    YEAR                           TIME
BENITO JUAREZ 25 No.50 INT 38 CENTRO                          04         08      2026 FROM                     06             08            2026            12:01
                                                                                                                                                                            365
ROSARITO, BAJA CALIFORNIA 22710                                                           TO                   06             08            2027            12:01
.                                                 70175
      DISCOUNT                      PREMIUM                      POLICY FEE                           TAX                      LOCAL TAX                         TOTAL
             -                        142.78                           30.00                         13.82                           0.00                         186.60

   LICENSE           N0017668                                    EMAIL          PWAGONER49@AOL.COM                        TELEPHONE 3102001538
       POLICY HOLDER
      1. NAME          :                   PAMELA DENISE WAGONER        Ph.3102001538
        ADDRESS        :                   49305 HIGHWAY 74 SPC 10, PALM DESERT, CA, 92260,
        DRIVER LICENSE :                   N0017668
      2. NAME          :
        ADDRESS        :
        DRIVER LICENSE :
                                             NONE
      3. NAME          :
        ADDRESS        :
        DRIVER LICENSE :
                                             NONE
      4. NAME          :
        ADDRESS        :
        DRIVER LICENSE :                     NONE
      5. NAME          :
        ADDRESS        :
        DRIVER LICENSE :                     NONE
     SPECIFICATION OF RISKS                                                                                    SUM INSURED                                     PREMIUM
     LIABILITY FOR PROPERTY DAMAGE TO THIRD PARTIES                                                     100,000.00 usd.                                      18.70         usd.
     BODILY INJURY LIABILITY ( EXCLUDING OCCUPANTS OF THE VEHICLE )                                     100,000.00 usd. Per Person
                                                                                                                                                             54.27         usd.
                                                                                                        200,000.00 usd. Per Accident
     CATASTROPHIC LIABILITY FOR DEATH OF THIRD PARTIES                                                 0.00        usd.                                        0.00        usd.

     MEDICAL EXPENSES                                                                                     4,000.00 usd. Per Person
                                                                                                                                                               9.81        usd.
                                                                                                         20,000.00 usd. Per Accident
                                                                                                                      COVERED/EXCLUDED                         PREMIUM
       LEGAL AID
                                                                                                                           COVERED                           30.00          usd.
                                                                                                                      COVERED/EXCLUDED                         PREMIUM
       AUTOMOBILE ASSISTANCE
                                                                                                                           COVERED                           30.00          usd.

     The following risks are excluded            Collision, overtuning and glass breakage, fire, total theft and natural disasters, partial theft and vandalism.`);


describe("detectPolicyProvider / ANA", () => {
  it("claims ANA from the letterhead", () => {
    expect(
      detectPolicyProvider("A.N.A. COMPAÑIA DE SEGUROS SA DE CV\nwww.anaseguros.com.mx"),
    ).toBe("ANA");
  });

  it("does not let GMX's layout rules claim an ANA page", () => {
    // Both books print "MATERIAL DAMAGE"-ish headings; the brand pass runs
    // before any layout rule precisely so this can't go the other way.
    expect(detectPolicyProvider(ANA_AUTO_AMPLIA.text)).toBe("ANA");
    expect(detectPolicyProvider(ANA_LICENCIA.text)).toBe("ANA");
  });
});

describe("parsePolicy / ANA automobile", () => {
  const p = parsePolicy(ANA_AUTO_AMPLIA);

  it("reads the header band", () => {
    expect(p.provider).toBe("ANA");
    expect(p.policyNumber).toBe("700489651");
    expect(p.insuredName).toBe("RAY DEAN II AND SUSAN ROCKHOLD");
    expect(p.agentName).toBe("JORGE HUMBERTO CUADROS");
    expect(p.legalAddress).toBe("10308 DONNA AVE, NORTHRIDGE, CA 91326");
    expect(p.zip).toBe("91326");
    expect(p.currency).toBe("USD");
    expect(p.premiumPayment).toBe("IMMEDIATE");
  });

  it("reads DD MM YYYY out of the three date column cells", () => {
    expect(p.policyDate?.toISOString().slice(0, 10)).toBe("2026-08-04");
    expect(p.policyFrom?.toISOString().slice(0, 10)).toBe("2026-08-07");
    expect(p.policyTo?.toISOString().slice(0, 10)).toBe("2027-08-07");
  });

  it("maps the six money cells positionally, not by finding six amounts", () => {
    // DISCOUNT prints as a bare "-" here. A "take the amounts in order"
    // reading would shift every value one column left.
    expect(p.netPremium).toBe(298.61);
    expect(p.policyFee).toBe(30);
    expect(p.tax).toBe(26.29);
    expect(p.total).toBe(354.9);
  });

  it("reads a TAX that reconciles against the rest of the row", () => {
    // 298.61 + 30.00 = 328.61, taxed at 8% -> 26.29, totalling 354.90. The
    // whole row agreeing is what proves the positional mapping landed on the
    // right cells rather than merely on six numbers.
    const base = p.netPremium! + p.policyFee!;
    expect(Math.round(base * 0.08 * 100) / 100).toBe(p.tax);
    expect(Math.round((base + p.tax!) * 100) / 100).toBe(p.total);
  });

  it("does not fold LOCAL TAX into the IVA", () => {
    // It prints 0.00 here, so nothing to fold — but the guard is that a
    // non-zero one would surface as a note instead of inflating `tax`.
    expect(p.notes.join(" | ")).not.toMatch(/impuesto local/);
  });

  it("reads the vehicle by token role, not by column", () => {
    expect(p.vehicles).toHaveLength(1);
    expect(p.vehicles[0]).toEqual({
      item: "VEHICLE",
      modelYear: "2017",
      make: "CHRYSLER",
      bodyType: "PACIFICA",
      vinNumber: "2C4RC1DG7HR654698",
      licensePlate: "8BPX206",
    });
  });

  it("reads a two-word BODY cell without losing the VIN", () => {
    // "GENESIS SEDAN" is two tokens where "PACIFICA" is one — the VIN shape
    // is the anchor, not the token count.
    const v = parsePolicy(ANA_AUTO_RC_DIAS).vehicles[0];
    expect(v.make).toBe("FORD");
    expect(v.vinNumber).toBe("1FBAX2CG3NKA69091");
    expect(v.licensePlate).toBe("EC46T99");
  });

  it("skips the empty TRAILER and TOWING slots", () => {
    // Both print a "." per cell rather than being absent.
    expect(p.vehicles.map((v) => v.item)).toEqual(["VEHICLE"]);
  });

  it("records the insured as a named driver with their licence", () => {
    expect(p.drivers).toHaveLength(1);
    expect(p.drivers[0].fullName).toBe("RAY DEAN II AND SUSAN ROCKHOLD");
    expect(p.drivers[0].licenseNumber).toBe("P0066762");
    expect(p.drivers[0].email).toBe("PROLABSALE@AOL.COM");
  });

  it("does not read the agent's own street number as the policy number", () => {
    // "BENITO JUAREZ 25 No.50 INT 38" sits three lines above the No. cell.
    expect(p.policyNumber).not.toBe("50");
    expect(p.notes.join(" | ")).not.toMatch(/formas/);
  });

  it("reads the agent clave without picking up their postal code", () => {
    // "ROSARITO, BAJA CALIFORNIA 22710" is five digits in the same band.
    expect(p.notes.join(" | ")).toMatch(/clave de agente: 70175/);
    expect(p.notes.join(" | ")).not.toMatch(/22710/);
  });

  it("labels the declared values by their printed item slot", () => {
    const c = byRisk(p);
    expect(c["MATERIAL DAMAGE — VEHICLE"]?.insuredAmount).toBe(8000);
    expect(c["MATERIAL DAMAGE — TRAILER"]?.insuredAmount).toBe(0);
    expect(c["TOTAL THEFT — TOWING"]?.insuredAmount).toBe(0);
  });

  it("keeps the deductible sentence out of the value columns", () => {
    const c = byRisk(p);
    expect(c["MATERIAL DAMAGE — VEHICLE"]?.deductible).toBe(
      "WITH MINIMUM OF $500.00 ON AUTOS (SEDANS, COUPES, CONVERTIBLES AND " +
        "STATION WAGONS) AND $500.00 ON ALL OTHERS (PICK UPS, VANS, SUV´s AND " +
        "MOTOR HOMES).",
    );
  });

  it("does not mistake the $500.00 inside the deductible for a sum insured", () => {
    // It is the one amount in the block not suffixed "DLLS.".
    const amounts = p.coverages.map((c) => c.insuredAmount);
    expect(amounts).not.toContain(500);
  });

  it("splits the per-person and per-accident limits", () => {
    const c = byRisk(p);
    expect(c["BODILY INJURY LIABILITY — POR PERSONA"]?.insuredAmount).toBe(100000);
    expect(c["BODILY INJURY LIABILITY — POR EVENTO"]?.insuredAmount).toBe(200000);
    expect(c["MEDICAL EXPENSES — POR PERSONA"]?.insuredAmount).toBe(5000);
    expect(c["MEDICAL EXPENSES — POR EVENTO"]?.insuredAmount).toBe(25000);
  });

  it("records an add-on's figure as a premium, never as a sum insured", () => {
    // $40 is what legal aid COST. As `insuredAmount` it would read on the
    // review screen as a $40 liability limit.
    const c = byRisk(p);
    expect(c["LEGAL AID"]?.premium).toBe(40);
    expect(c["LEGAL AID"]?.insuredAmount).toBeNull();
    expect(c["ROADSIDE ASSISTANCE"]?.premium).toBe(40);
  });

  it("unpacks section 9's parenthesised limit and deductible", () => {
    const c = byRisk(p);
    const theft = c["ELITE / ELITE PLUS — PARTIAL THEFT: EXCLUDED"];
    expect(theft?.insuredAmount).toBe(0);
    expect(theft?.deductible).toBe("0.00 DLLS. POR EVENTO");
    expect(c["ELITE / ELITE PLUS — VANDALISM: EXCLUDED"]).toBeDefined();
  });

  it("emits each coverage once even though the PDF prints the face twice", () => {
    // The real upload is ORIGINAL + AGENT COPY + receipt + three travel
    // cards, all concatenated into one string before parsing.
    const doubled = page(ANA_AUTO_AMPLIA.text + "\n\n" + ANA_AUTO_AMPLIA.text);
    expect(parsePolicy(doubled).coverages).toHaveLength(p.coverages.length);
    expect(parsePolicy(doubled).vehicles).toHaveLength(1);
  });
});

describe("policy type, as a name for confirm to resolve", () => {
  it("names ANA's two faces after the legacy tables they belong to", () => {
    expect(parsePolicy(ANA_AUTO_AMPLIA).policyTypeName).toBe("AUTO");
    expect(parsePolicy(ANA_AUTO_RC_DIAS).policyTypeName).toBe("AUTO");
    expect(parsePolicy(ANA_LICENCIA).policyTypeName).toBe("LICENCIAS");
  });

  it("emits a NAME, never an id — the parser must not need a database", () => {
    // Anything id-shaped here would mean the parser had reached for the DB.
    for (const p of [ANA_AUTO_AMPLIA, ANA_AUTO_RC_DIAS, ANA_LICENCIA]) {
      expect(parsePolicy(p).policyTypeName).toMatch(/^[A-Z_]+$/);
    }
  });

  it("leaves the type unnamed when no parser claimed the page", () => {
    expect(parsePolicy(page("a laundry receipt")).policyTypeName).toBeNull();
  });
});

describe("parsePolicy / ANA responsabilidad civil por días", () => {
  const p = parsePolicy(ANA_AUTO_RC_DIAS);

  it("reads a by-the-day term rather than defaulting to a year", () => {
    // Left at the schema's 365 default this weekend policy would sit in the
    // renewals window a year out.
    expect(p.policyFrom?.toISOString().slice(0, 10)).toBe("2026-07-23");
    expect(p.policyTo?.toISOString().slice(0, 10)).toBe("2026-07-26");
    expect(p.coveragePeriodDays).toBe(3);
  });

  it("reads the clave when the agent's phone occupies the left cell", () => {
    // The by-the-day products print "(661) 612 12 55" ahead of the clave, so
    // it is no longer the first thing on its line.
    expect(p.notes.join(" | ")).toMatch(/clave de agente: 70175/);
  });

  it("marks the excluded sections as excluded, not as insured for zero", () => {
    const risks = p.coverages.map((c) => c.risk);
    expect(risks).toContain("MATERIAL DAMAGE — VEHICLE: EXCLUDED");
    expect(risks).toContain("TOTAL THEFT — TOWING: EXCLUDED");
    // The liability sections are what this product actually sells, and they
    // are NOT excluded.
    expect(risks).toContain("LIABILITY FOR PROPERTY DAMAGE TO THIRD PARTIES");
  });
});

describe("parsePolicy / ANA driver's policy (licencia)", () => {
  const p = parsePolicy(ANA_LICENCIA);

  it("reads the holder off the numbered POLICY HOLDER list", () => {
    expect(p.policyNumber).toBe("700489616");
    expect(p.insuredName).toBe("PAMELA DENISE WAGONER");
    expect(p.legalAddress).toBe("49305 HIGHWAY 74 SPC 10, PALM DESERT, CA, 92260");
    expect(p.zip).toBe("92260");
  });

  it("lists one driver, not one per printed copy of the page", () => {
    // The face renders three times in the real PDF; an unbounded walk
    // returns the same person three times, which reads as a three-driver
    // policy rather than as a parse bug.
    const tripled = page([ANA_LICENCIA.text, ANA_LICENCIA.text, ANA_LICENCIA.text].join("\n\n"));
    expect(p.drivers).toHaveLength(1);
    expect(parsePolicy(tripled).drivers).toHaveLength(1);
  });

  it("splits the phone off the name even without the printed column gap", () => {
    // The phone shares the name cell, and the only thing marking it off is
    // white space — which the OCR seam is free to collapse. Depending on the
    // gap surviving is what put "PAMELA DENISE WAGONER Ph.3102001538" in the
    // insured field, where it matched no customer.
    const collapsed = page(ANA_LICENCIA.text.replace(/ {2,}/g, " "));
    expect(parsePolicy(collapsed).insuredName).toBe("PAMELA DENISE WAGONER");
  });

  it("drops the four empty driver slots", () => {
    // Slots 2-5 print an empty NAME and a bare "NONE" licence.
    expect(p.drivers.map((d) => d.fullName)).toEqual(["PAMELA DENISE WAGONER"]);
    expect(p.drivers[0].licenseNumber).toBe("N0017668");
    expect(p.drivers[0].phone).toBe("3102001538");
  });

  it("insures no vehicle", () => {
    expect(p.vehicles).toEqual([]);
    expect(p.notes.join(" | ")).toMatch(/no ampara un veh[íi]culo determinado/);
  });

  it("separates the SUM INSURED and PREMIUM columns by position", () => {
    // Both columns print the same shape ("100,000.00 usd." / "18.70 usd.")
    // and neither is labelled per row — only the offset tells them apart.
    const c = byRisk(p);
    const pd = c["LIABILITY FOR PROPERTY DAMAGE TO THIRD PARTIES"];
    expect(pd?.insuredAmount).toBe(100000);
    expect(pd?.premium).toBe(18.7);
  });

  it("reads the trailing Per Person / Per Accident labels on this layout", () => {
    // They FOLLOW their amount here and PRECEDE it on the automobile face.
    const c = byRisk(p);
    expect(c["BODILY INJURY LIABILITY — POR PERSONA"]?.insuredAmount).toBe(100000);
    expect(c["BODILY INJURY LIABILITY — POR EVENTO"]?.insuredAmount).toBe(200000);
    expect(c["MEDICAL EXPENSES — POR PERSONA"]?.insuredAmount).toBe(4000);
    expect(c["MEDICAL EXPENSES — POR EVENTO"]?.insuredAmount).toBe(20000);
  });

  it("charges a section's premium once, not once per limit", () => {
    const c = byRisk(p);
    expect(c["BODILY INJURY LIABILITY — POR PERSONA"]?.premium).toBe(54.27);
    expect(c["BODILY INJURY LIABILITY — POR EVENTO"]?.premium).toBeNull();
  });

  it("handles the section order this layout uses", () => {
    // CATASTROPHIC LIABILITY prints ABOVE MEDICAL EXPENSES here and below it
    // on the automobile face; blocks are keyed by where the labels land.
    const c = byRisk(p);
    expect(c["CATASTROPHIC LIABILITY FOR DEATH OF THIRD PARTIES"]?.insuredAmount).toBe(0);
    expect(c["LEGAL AID"]?.premium).toBe(30);
    expect(c["ROADSIDE ASSISTANCE"]?.premium).toBe(30);
  });

  it("carries the excluded-risk sentence that defines the product", () => {
    expect(p.notes.join(" | ")).toMatch(/riesgos excluidos: Collision, overtuning/);
  });
});
