feat(reports): parameterized renewal-notice report + legacy report reference
Replaces ~40 legacy Access renewal-notice report clones (one per carrier per coverage tier, e.g. AMPL/RC/LIC RENEW X MES/VENCE ATLAS 13/2013) with one parameterized aviso-renovacion report driven by real Policy/Vehicle/ coveragesJson data instead of hand-typed label text per clone. - schema.prisma: add RenewalNotice, replacing the legacy CONTROL <ramo> RENEW[2/3] X MES paper log of which notice generation was sent - reports: new "letter" ReportFormat + aviso-renovacion registry entry + LetterLayout renderer in ReportRunner.tsx - docs/RENEWAL_NOTICES.md + migration/legacy_report_defs/: extracted (via Application.SaveAsText, since the VBA project wouldn't load) and documented the legacy report/query chain this replaces Coveragesjson key names and a mark-as-sent mutation are still unverified/ unbuilt — see caveats in docs/RENEWAL_NOTICES.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -267,6 +267,8 @@ function ResultBlock({
|
||||
|
||||
{def.format === "statement" ? (
|
||||
<StatementLayout result={result} />
|
||||
) : def.format === "letter" ? (
|
||||
<LetterLayout result={result} />
|
||||
) : (
|
||||
<TabularLayout result={result} />
|
||||
)}
|
||||
@@ -472,5 +474,162 @@ function StatementLayout({ result }: { result: ReportRunResult }) {
|
||||
);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------- letter layout */
|
||||
|
||||
const GENERATION_LABEL: Record<string, string> = {
|
||||
"1": "1er aviso",
|
||||
"2": "2o aviso",
|
||||
"3": "3er aviso",
|
||||
};
|
||||
|
||||
interface LetterVehicle {
|
||||
make?: string | null;
|
||||
model?: string | null;
|
||||
modelYear?: string | number | null;
|
||||
bodyType?: string | null;
|
||||
engineNumber?: string | null;
|
||||
licensePlate?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* One card per policy due for renewal — the parameterized replacement for
|
||||
* the ~40 cloned "AVISO DE RENOVACION" Access reports (see
|
||||
* docs/RENEWAL_NOTICES.md). Coverage figures (CSL limit, medical coverage,
|
||||
* etc.) come from data (`aviso-renovacion`'s ReportDef reads
|
||||
* `Policy.coveragesJson`) instead of the legacy's hand-typed label text, so
|
||||
* one layout renders every carrier/coverage combination.
|
||||
*/
|
||||
function LetterLayout({ result }: { result: ReportRunResult }) {
|
||||
const letters = result.rows.filter((r) => r.__kind === "letter");
|
||||
|
||||
if (letters.length === 0) {
|
||||
return (
|
||||
<div className="empty-inline">
|
||||
No hay pólizas por vencer con los filtros actuales.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="renewal-letters">
|
||||
{letters.map((r, i) => {
|
||||
const vehicle = r.vehicle as LetterVehicle | null;
|
||||
const generation = String(r.generation ?? "1");
|
||||
return (
|
||||
<article className="renewal-letter" key={String(r.policyId ?? i)}>
|
||||
<header className="renewal-letter-head">
|
||||
<div>
|
||||
<h2 className="renewal-letter-title">Aviso de renovación</h2>
|
||||
<p className="muted small">
|
||||
{GENERATION_LABEL[generation] ?? `Aviso ${generation}`}
|
||||
</p>
|
||||
</div>
|
||||
<div className="renewal-letter-status">
|
||||
{r.sentAt ? (
|
||||
<span className="badge badge-positive">
|
||||
<span className="dot" />
|
||||
Enviado {String(r.sentAt)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="badge badge-neutral">
|
||||
<span className="dot" />
|
||||
Pendiente
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div className="renewal-letter-grid">
|
||||
<div>
|
||||
<p className="muted small">Cliente</p>
|
||||
<p className="renewal-letter-value">
|
||||
{String(r.customerName ?? "—")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="muted small">Póliza</p>
|
||||
<p className="renewal-letter-value">
|
||||
{String(r.policyNumber ?? "—")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="muted small">Aseguradora</p>
|
||||
<p className="renewal-letter-value">
|
||||
{String(r.provider ?? "—")}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="muted small">Vence</p>
|
||||
<p className="renewal-letter-value">
|
||||
{String(r.policyTo ?? "—")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{vehicle && (
|
||||
<div className="renewal-letter-vehicle">
|
||||
<p className="muted small">Vehículo asegurado</p>
|
||||
<p className="renewal-letter-value">
|
||||
{[vehicle.modelYear, vehicle.make, vehicle.model]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
{vehicle.bodyType ? ` · ${vehicle.bodyType}` : ""}
|
||||
</p>
|
||||
<p className="muted small">
|
||||
{[
|
||||
vehicle.engineNumber && `Motor: ${vehicle.engineNumber}`,
|
||||
vehicle.licensePlate && `Placa: ${vehicle.licensePlate}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="renewal-letter-coverage">
|
||||
<CoverageItem label="Cobertura (días)" value={r.coverageDays} />
|
||||
<CoverageItem label="CSL" value={r.cslLimit} money />
|
||||
<CoverageItem label="Gastos médicos" value={r.medicalCoverage} money />
|
||||
<CoverageItem label="Daños a propiedad" value={r.propertyDamage} money />
|
||||
<CoverageItem label="Responsabilidad por persona" value={r.perPersonLiability} money />
|
||||
</div>
|
||||
|
||||
<div className="renewal-letter-premium">
|
||||
{r.netPremium && (
|
||||
<span>Prima neta: {formatCell(r.netPremium, "money")}</span>
|
||||
)}
|
||||
{r.total && (
|
||||
<span className="renewal-letter-total">
|
||||
Total: {formatCell(r.total, "money")} {String(r.currency ?? "")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</article>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CoverageItem({
|
||||
label,
|
||||
value,
|
||||
money,
|
||||
}: {
|
||||
label: string;
|
||||
value: unknown;
|
||||
money?: boolean;
|
||||
}) {
|
||||
if (value === null || value === undefined || value === "") return null;
|
||||
return (
|
||||
<div className="renewal-letter-coverage-item">
|
||||
<p className="muted small">{label}</p>
|
||||
<p className="renewal-letter-value">
|
||||
{money ? formatCell(value, "money") : String(value)}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Hint to the bundler that API_ORIGIN is part of the API surface used here.
|
||||
void API_ORIGIN;
|
||||
|
||||
Reference in New Issue
Block a user