"use client"; import { useCallback, useEffect, useRef, useState } from "react"; import Link from "next/link"; import { AppShell } from "@/components/AppShell"; import { ContextReports } from "@/components/ContextReports"; import { useCan } from "@/lib/abilities"; import { EXPIRY_WINDOW_DAYS, getPolicyFacets, getPolicyStats, listPolicies, } from "@/lib/api"; import { expiryPhrase, formatDate, formatMoney, formatNumber, policyStatusLabel, premiumHeadline, SIN_NOMBRE, } from "@/lib/labels"; import type { PolicyFacets, PolicyListItem, PolicyListResponse, PolicySort, PolicyStats, PolicyStatus, } from "@/lib/types"; type StatusFilter = "all" | PolicyStatus; const STATUS_FILTERS: { key: StatusFilter; label: string }[] = [ { key: "all", label: "Todas" }, { key: "expiring", label: "Por vencer" }, { key: "active", label: "Vigentes" }, { key: "expired", label: "Vencidas" }, { key: "undated", label: "Sin vigencia" }, ]; const SORTS: { key: PolicySort; label: string }[] = [ { key: "expiry_desc", label: "Vencimiento (más reciente)" }, { key: "expiry_asc", label: "Vencimiento (más próximo)" }, { key: "customer", label: "Cliente (A–Z)" }, { key: "number", label: "Número de póliza" }, { key: "premium_desc", label: "Prima (mayor a menor)" }, ]; export default function PolizasPage() { return ( ); } function PolizasBrowser() { const canCreate = useCan("policy:create"); const [stats, setStats] = useState(null); const [facets, setFacets] = useState(null); const [query, setQuery] = useState(""); const [status, setStatus] = useState("all"); const [typeId, setTypeId] = useState(""); const [providerId, setProviderId] = useState(""); const [sort, setSort] = useState("expiry_desc"); const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const debounceRef = useRef>(); useEffect(() => { getPolicyStats().then(setStats).catch(() => setStats(null)); getPolicyFacets().then(setFacets).catch(() => setFacets(null)); }, []); const runSearch = useCallback( (p: number) => { setLoading(true); setError(null); listPolicies({ query: query || undefined, status: status === "all" ? undefined : status, typeId: typeId || undefined, providerId: providerId || undefined, sort, days: EXPIRY_WINDOW_DAYS, page: p, pageSize: 25, }) .then((res) => { setData(res); setLoading(false); }) .catch((e) => { setError(e?.message ?? "No se pudieron cargar las pólizas."); setLoading(false); }); }, [query, status, typeId, providerId, sort], ); // Debounced re-query whenever any filter changes; always back to page 1. useEffect(() => { if (debounceRef.current) clearTimeout(debounceRef.current); debounceRef.current = setTimeout(() => runSearch(1), 280); return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, [runSearch]); function goToPage(p: number) { runSearch(p); if (typeof window !== "undefined") window.scrollTo({ top: 0, behavior: "smooth" }); } const filtered = query !== "" || status !== "all" || typeId !== "" || providerId !== ""; return ( <>

Cartera de seguros

Pólizas

{canCreate && ( + Nueva póliza )}
setStatus(s)} />
setQuery(e.target.value)} placeholder="Buscar por póliza, cliente, placa, agente…" aria-label="Buscar pólizas" />
{STATUS_FILTERS.map((f) => ( ))}
{filtered && ( )}
{data && !loading && !error && (
{data.total === 0 ? "Sin resultados" : `${formatNumber(data.total)} ${ data.total === 1 ? "póliza" : "pólizas" }`} {query ? ` para “${query}”` : ""}
)} {error ? (
{error}
) : loading ? ( ) : data && data.items.length === 0 ? ( ) : ( <>
{data?.items.map((p) => ( ))}
{data && data.pageCount > 1 && ( )} )} ); } /** Counts double as filter shortcuts — clicking a cell applies that bucket. */ function StatStrip({ stats, status, onPickStatus, }: { stats: PolicyStats | null; status: StatusFilter; onPickStatus: (s: StatusFilter) => void; }) { if (!stats) { return (
{Array.from({ length: 6 }).map((_, i) => (
))}
); } const cells: { key: StatusFilter; value: number; label: string; accent?: boolean; }[] = [ { key: "all", value: stats.total, label: "Pólizas", accent: true }, { key: "expiring", value: stats.expiring, label: `Vencen en ${stats.days} días`, accent: true, }, { key: "active", value: stats.active, label: "Vigentes" }, { key: "expired", value: stats.expired, label: "Vencidas" }, { key: "undated", value: stats.undated, label: "Sin vigencia" }, ]; return (
{cells.map((c) => ( ))}
{formatNumber(stats.pending)}
Sin liquidar
); } /** Premium in force, split by currency — MXN and USD can't be summed. */ function PremiumStrip({ stats }: { stats: PolicyStats | null }) { if (!stats || stats.premiumInForce.length === 0) return null; return (
Prima neta vigente {stats.premiumInForce.map((row) => ( {formatMoney(row.netPremium, row.currency)} {row.currency} · {formatNumber(row.count)}{" "} {row.count === 1 ? "póliza" : "pólizas"} ))}
); } function PolicyRow({ p }: { p: PolicyListItem }) { const premium = premiumHeadline(p); const phrase = expiryPhrase(p.daysToExpiry); const showPhrase = p.status === "expiring" || p.status === "active"; return (
{p.policyNumber || "—"} {p.policyType?.name && ( {p.policyType.name} )} {policyStatusLabel(p.status)} {!p.liquidated && ( Sin liquidar )}
{p.customerName} {p.insuranceProvider?.name && ( <> · {p.insuranceProvider.name} )} {p.vehicleCount > 0 && ( <> · {p.vehicleCount}{" "} {p.vehicleCount === 1 ? "vehículo" : "vehículos"} )}
{formatMoney(premium.value, p.currency)}
{formatDate(p.policyFrom)} – {formatDate(p.policyTo)}
{showPhrase && phrase && (
{phrase}
)}
); } function Pager({ page, pageCount, onChange, }: { page: number; pageCount: number; onChange: (p: number) => void; }) { return ( ); } function ListSkeleton() { return (
{Array.from({ length: 8 }).map((_, i) => (
))}
); } function EmptyState({ query }: { query: string }) { return (

Sin resultados

{query ? `No encontramos pólizas para “${query}”.` : "No hay pólizas que coincidan con los filtros."}

); }