ecosystem-wikicómo extender los templates sin romper sus convenciones
‹ todas las recipes

¿Evento, o llamada directa?

template-api
último cambio 2026-09-25

Un use case termina su trabajo y hay que avisar a alguien: un email, un push, revocar sesiones. Dos formas de hacerlo, y la elección no es de estilo.

Evento cuando puede fallar sin invalidar lo que acabas de hacer. Llamada directa cuando no.

Si el push de “han escaneado la chapa de tu mascota” no sale, la chapa se regeneró igual y el usuario no debería ver un error. Eso es un evento. Si al crear una mascota falla la creación de su chapa, la mascota se queda sin chapa y eso sí es un problema — llamada directa:

// CreatePetUseCase: directo, forma parte de crear la mascota
const created = await this.pets.create(pet);
await this.createPetTagUC.execute(created.id);
return created;
// GetPublicPetProfileUseCase: evento, es un efecto lateral
if (owner && tag.canNotify(this.clock.now())) {
  await this.petTags.markNotified(tag.id, this.clock.now());
  this.eventEmitter.emit(
    PetTagScannedEvent.KEY,
    new PetTagScannedEvent(owner.id, owner.tenantId, pet.id, pet.name),
  );
}

Fíjate en que la decisión de si notificar (canNotify) y la marca de que se notificó se quedan en el use case. Al handler solo le llega el hecho de que pasó algo.

Cómo se escribe

El evento (application/events/pet-tag-scanned.event.ts): una clase con KEY estática y los datos en el constructor. Lleva todo lo que el handler va a necesitar, porque un handler no tiene repositorios para ir a buscar lo que le falte:

new PetTagScannedEvent(owner.id, owner.tenantId, pet.id, pet.name)

Ese pet.name está ahí precisamente por eso: el handler no puede resolverlo por su cuenta.

El handler (application/handlers/): nombre rígido, on-<evento>-event-<acción>.handler.ts. Un handler, una acción. Si un evento dispara email y push, son dos ficheros:

on-two-factor-enabled-event-send-email.handler.ts
on-two-factor-enabled-event-send-push.handler.ts

En petid-api hay 15 eventos y 19 handlers, justo por esto.

@Injectable()
export class OnPasswordChangedEventSendPushHandler {
  constructor(
    @Inject(DEVICE_REPOSITORY) private readonly devices: DeviceRepository,
    @Inject(PUSH_SENDER) private readonly push: PushSender,
  ) {}

  @OnEvent(PasswordChangedEvent.KEY)
  async handle(event: PasswordChangedEvent): Promise<void> {
    const userDevices = await this.devices.findByUserId(event.userId);
    if (userDevices.length === 0) return;
    await this.push.sendToDevices(userDevices, {
      code: "account-security-updated",
      sourceTenantId: event.tenantId,
    });
  }
}

Es el handler de petid-api, que pasa los Device enteros a sendToDevices para que viaje la plataforma de cada uno. template-api todavía tiene sendToTokens(tokens, payload), que solo manda los tokens: ver conectar con notification-api.

Se registran todos en application/handlers/event-handlers.module.ts.

Qué canal: email, push o los dos

Esta convención existe y conviene respetarla al añadir un aviso nuevo:

Canal Para qué Ejemplos
Email Cambios de estado de la cuenta dispositivo registrado, cuenta borrada, visibilidad activada
Push Operativo, del día a día recordatorio de cita, cita cancelada, chapa escaneada
Ambos Seguridad crítica contraseña cambiada, 2FA activado o desactivado

El criterio de fondo: el email deja constancia consultable meses después y llega aunque no tengas la app; el push es inmediato y se pierde.

No todo aviso es un evento

Las tareas programadas no pasan por el bus. El recordatorio del día anterior (SendAppointmentRemindersTask, en infrastructure/tasks/) consulta las citas de mañana y manda el push directamente. No hay evento porque no hay nada que haya “ocurrido”: es el reloj.

Un evento sin handler es legítimo, pero decídelo. EmailVerifiedEvent se emite desde tres sitios y hoy no dispara nada. Es un punto de enganche a la espera de un caso de uso — pero si va a quedarse así, mejor que lo diga un comentario a que parezca un olvido.

Trampa

No uses un evento para encadenar pasos obligatorios. Si B tiene que pasar sí o sí después de A, y te importa el orden y el resultado, llama a B. Los handlers no devuelven nada, no se esperan entre ellos y sus fallos no llegan al llamante — que es justo lo que los hace buenos para lo opcional y malos para lo esencial.