"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { CustomerPicker } from "@/components/CustomerPicker"; import { createPolicy, getLookups, updatePolicy } from "@/lib/api"; import { PAYMENT_FREQUENCY_LABELS, type Currency, type LookupsResponse, type PaymentFrequency, type PolicyDetail, type PolicyInput, } from "@/lib/types"; import { computeTax, computeTotal, formatRate, resolveTaxRate, surchargeApplies, taxableBase, } from "@/lib/premium"; function toDateInput(v: string | null | undefined): string { if (!v) return ""; const d = new Date(v); return isNaN(d.getTime()) ? "" : d.toISOString().slice(0, 10); } function numOrUndef(v: string): number | undefined { const t = v.trim(); if (t === "") return undefined; const n = Number(t); return isNaN(n) ? undefined : n; } function s(v: string): string | undefined { const t = v.trim(); return t === "" ? undefined : t; } type V = { policyNumber: string; policyTypeId: string; insuranceProviderId: string; agentName: string; policyDate: string; policyFrom: string; policyTo: string; netPremium: string; surcharge: string; policyFee: string; brokerFee: string; commission: string; /** Blank means "use the computed figure". Only ever holds a value once the * operator overrides it, so a later change to prima neta keeps flowing * through instead of being frozen by a value the form itself wrote. */ tax: string; total: string; paymentFrequency: PaymentFrequency | ""; currency: Currency; liquidated: boolean; liquidationNumber: string; liquidationDate: string; endorsement: boolean; observations: string; notes: string; }; function initial(p?: PolicyDetail): V { return { policyNumber: p?.policyNumber ?? "", policyTypeId: p?.policyType?.id ?? "", insuranceProviderId: p?.insuranceProvider?.id ?? "", agentName: p?.agentName ?? "", policyDate: toDateInput(p?.policyDate), policyFrom: toDateInput(p?.policyFrom), policyTo: toDateInput(p?.policyTo), netPremium: p?.netPremium != null ? String(p.netPremium) : "", surcharge: p?.surcharge != null ? String(p.surcharge) : "", policyFee: p?.policyFee != null ? String(p.policyFee) : "", brokerFee: p?.brokerFee != null ? String(p.brokerFee) : "", commission: p?.commission != null ? String(p.commission) : "", tax: p?.tax != null ? String(p.tax) : "", total: p?.total != null ? String(p.total) : "", paymentFrequency: p?.paymentFrequency ?? "", currency: (p?.currency as Currency) ?? "MXN", liquidated: p?.liquidated ?? false, liquidationNumber: p?.liquidationNumber ?? "", liquidationDate: toDateInput(p?.liquidationDate), endorsement: p?.endorsement ?? false, observations: p?.observations ?? "", notes: p?.notes ?? "", }; } export function PolicyForm({ policy, fixedCustomerId, fixedCustomerName, }: { policy?: PolicyDetail; fixedCustomerId?: string; fixedCustomerName?: string; }) { const router = useRouter(); const editing = !!policy; const [v, setV] = useState(() => initial(policy)); const [lookups, setLookups] = useState(null); const [customerId, setCustomerId] = useState( policy?.customer.id ?? fixedCustomerId ?? "", ); const [customerName, setCustomerName] = useState( policy?.customer.name ?? fixedCustomerName ?? "", ); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); useEffect(() => { getLookups().then(setLookups).catch(() => setLookups(null)); }, []); function set(k: K, val: V[K]) { setV((p) => ({ ...p, [k]: val })); } // IVA and Total are the only two figures the form derives. Everything else, // the recargo included, is keyed by hand — the carrier quotes the financing // charge, we do not compute it. const selectedType = lookups?.types.find((t) => t.id === v.policyTypeId); const taxRate = resolveTaxRate(policy?.taxRate, selectedType?.taxRate); const parts = { netPremium: v.netPremium, // A recargo on an annual policy is a data-entry mistake, so it is dropped // from the arithmetic as well as disabled in the UI. Otherwise switching // ANNUAL after typing one would leave it silently inflating the IVA. surcharge: surchargeApplies(v.paymentFrequency || null) ? v.surcharge : "", policyFee: v.policyFee, }; const computedTax = computeTax(parts, taxRate); const computedTotal = computeTotal(parts, taxRate); // Blank field = take the computed figure. A typed one wins, so staff can key // the carrier's rounding verbatim when it disagrees with ours by a centavo. const effectiveTax = v.tax.trim() === "" ? computedTax : Number(v.tax); const effectiveTotal = v.total.trim() === "" ? computedTotal : Number(v.total); const showSurcharge = surchargeApplies(v.paymentFrequency || null); async function submit(e: React.FormEvent) { e.preventDefault(); if (!customerId) { setError("Seleccione un cliente."); return; } setSaving(true); setError(null); const base = { policyNumber: v.policyNumber.trim(), policyTypeId: s(v.policyTypeId), insuranceProviderId: s(v.insuranceProviderId), agentName: s(v.agentName), policyDate: s(v.policyDate), policyFrom: s(v.policyFrom), policyTo: s(v.policyTo), netPremium: numOrUndef(v.netPremium), surcharge: showSurcharge ? numOrUndef(v.surcharge) : undefined, policyFee: numOrUndef(v.policyFee), brokerFee: numOrUndef(v.brokerFee), commission: numOrUndef(v.commission), // The derived figures are persisted, not recomputed on read: the printed // policy is the record of truth and a later rate change must not silently // restate what was issued. `taxRate` rides along for the same reason. tax: Number.isFinite(effectiveTax) ? effectiveTax : undefined, taxRate, total: Number.isFinite(effectiveTotal) ? effectiveTotal : undefined, paymentFrequency: v.paymentFrequency || undefined, currency: v.currency, liquidated: v.liquidated, liquidationNumber: s(v.liquidationNumber), liquidationDate: s(v.liquidationDate), endorsement: v.endorsement, observations: s(v.observations), notes: s(v.notes), }; try { if (editing) { const saved = await updatePolicy(policy!.id, base); router.push(`/polizas/${saved.id}`); } else { const payload: PolicyInput = { ...base, customerId }; const saved = await createPolicy(payload); router.push(`/polizas/${saved.id}`); } } catch (e2) { setError((e2 as Error)?.message ?? "No se pudo guardar la póliza."); setSaving(false); } } return (
{error &&
{error}
}

Datos de la póliza

Vigencia

Primas

IVA y prima total se calculan solos sobre (prima neta + recargo + derecho de póliza). Puede sobrescribirlos si la póliza impresa redondea distinto.

Liquidación