Insurance module: policy browser (list/search/detail) + renewals view
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>
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
domainLabel,
|
||||
formatDate,
|
||||
formatMoney,
|
||||
premiumHeadline,
|
||||
serviceKindGlyph,
|
||||
serviceKindLabel,
|
||||
SIN_NOMBRE,
|
||||
@@ -409,14 +410,15 @@ function PolizasSection({ policies }: { policies: Policy[] }) {
|
||||
}
|
||||
|
||||
function PolicyCard({ p }: { p: Policy }) {
|
||||
const headline = p.total ?? p.netPremium;
|
||||
const headlineLabel = p.total ? "Total" : "Prima neta";
|
||||
const { value: headline, label: headlineLabel } = premiumHeadline(p);
|
||||
|
||||
return (
|
||||
<div className="policy-card">
|
||||
<div className="policy-head">
|
||||
<div>
|
||||
<div className="policy-num">{p.policyNumber || "—"}</div>
|
||||
<Link href={`/polizas/${p.id}`} className="policy-num policy-num-link">
|
||||
{p.policyNumber || "—"} →
|
||||
</Link>
|
||||
<div className="policy-type-row">
|
||||
{p.policyType?.name && (
|
||||
<span className="badge badge-seguros">
|
||||
@@ -454,11 +456,6 @@ function PolicyCard({ p }: { p: Policy }) {
|
||||
{formatMoney(headline, p.currency)}
|
||||
</div>
|
||||
<div className="policy-total-label">{headlineLabel}</div>
|
||||
{p.total && p.netPremium && (
|
||||
<div className="muted" style={{ fontSize: 12, marginTop: 4 }}>
|
||||
Prima neta {formatMoney(p.netPremium, p.currency)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user