feat(notificaciones): ejecutar todos for servicios jobs
Build and Push Images / Build jorgecuadros-web (push) Successful in 1m53s
Build and Push Images / Build jorgecuadros-api (push) Successful in 2m13s

Add POST /notifications/run-all: runs the four notification jobs
(outstanding, payment confirmation, account status, trust confirmation)
sequentially with one shared set of flags from "Flags del envío".

Sequential rather than parallel — the jobs share the SES transport and
account status can self-throttle via useEmailLimit. A job that throws is
captured and the sweep continues, so one bad query cannot swallow the
other three envíos; the aggregate response carries per-job results plus
summed sent/skipped/failed and an errors count.

Audited as a single notification.run-all.run entry so one staff click is
one audit row. UI adds the button to the flags card, with a confirm when
debug is off, and a per-job summary in "Última respuesta".
This commit is contained in:
2026-08-02 02:32:13 -07:00
parent 0332292ae9
commit 53a5fe8076
5 changed files with 251 additions and 2 deletions
@@ -12,7 +12,10 @@ import { MailService } from "../mail/mail.service";
import { PrismaService } from "../prisma/prisma.service";
import {
SendAttempt,
NotificationJobKind,
NotificationJobResponse,
NotificationRunAllJobResult,
NotificationRunAllResponse,
AttemptStatus,
} from "./notification.types";
import {
@@ -649,6 +652,66 @@ export class NotificationsService {
return response;
}
/**
* "Ejecutar todos" — run all four jobs back to back with one set of flags.
*
* Sequential on purpose: the jobs share the SES transport and Job 3 can
* self-throttle via `useEmailLimit`, so firing them in parallel would both
* defeat that pause and interleave `email_notification_log` writes for no
* gain. A job that throws is captured and the sweep continues — one bad
* query must not swallow the other three envíos.
*/
async runAll(flags: {
debug?: boolean;
ignoreDayRestriction?: boolean;
useEmailLimit?: boolean;
}): Promise<NotificationRunAllResponse> {
const debug = !!flags.debug;
const steps: {
kind: NotificationJobKind;
run: () => Promise<NotificationJobResponse>;
}[] = [
{ kind: "outstanding", run: () => this.runOutstandingPayments(flags) },
{ kind: "payment", run: () => this.runPaymentConfirmation(flags) },
{ kind: "account", run: () => this.runAccountStatus(flags) },
{ kind: "trust", run: () => this.runTrustConfirmation(flags) },
];
const jobs: NotificationRunAllJobResult[] = [];
let sent = 0;
let skipped = 0;
let failed = 0;
let errors = 0;
for (const step of steps) {
try {
const result = await step.run();
sent += result.sent;
skipped += result.skipped;
failed += result.failed;
jobs.push({ kind: step.kind, ok: true, result });
} catch (e) {
errors++;
const message = e instanceof Error ? e.message : String(e);
this.logger.error(`run-all: job ${step.kind} failed — ${message}`);
jobs.push({ kind: step.kind, ok: false, error: message });
}
}
return {
request: "success",
notificationType: "runAllNotifications",
statusCode: 200,
debug,
sent,
skipped,
failed,
errors,
jobs,
type: "RUN_ALL",
};
}
/* ============================================================================
* Log browser — list / drill-down for the UI.
* ========================================================================== */