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:
@@ -1444,3 +1444,243 @@ button {
|
||||
color: var(--muted);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
Policies — vigencia badges
|
||||
========================================================================== */
|
||||
.badge.status-active {
|
||||
background: var(--positive-tint);
|
||||
color: var(--positive);
|
||||
border-color: rgba(47, 109, 60, 0.25);
|
||||
}
|
||||
.badge.status-expiring {
|
||||
background: var(--accent-tint);
|
||||
color: var(--accent-600);
|
||||
border-color: rgba(191, 90, 52, 0.28);
|
||||
}
|
||||
.badge.status-expired {
|
||||
background: var(--negative-tint);
|
||||
color: var(--negative);
|
||||
border-color: rgba(162, 58, 44, 0.25);
|
||||
}
|
||||
.badge.status-undated {
|
||||
background: var(--paper-2);
|
||||
color: var(--muted);
|
||||
border-color: var(--line-strong);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
Policies — header strips
|
||||
========================================================================== */
|
||||
/* Stat cells double as vigencia filters on /polizas. */
|
||||
.stat-cell-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font: inherit;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.stat-cell-btn:hover {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.stat-cell-btn.accent:hover {
|
||||
background: #d7e6e1;
|
||||
}
|
||||
.stat-cell-btn.selected {
|
||||
box-shadow: inset 0 -3px 0 var(--brand-600);
|
||||
}
|
||||
|
||||
.premium-strip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 14px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
.premium-caption {
|
||||
font-size: 11.5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
}
|
||||
.premium-chip {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 8px;
|
||||
}
|
||||
.premium-chip strong {
|
||||
font-family: var(--font-mono);
|
||||
font-feature-settings: "tnum" 1;
|
||||
font-size: 15px;
|
||||
color: var(--brand-700);
|
||||
}
|
||||
.premium-chip-sub {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
Policies — secondary filter row
|
||||
========================================================================== */
|
||||
.filter-row {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
align-items: flex-end;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.filter-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
flex: 1 1 210px;
|
||||
min-width: 0;
|
||||
}
|
||||
.filter-label {
|
||||
font-size: 11.5px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
font-weight: 600;
|
||||
color: var(--muted);
|
||||
}
|
||||
.select {
|
||||
appearance: none;
|
||||
cursor: pointer;
|
||||
padding-right: 34px;
|
||||
background-image: linear-gradient(45deg, transparent 50%, var(--muted) 50%),
|
||||
linear-gradient(135deg, var(--muted) 50%, transparent 50%);
|
||||
background-position: calc(100% - 18px) 55%, calc(100% - 13px) 55%;
|
||||
background-size: 5px 5px, 5px 5px;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
.filter-clear {
|
||||
flex: 0 0 auto;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
Policies — list rows
|
||||
========================================================================== */
|
||||
.pol-row .cust-name {
|
||||
flex-wrap: wrap;
|
||||
gap: 9px;
|
||||
}
|
||||
.pol-number {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.pol-side {
|
||||
text-align: right;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 3px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.pol-premium {
|
||||
font-family: var(--font-mono);
|
||||
font-feature-settings: "tnum" 1;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--ink);
|
||||
}
|
||||
.pol-dates {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.pol-phrase {
|
||||
font-size: 11.5px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.pol-phrase.expiring {
|
||||
color: var(--accent-600);
|
||||
}
|
||||
.pol-phrase.active {
|
||||
color: var(--positive);
|
||||
}
|
||||
@media (max-width: 620px) {
|
||||
.pol-side {
|
||||
align-items: flex-start;
|
||||
text-align: left;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================================
|
||||
Policy detail — owner card, linked properties, vehicles
|
||||
========================================================================== */
|
||||
.owner-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 18px 22px;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border-radius: var(--radius-lg);
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.owner-link:hover {
|
||||
background: var(--surface-2);
|
||||
}
|
||||
.owner-name {
|
||||
font-family: var(--font-display);
|
||||
font-size: 19px;
|
||||
font-weight: 560;
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
.owner-cta {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--brand-600);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.linked-props {
|
||||
border-top: 1px solid var(--line);
|
||||
padding: 16px 22px 18px;
|
||||
}
|
||||
.linked-prop {
|
||||
font-size: 14px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.veh-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||
gap: 12px;
|
||||
padding: 16px;
|
||||
}
|
||||
.veh-card {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 13px 15px;
|
||||
}
|
||||
.veh-title {
|
||||
font-weight: 600;
|
||||
font-size: 14.5px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.veh-facts {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 3px;
|
||||
font-size: 12.5px;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
/* Policy number on the customer detail card links through to /polizas/[id]. */
|
||||
.policy-num-link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
transition: color 0.15s;
|
||||
}
|
||||
.policy-num-link:hover {
|
||||
color: var(--brand-600);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user