Skip to content

Comandos Asíncronos

Los comandos asíncronos te permiten ejecutar tareas fuera del flujo principal de la aplicación. Son ideales para operaciones lentas como envío de emails, procesamiento de archivos o integraciones con servicios externos.

Conceptos clave

  • @command('name'): registra una clase como un comando ejecutable.
  • @commandHandler(CommandClass): registra un handler que procesa un comando.
  • ICommandHandler<C>: interfaz que debe implementar cada handler.
  • Async: servicio singleton para ejecutar o programar comandos.
  • runCommandHandlers([]): activa el sistema de procesamiento de comandos.

Paso 1: Definir un comando

Un comando es una clase decorada con @command que extiende Storable. Define los datos que necesita el handler para ejecutarse:

import { command, Storable, isString, isNotEmpty } from '@wabot-dev/framework'
@command('send-email')
export class SendEmailCommand extends Storable<{
to: string
subject: string
body: string
}> {}

Los datos del comando se validan usando los mismos decoradores de validación que el resto del framework.

Paso 2: Crear el handler

El handler implementa ICommandHandler<C> y contiene la lógica de ejecución:

import { commandHandler, ICommandHandler, injectable } from '@wabot-dev/framework'
@commandHandler(SendEmailCommand)
export class SendEmailHandler implements ICommandHandler<SendEmailCommand> {
constructor(private emailService: EmailService) {}
async handle(command: SendEmailCommand) {
await this.emailService.send({
to: command.data.to,
subject: command.data.subject,
body: command.data.body,
})
}
}

Paso 3: Ejecutar comandos

Usa el servicio Async para ejecutar o programar comandos:

import { Async, injectable } from '@wabot-dev/framework'
@injectable()
export class NotificationService {
constructor(private async: Async) {}
// Ejecutar inmediatamente (en background)
async sendWelcomeEmail(userEmail: string) {
await this.async.runCommand(SendEmailCommand, {
to: userEmail,
subject: 'Bienvenido',
body: 'Gracias por registrarte.',
})
}
// Programar para después
async scheduleReminder(userEmail: string) {
await this.async.scheduleCommand(
SendEmailCommand,
{
to: userEmail,
subject: 'Recordatorio',
body: 'No olvides completar tu perfil.',
},
{ hours: 24 }, // Ejecutar en 24 horas
)
}
// Programar para una fecha específica
async scheduleReport(adminEmail: string) {
const tomorrow = new Date()
tomorrow.setDate(tomorrow.getDate() + 1)
tomorrow.setHours(9, 0, 0, 0)
await this.async.scheduleCommand(
SendEmailCommand,
{
to: adminEmail,
subject: 'Reporte diario',
body: 'Aquí está tu reporte...',
},
tomorrow, // Ejecutar mañana a las 9:00
)
}
}

Opciones de scheduling

El segundo parámetro de scheduleCommand acepta:

  • Date: fecha exacta de ejecución.
  • { seconds: number }: delay en segundos.
  • { minutes: number }: delay en minutos.
  • { hours: number }: delay en horas.
  • { days: number }: delay en días.

Paso 4: Ciclo de vida

Cada comando pasa por estos estados:

  1. Scheduled: el comando fue registrado y espera ejecución.
  2. Running: el handler está procesando el comando.
  3. Success: el handler terminó sin errores.
  4. Failed: el handler lanzó un error.

Los comandos fallidos se reintentan automáticamente según la configuración del framework.

Paso 5: Activar el sistema

Registra tus handlers en el punto de entrada de la aplicación:

import { runCommandHandlers } from '@wabot-dev/framework'
runCommandHandlers([
SendEmailHandler,
GenerateReportHandler,
ProcessPaymentHandler,
])

Ejemplo completo

Sistema de notificaciones programadas para un bot de ventas:

import {
command, commandHandler, ICommandHandler, Storable,
Async, singleton, injectable,
runCommandHandlers,
} from '@wabot-dev/framework'
// --- Comando ---
@command('send-notification')
export class SendNotificationCommand extends Storable<{
prospectId: string
type: 'followup' | 'offer_expiring' | 'welcome'
message: string
}> {}
// --- Handler ---
@commandHandler(SendNotificationCommand)
export class SendNotificationHandler implements ICommandHandler<SendNotificationCommand> {
constructor(
private whatsappService: WhatsAppService,
private prospectRepository: ProspectRepository,
) {}
async handle(command: SendNotificationCommand) {
const prospect = await this.prospectRepository.findById(command.data.prospectId)
if (!prospect) return
const phone = prospect.contactInfo.find(c => c.type === 'phone')
if (!phone) return
await this.whatsappService.sendMessage(phone.value, command.data.message)
}
}
// --- Servicio que programa notificaciones ---
@singleton()
export class FollowUpService {
constructor(private async: Async) {}
async scheduleFollowUp(prospectId: string) {
// Enviar seguimiento en 2 horas
await this.async.scheduleCommand(
SendNotificationCommand,
{
prospectId,
type: 'followup',
message: 'Hola, quería saber si tuviste oportunidad de revisar nuestra propuesta.',
},
{ hours: 2 },
)
}
async scheduleOfferExpiry(prospectId: string, expiresAt: Date) {
// Notificar 24 horas antes de que expire la oferta
const notifyAt = new Date(expiresAt.getTime() - 24 * 60 * 60 * 1000)
await this.async.scheduleCommand(
SendNotificationCommand,
{
prospectId,
type: 'offer_expiring',
message: 'Tu oferta especial vence mañana. ¿Te gustaría aprovecharla?',
},
notifyAt,
)
}
async sendWelcome(prospectId: string) {
// Enviar inmediatamente
await this.async.runCommand(SendNotificationCommand, {
prospectId,
type: 'welcome',
message: 'Bienvenido. Soy Martín, tu asesor comercial. ¿En qué puedo ayudarte?',
})
}
}
// --- Registro ---
runCommandHandlers([SendNotificationHandler])

Siguiente paso

Programa tareas recurrentes con expresiones cron: Tareas Programadas (Cron).