feat(notificaciones): sweep one aseguradora at a time, and stop the robot quoting a premium
The office works GMX and ANA as separate batches, so the manual barrido now takes a "Compañía" selection. It filters the pending list as well as the sweep, so what is on screen is exactly what "Ejecutar barrido" will mail, and the confirmation names the carrier — running GMX when ANA was meant is the mistake the filter exists to prevent, and it is not reversible once the mail is out. The carrier is chosen by InsuranceProvider id, from the same /lookups the policy form reads, so a renamed or newly added aseguradora needs no code change here. A carrier-scoped run deliberately does NOT advance `lastSuccessfulAt`. The sweep's catch-up window is computed from it, so advancing after a run that looked at every day but mailed only one carrier would push every OTHER carrier's letters out of tomorrow's window and they would never be sent. Same reasoning that already keeps a debug run from advancing it. Separately, the letter's "Prima" row is now dropped from the unattended scheduled sweep only. A premium can still be re-rated at renewal, and an amount a robot mailed out is one the office has to walk back; every send a person triggers — the manual barrido and the per-row "Enviar aviso" — still quotes it. `recordLog` renders with the same flag, so `bodySnapshot` cannot show the office a letter the customer never received. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -183,6 +183,50 @@ describe("renewal notices write the shared notification log", () => {
|
||||
expect(prisma.renewalNotice.upsert).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("quotes the premium on a staff-triggered sweep but not the scheduled one", async () => {
|
||||
const manual = build({});
|
||||
await manual.service.sweep("user-1");
|
||||
expect(manual.record.mock.calls[0][0].bodySnapshot).toContain("Prima");
|
||||
|
||||
const automatic = build({});
|
||||
await automatic.service.scheduledSweep();
|
||||
const body = automatic.record.mock.calls[0][0].bodySnapshot;
|
||||
// The snapshot has to match the mail that actually went out, or the
|
||||
// office reads a letter the customer never received.
|
||||
expect(body).not.toContain("Prima");
|
||||
expect(body).toContain("700442181");
|
||||
});
|
||||
|
||||
it("scopes a sweep to one aseguradora without advancing the catch-up window", async () => {
|
||||
const { service, prisma, send } = build({});
|
||||
|
||||
const result = await service.sweep("user-1", { providerId: "gmx-id" });
|
||||
|
||||
expect(result.sent).toBe(1);
|
||||
expect(result.providerId).toBe("gmx-id");
|
||||
expect(prisma.policy.findMany.mock.calls[0][0].where).toMatchObject({
|
||||
insuranceProviderId: "gmx-id",
|
||||
});
|
||||
expect(send).toHaveBeenCalledTimes(1);
|
||||
// Only one carrier was mailed, so the days this run covered are still owed
|
||||
// to every other carrier: advancing `lastSuccessfulAt` would move them out
|
||||
// of tomorrow's window and they would never be sent.
|
||||
const release = prisma.scheduledJobState.update.mock.calls.at(-1)?.[0];
|
||||
expect(release.data.lastSuccessfulAt).toBeUndefined();
|
||||
});
|
||||
|
||||
it("advances the catch-up window on a clean unfiltered sweep", async () => {
|
||||
const { service, prisma } = build({});
|
||||
|
||||
await service.sweep("user-1");
|
||||
|
||||
expect(prisma.policy.findMany.mock.calls[0][0].where).not.toHaveProperty(
|
||||
"insuranceProviderId",
|
||||
);
|
||||
const release = prisma.scheduledJobState.update.mock.calls.at(-1)?.[0];
|
||||
expect(release.data.lastSuccessfulAt).toBeInstanceOf(Date);
|
||||
});
|
||||
|
||||
it("does not fail a delivered notice when the log write throws", async () => {
|
||||
const { service, record } = build({});
|
||||
record.mockRejectedValue(new Error("log table gone"));
|
||||
|
||||
Reference in New Issue
Block a user