Etapa 7 — Motor de alertas (BullMQ)

Adiciona um processo worker separado do processo web (src/worker.ts,
WorkerModule) com dois crons diários e processors do BullMQ para alertas
de mensalidade (master→tenant) e cobrança (tenant→customer): marca
faturas/parcelas atrasadas, resolve template com fallback tenant→master
por canal, renderiza placeholders, dispara WhatsApp/e-mail com retry e
backoff exponencial, e grava alert_logs com idempotência por dia.

Módulo alerts (web) traz o CRUD de alert_templates (master e tenant) e a
consulta de alert_logs com filtros. alert_templates/alert_logs não têm
RLS no schema, então o isolamento entre tenants é feito manualmente
(PrismaMasterService + filtro por tenant_id), coberto por teste e2e
dedicado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-14 22:50:59 -03:00
co-authored by Claude Sonnet 5
parent 7daaa16935
commit d6d7d5fa61
37 changed files with 2661 additions and 19 deletions
+51 -8
View File
@@ -26,7 +26,9 @@ Docker/Docker Compose. Multi-tenancy via banco único, isolado por
healthcheck, e só inicia a API depois que as duas dependências estiverem
saudáveis. `docker-compose.override.yml` é aplicado automaticamente e monta
o código-fonte local no container, então alterações em `src/` reiniciam a
API sozinhas (`npm run dev` / `nest start --watch`).
API sozinhas (`npm run dev` / `nest start --watch`). O mesmo `docker
compose up` já sobe o serviço `worker` (motor de alertas — ver seção
própria abaixo) lado a lado com a API.
3. Rode as migrations do Prisma (tabelas, enums, índices) e a migration
manual de RLS/views/checks:
@@ -133,6 +135,41 @@ contexto usam `TenantContextService.getTx()` (ver
`AuthController.listTenantUsers` como exemplo). Requests master usam
`PrismaMasterService` (conexão `app_master`, sem passar pelas policies).
## Motor de alertas (worker)
O disparo automatizado de alertas (mensalidade do tenant, cobrança de
parcelas do customer) roda num **processo separado do processo web**:
- `src/main.ts` (processo `app`) — só HTTP. Não importa `ScheduleModule`
nem processa nenhuma fila.
- `src/worker.ts` (processo `worker`) — sem HTTP. Sobe via
`NestFactory.createApplicationContext(WorkerModule)`, que inicializa os
dois crons diários (`AlertSchedulerService`, `@nestjs/schedule`) e os
dois processors do BullMQ (`MensalidadeProcessor`/`CobrancaProcessor`,
filas `alertas-mensalidade`/`alertas-cobranca` no Redis já usado pelo
`docker-compose`).
Essa separação existe para poder escalar os dois processos de forma
independente sem duplicar o disparo do cron: se o `app` rodar em várias
réplicas (comum atrás de um load balancer), um `@Cron()` registrado nele
dispararia o mesmo job uma vez por réplica. Como os crons vivem só no
`worker`, escalar `worker` (`docker compose up --scale worker=3`)
paraleliza o *processamento* das filas, não a geração dos jobs.
`docker compose up` já sobe os dois processos (serviço `worker` no
`docker-compose.yml`/`docker-compose.override.yml`, mesma imagem do `app`,
`command` diferente: `node dist/worker` em produção, `npm run worker:dev`
em desenvolvimento). Para rodar só o worker manualmente:
```bash
docker compose up worker
```
Horário dos crons, antecedência do alerta e política de retry são
configuráveis via `ALERT_CRON_MENSALIDADE`/`ALERT_CRON_COBRANCA`,
`ALERT_DIAS_ANTECEDENCIA_MENSALIDADE`/`ALERT_DIAS_ANTECEDENCIA_COBRANCA` e
`ALERT_JOB_MAX_ATTEMPTS`/`ALERT_JOB_BACKOFF_MS` (ver `.env.example`).
## Scripts
| Script | Descrição |
@@ -140,6 +177,8 @@ contexto usam `TenantContextService.getTx()` (ver
| `npm run dev` | Inicia a API em modo watch |
| `npm run build` | Compila TypeScript para `dist/` |
| `npm run start:prod` | Roda a build de produção (`dist/main.js`) |
| `npm run worker:dev` | Inicia o worker de alertas em modo watch |
| `npm run worker:prod` | Roda a build de produção do worker (`dist/worker.js`) |
| `npm run lint` | ESLint (com `--fix`) |
| `npm run test` | Testes unitários (Jest) |
| `npm run test:e2e` | Testes end-to-end (Jest + Supertest) |
@@ -152,17 +191,21 @@ contexto usam `TenantContextService.getTx()` (ver
```
src/
├── main.ts
├── app.module.ts
├── config/ # ConfigModule, validação de env, PrismaService/PrismaMasterService/
│ # PrismaTenantService, TenantContextService (mecanismo de RLS por request)
├── main.ts # processo web (HTTP)
├── worker.ts # processo worker (crons + filas BullMQ, sem HTTP)
├── app.module.ts # módulos do processo web
├── config/ # ConfigModule, validação de env, PrismaService/PrismaMasterService/
│ # PrismaTenantService, TenantContextService (mecanismo de RLS por request)
├── worker/ # WorkerModule, AlertSchedulerService (crons), processors do BullMQ
└── modules/
├── health/ # GET /health
└── auth/ # login/refresh/logout/reset, guards, decorators, JwtStrategy
├── health/ # GET /health
├── auth/ # login/refresh/logout/reset, guards, decorators, JwtStrategy
└── alerts/ # CRUD de alert_templates, GET .../alertas/logs, resolver de fallback
# e placeholders (usado pelo módulo web e pelo worker)
prisma/
├── schema.prisma
└── migrations/
Etapas/ # Prompts e controle de progresso do build faseado
Etapas/ # Prompts e controle de progresso do build faseado
```
## Progresso do build