Plan step 4. Adds the policies API and the Spanish-first /polizas pages on
top of the customer records the customer module already exposes.
API (apps/api/src/policies):
- GET /policies — search over policy number, customer name, agent, vehicle
license plate, insured-driver name and legacy id; filters for vigencia
bucket, ramo, aseguradora and liquidation state; five sort orders.
- GET /policies/stats — bucket counts plus premium in force split by
currency (MXN and USD can't be summed).
- GET /policies/facets — ramos/aseguradoras with counts for the dropdowns.
- GET /policies/:id — full policy plus the owning customer.
Vigencia is derived from policyTo as active/expiring/expired/undated.
"undated" is a real bucket rather than an error case: 528 of the 2378
migrated policies carry no end date at all.
Web:
- /polizas — renewals-first browser; the stat cells double as vigencia
filters, with a secondary row for ramo, aseguradora and sort order.
- /polizas/[id] — vigencia hero, condiciones y primas, pagos, vehículos,
asegurados/beneficiarios, siniestros, the verbatim legacy coverage
columns, and documents.
- Nav gains Clientes | Pólizas with a real active state, and the two
modules cross-link in both directions.
Also fixes a display bug on the customer detail page: it headlined
policies.total, which is dead data — only 2 of 2378 rows are non-zero
(1585 are literally 0, 791 null), and one of those two is lower than its
own net premium. That rendered "$0.00 Total" on 1585 policies. Premium
headlines and the premium sort now use netPremium (2377/2378 populated);
total is shown only where it is non-zero, as raw source data.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>