import type { PaymentFrequency } from "./types"; /** * Client-side twin of apps/api/src/policies/premium.ts. Duplicated rather than * shared because the API and the web app do not share a package today, and * both need it: the form computes IVA and Total live as the operator types, * the API stores what it is sent. * * base = prima neta + recargo + derecho de póliza * IVA = round(base * tasa) * Total = base + IVA * * The recargo is inside the taxable base — that is what reconciles the Access * books (policy 7006785 prints IVA 52.03 on 610.86 + 8.55 + 31.00; leaving the * recargo out gives 51.35, which matches nothing on the page). */ /** Applied when neither the policy nor its type carries a rate. The single * row both legacy IMPUESTOS tables held. */ export const DEFAULT_TAX_RATE = 0.08; /** Paying in more than one exhibición is what earns a recargo. A null * frequency (every migrated policy) is treated as "unknown, allow it": the * legacy recargo figures are real and hiding the field would hide them. */ export function surchargeApplies( frequency: PaymentFrequency | null | undefined, ): boolean { return frequency !== "ANNUAL" && frequency !== "SINGLE"; } export function num(v: string | number | null | undefined): number { if (v === null || v === undefined || v === "") return 0; const n = typeof v === "number" ? v : Number(String(v).trim()); return Number.isFinite(n) ? n : 0; } /** Half-up to cents, matching how the printed policy rounds. */ export function round2(n: number): number { return Math.round((n + Number.EPSILON) * 100) / 100; } export interface PremiumParts { netPremium: string | number | null | undefined; surcharge: string | number | null | undefined; policyFee: string | number | null | undefined; } export function taxableBase(p: PremiumParts): number { return round2(num(p.netPremium) + num(p.surcharge) + num(p.policyFee)); } export function computeTax(p: PremiumParts, rate: number): number { return round2(taxableBase(p) * rate); } export function computeTotal(p: PremiumParts, rate: number): number { return round2(taxableBase(p) + computeTax(p, rate)); } /** Rate ladder: what the policy was issued at, else its line of business, else * the default. Keeps an old policy reading back at its original rate after * somebody edits the catalog. */ export function resolveTaxRate( policyRate: string | number | null | undefined, policyTypeRate: string | number | null | undefined, ): number { for (const candidate of [policyRate, policyTypeRate]) { if (candidate === null || candidate === undefined || candidate === "") continue; const n = Number(candidate); if (Number.isFinite(n) && n >= 0) return n; } return DEFAULT_TAX_RATE; } /** 0.08 -> "8%". Rates are stored as fractions but read as percentages. */ export function formatRate(rate: number): string { const pct = round2(rate * 100); return `${pct}%`; }