The "Flags del envío" panel lived inside the Servicios tab and only
governed the four bulk jobs. The pólizas half had no debug at all, so
there was no way to test a renewal notice without mailing a real
customer. The panel now lives in the /notificaciones shell above the
tabs and both halves read it.
`debug` on the renewal path diverts to the same override inbox as the
servicios jobs and deliberately does NOT write the `RenewalNotice` row
or advance the sweep's `lastSuccessfulAt` — the customer was not
notified, so nothing may gate the letter they are still owed.
`ignoreDayRestriction` and `useEmailLimit` stay estado-de-cuenta-only
and are labelled as such.
Both automatic sweeps are now operator-editable. The renewal cadence
was a `@Cron("0 6 * * *")` literal and servicios had no automatic run
at all; both now resolve through `NotificationScheduleService`, which
stores the cadence in `app_settings` and reinstalls the cron job on
save — no redeploy, no restart. Defaults preserve current behaviour:
pólizas 06:00 daily, servicios off. A scheduled run never inherits the
UI flags; it always sends for real.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 lines
1.9 KiB
TypeScript
58 lines
1.9 KiB
TypeScript
import { parseSchedule, scheduleCron } from "./notification-schedule.service";
|
|
|
|
/**
|
|
* The cadence editor's only sharp edge: a stored value compiles to a cron
|
|
* expression that the scheduler installs verbatim. A malformed one either
|
|
* throws at install time (taking the sweep down) or silently installs the
|
|
* wrong cadence, so validation happens before anything is written.
|
|
*/
|
|
|
|
describe("scheduleCron", () => {
|
|
it("compiles a daily schedule with no weekday filter", () => {
|
|
expect(
|
|
scheduleCron({ enabled: true, hour: 6, minute: 0, weekdays: [] }),
|
|
).toBe("0 6 * * *");
|
|
});
|
|
|
|
it("compiles the legacy Mon/Wed/Fri cadence, sorted and de-duplicated", () => {
|
|
expect(
|
|
scheduleCron({ enabled: true, hour: 7, minute: 30, weekdays: [5, 1, 3, 1] }),
|
|
).toBe("30 7 * * 1,3,5");
|
|
});
|
|
});
|
|
|
|
describe("parseSchedule", () => {
|
|
it("normalizes weekdays and coerces enabled to a boolean", () => {
|
|
const parsed = parseSchedule({
|
|
enabled: 1,
|
|
hour: 6,
|
|
minute: 0,
|
|
weekdays: [3, 1, 3],
|
|
});
|
|
expect(parsed).toEqual({
|
|
ok: true,
|
|
value: { enabled: true, hour: 6, minute: 0, weekdays: [1, 3] },
|
|
});
|
|
});
|
|
|
|
it("defaults a missing weekday list to every day", () => {
|
|
const parsed = parseSchedule({ enabled: true, hour: 0, minute: 0 });
|
|
expect(parsed.ok && parsed.value.weekdays).toEqual([]);
|
|
});
|
|
|
|
it.each([
|
|
[{ enabled: true, hour: 24, minute: 0 }, "hora"],
|
|
[{ enabled: true, hour: 6, minute: 60 }, "minutos"],
|
|
[{ enabled: true, hour: 6, minute: 0, weekdays: [7] }, "días"],
|
|
[{ enabled: true, hour: 6.5, minute: 0 }, "hora"],
|
|
])("rejects %p", (input, field) => {
|
|
const parsed = parseSchedule(input);
|
|
expect(parsed.ok).toBe(false);
|
|
expect(!parsed.ok && parsed.error.toLowerCase()).toContain(field);
|
|
});
|
|
|
|
it("rejects a non-object", () => {
|
|
expect(parseSchedule(null).ok).toBe(false);
|
|
});
|
|
});
|