Agrega funcionalidad a tu mentalidad
Los módulos permiten extender las capacidades de tus mindsets, proporcionándoles funcionalidades específicas que pueden ser invocadas dinámicamente durante las conversaciones. Estos módulos actúan como herramientas especializadas que el bot puede utilizar para realizar acciones concretas como registrar información, consultar bases de datos, actualizar estados o ejecutar lógica de negocio compleja.
La arquitectura de módulos permite separar la lógica conversacional (definida en el mindset) de las operaciones técnicas (implementadas en los módulos), facilitando el mantenimiento, la reutilización y la escalabilidad de tu aplicación. Un mindset puede tener múltiples módulos, cada uno enfocado en un dominio o funcionalidad específica.
Conceptos clave
El sistema de módulos se compone de tres elementos principales:
@mindsetModule(): marca una clase como módulo funcional disponible para un mindset.@description(): describe métodos y propiedades para que el LLM sepa cuándo invocarlos y qué datos extraer.- Decoradores de validación: aseguran que los datos extraídos cumplan criterios antes de ejecutar la función.
Existe también un enfoque alternativo usando @mindsetFunction() y @param() que verás en la guía de Guarda información:
@mindsetFunction({ description }): marca un método como función invocable por el bot, con descripción inline.@param({ description, optional? }): define propiedades de entrada con descripción y opcionalidad. Los tipos soportados sonstring,number,booleanyDate. Todas las propiedades deben tener valores por defecto.
Ambos enfoques son válidos. El primero (@description) es más flexible para validaciones; el segundo (@mindsetFunction + @param) es más conciso.
Decorador @mindsetModule
El decorador @mindsetModule marca una clase como módulo funcional que puede ser utilizado por un mindset. Este decorador se aplica sin parámetros, y el framework detecta automáticamente los métodos disponibles dentro del módulo.
Importación:
import { mindsetModule } from '@wabot-dev/framework'Sintaxis:
@mindsetModule()export class MiModulo { // Implementación del módulo con métodos decorados}Decorador @description
El decorador @description se utiliza tanto en los métodos del módulo como en las propiedades de las clases de request para proporcionar información al LLM sobre qué hace cada función y qué datos debe extraer de la conversación.
Para métodos del módulo: La descripción explica qué hace la función y cuándo debe ser invocada por el bot.
Para propiedades de request: La descripción ayuda al bot a identificar qué información debe extraer de la conversación del usuario.
Importación:
import { description } from '@wabot-dev/framework'Sintaxis para métodos:
@description('Descripción clara de qué hace esta función y cuándo debe ser invocada')async miFuncion(parametros: TipoDeParametros) { // Implementación de la función}Sintaxis para propiedades de request:
export class MiRequest { @description('Descripción del dato que el bot debe extraer de la conversación') miPropiedad: string = ''}Validadores de parámetros
Las clases de request pueden utilizar decoradores de validación para asegurar que los datos extraídos cumplan con ciertos criterios antes de ejecutar la función del módulo.
Importación:
import { isString, isNumber, isBoolean, isDate, isNotEmpty, min, max} from '@wabot-dev/framework'Validadores disponibles:
@isString(): Valida que el valor sea una cadena de texto@isNumber(): Valida que el valor sea un número@isBoolean(): Valida que el valor sea booleano@isDate(): Valida que el valor sea una fecha válida@isNotEmpty(): Valida que el valor no esté vacío@min(valor): Valida que el número sea mayor o igual al valor especificado@max(valor): Valida que el número sea menor o igual al valor especificado
Ejemplo de uso:
export class MiRequest { @isString() @isNotEmpty() @description('Nombre del producto') productName: string = ''
@isNumber() @min(1) @max(1000) @description('Cantidad de unidades') quantity: number = 1}Ejemplo completo: Módulo de gestión de ofertas para bot vendedor
Este ejemplo muestra un módulo completo que permite a un bot vendedor gestionar ofertas comerciales, incluyendo su creación, actualización y seguimiento.
src/modules/OfferManagementModule.ts
import { Chat, mindsetModule, description } from '@wabot-dev/framework'import { CreateOfferRequest, UpdateOfferStatusRequest, AddOfferNoteRequest, UpdateOfferTermsRequest} from './requests'import { OfferRepository } from '@/repositories/OfferRepository'import { ProspectRepository } from '@/repositories/ProspectRepository'import { Offer } from '@/models/Offer'
@mindsetModule()export class OfferManagementModule { constructor( private offerRepository: OfferRepository, private prospectRepository: ProspectRepository, private chat: Chat, ) {}
@description(` Crea una nueva oferta comercial cuando el prospecto muestra interés en un producto específico o cuando se ha identificado claramente su necesidad y presupuesto. Esta función debe ser llamada después de calificar al prospecto y antes de enviar una propuesta formal. `) async createOffer(req: CreateOfferRequest) { const prospect = await this.prospectRepository.findByChatId(this.chat.id)
if (!prospect) { throw new Error('Debe existir un prospecto antes de crear una oferta') }
const offer = new Offer({ chatId: this.chat.id, prospectId: prospect.id, productName: req.productName, quantity: req.quantity, unitPrice: req.unitPrice, discount: req.discount, totalPrice: 0, status: 'draft', validUntil: req.validUntil, specialConditions: req.specialConditions, notes: [], createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), statusHistory: [{ status: 'draft', timestamp: new Date().toISOString() }] })
const totalPrice = offer.calculateTotal() await this.offerRepository.create(offer)
return { offerId: offer.id, totalPrice: totalPrice, savings: offer.calculateSavings(), message: 'Oferta creada exitosamente' } }
@description(` Actualiza el estado de una oferta existente según el progreso de la negociación. Usar cuando el prospecto acepta, rechaza, solicita modificaciones o cuando la oferta expira. Los estados válidos son: 'draft', 'sent', 'negotiating', 'accepted', 'rejected', 'expired'. `) async updateOfferStatus(req: UpdateOfferStatusRequest) { const offer = await this.offerRepository.findByChatId(this.chat.id)
if (!offer) { throw new Error('No se encontró una oferta activa para este chat') }
offer.updateStatus(req.newStatus, req.reason) await this.offerRepository.update(offer)
return { offerId: offer.id, currentStatus: offer.status, updatedAt: offer.updatedAt, isActive: offer.isActive, message: `Oferta actualizada a estado: ${req.newStatus}` } }
@description(` Registra notas importantes sobre la oferta durante la conversación, como objeciones del cliente, solicitudes especiales, puntos de negociación o cualquier información relevante que ayude a dar seguimiento efectivo a la oportunidad de venta. `) async addOfferNote(req: AddOfferNoteRequest) { const offer = await this.offerRepository.findByChatId(this.chat.id)
if (!offer) { throw new Error('No se encontró una oferta activa para este chat') }
offer.addNote({ type: req.noteType, content: req.noteContent, timestamp: new Date().toISOString(), priority: req.priority })
await this.offerRepository.update(offer)
return { offerId: offer.id, notesCount: offer.notes.length, message: 'Nota agregada exitosamente' } }
@description(` Modifica los términos de una oferta existente cuando el prospecto solicita cambios en cantidad, precio, descuento o condiciones especiales. Solo se pueden modificar ofertas en estado 'draft', 'sent' o 'negotiating' que no hayan expirado. `) async updateOfferTerms(req: UpdateOfferTermsRequest) { const offer = await this.offerRepository.findByChatId(this.chat.id)
if (!offer) { throw new Error('No se encontró una oferta activa para este chat') }
if (!offer.canBeModified()) { throw new Error('Esta oferta no puede ser modificada en su estado actual') }
offer.updateTerms({ quantity: req.quantity, unitPrice: req.unitPrice, discount: req.discount, validUntil: req.validUntil, specialConditions: req.specialConditions })
await this.offerRepository.update(offer)
return { offerId: offer.id, newTotalPrice: offer.totalPrice, newSavings: offer.calculateSavings(), message: 'Términos de la oferta actualizados exitosamente' } }}src/modules/requests/CreateOfferRequest.ts
import { description, isString, isNumber, isNotEmpty, min } from '@wabot-dev/framework'
export class CreateOfferRequest { @isString() @isNotEmpty() @description(` Nombre del producto o servicio que se está ofreciendo al prospecto. Debe ser específico y coincidir con el catálogo de productos disponibles. `) productName: string = ''
@isNumber() @min(1) @description(` Cantidad de unidades o licencias del producto que el prospecto necesita. Debe ser un número entero positivo. `) quantity: number = 1
@isNumber() @min(0) @description(` Precio unitario del producto en dólares (USD). Debe ser el precio de lista antes de aplicar descuentos. `) unitPrice: number = 0
@isNumber() @min(0) @max(100) @description(` Porcentaje de descuento a aplicar sobre el precio total. Debe ser un número entre 0 y 100. Si no hay descuento, usar 0. `) discount: number = 0
@isString() @isNotEmpty() @description(` Fecha de vencimiento de la oferta en formato ISO (YYYY-MM-DD). Después de esta fecha la oferta ya no será válida. `) validUntil: string = ''
@isString() @description(` Condiciones especiales o términos adicionales de la oferta, como forma de pago, garantías extendidas, soporte incluido, o cualquier beneficio adicional negociado con el prospecto. `) specialConditions: string = ''}src/modules/requests/UpdateOfferStatusRequest.ts
import { description, isString, isNotEmpty } from '@wabot-dev/framework'
export class UpdateOfferStatusRequest { @isString() @isNotEmpty() @description(` Nuevo estado de la oferta. Los valores válidos son: - 'draft': Oferta en borrador, aún no enviada - 'sent': Oferta enviada al prospecto - 'negotiating': En proceso de negociación - 'accepted': Prospecto aceptó la oferta - 'rejected': Prospecto rechazó la oferta - 'expired': Oferta venció sin respuesta
Usar el estado apropiado según lo que comunique el prospecto. `) newStatus: string = ''
@isString() @description(` Razón o comentario sobre el cambio de estado. Por ejemplo, si fue rechazada explicar por qué, o si está en negociación indicar qué puntos se están discutiendo. `) reason: string = ''}src/modules/requests/AddOfferNoteRequest.ts
import { description, isString, isNotEmpty } from '@wabot-dev/framework'
export class AddOfferNoteRequest { @isString() @isNotEmpty() @description(` Tipo de nota que se está registrando. Usar categorías estandarizadas en minúsculas y en inglés, como: 'objection', 'special_request', 'negotiation_point', 'customer_concern', 'competitor_mention', 'timeline_update', 'budget_constraint'. `) noteType: string = ''
@isString() @isNotEmpty() @description(` Contenido detallado de la nota. Debe capturar información relevante expresada por el prospecto que ayude a dar seguimiento efectivo o a personalizar la oferta. `) noteContent: string = ''
@isString() @description(` Nivel de prioridad de esta nota. Valores válidos: 'low', 'normal', 'high'. Usar 'high' para información crítica que requiere atención inmediata, 'normal' para seguimiento regular, 'low' para información complementaria. `) priority: string = 'normal'}src/modules/requests/UpdateOfferTermsRequest.ts
import { description, isNumber, isString, min, max } from '@wabot-dev/framework'
export class UpdateOfferTermsRequest { @isNumber() @min(1) @description(` Nueva cantidad de unidades si el prospecto solicita modificar la cantidad. Debe ser un número entero positivo. `) quantity: number = 0
@isNumber() @min(0) @description(` Nuevo precio unitario si se negocia un precio diferente al original. Debe ser en dólares (USD). `) unitPrice: number = 0
@isNumber() @min(0) @max(100) @description(` Nuevo porcentaje de descuento si se autoriza un descuento adicional. Debe ser un número entre 0 y 100. `) discount: number = 0
@isString() @description(` Nueva fecha de vencimiento si se extiende la validez de la oferta. Formato ISO (YYYY-MM-DD). `) validUntil: string = ''
@isString() @description(` Condiciones especiales modificadas o adicionales según la negociación. `) specialConditions: string = ''}Ejemplo completo: Módulo de gestión de prospectos
Este módulo complementa el módulo de ofertas, permitiendo gestionar la información de los prospectos durante el proceso de venta:
src/modules/ProspectManagementModule.ts
import { Chat, mindsetModule, description } from '@wabot-dev/framework'import { CreateProspectRequest, UpdateProspectInfoRequest, AddProspectObjectionRequest, ResolveObjectionRequest} from './requests'import { ProspectRepository } from '@/repositories/ProspectRepository'import { Prospect } from '@/models/Prospect'
@mindsetModule()export class ProspectManagementModule { constructor( private prospectRepository: ProspectRepository, private chat: Chat, ) {}
@description(` Crea un nuevo registro de prospecto cuando un usuario inicia una conversación por primera vez. Debe ser llamada al inicio de la interacción para establecer el perfil básico del prospecto antes de comenzar la calificación. `) async createProspect(req: CreateProspectRequest) { const prospect = new Prospect({ chatId: this.chat.id, firstName: req.firstName, lastName: req.lastName, companyName: req.companyName, industry: req.industry, position: req.position, interestLevel: 'medium', status: 'new', contactSource: req.contactSource, contactInfo: [], objections: [], notes: [], lastInteractionDate: new Date().toISOString(), createdAt: new Date().toISOString(), })
await this.prospectRepository.create(prospect)
return { prospectId: prospect.id, message: 'Prospecto creado exitosamente' } }
@description(` Actualiza la información del prospecto cuando se descubre nueva información durante la conversación, como nivel de interés, rango de presupuesto, estado de calificación, o datos de contacto adicionales. `) async updateProspectInfo(req: UpdateProspectInfoRequest) { const prospect = await this.resolveProspect()
if (req.interestLevel) prospect.setInterestLevel(req.interestLevel) if (req.status) prospect.setStatus(req.status) if (req.budgetRange) prospect.setBudgetRange(req.budgetRange)
if (req.contactType && req.contactValue) { prospect.addContactInfo({ type: req.contactType, value: req.contactValue, verified: false }) }
prospect.updateLastInteraction() await this.prospectRepository.update(prospect)
return { prospectId: prospect.id, isQualified: prospect.isQualified(), priorityScore: prospect.calculatePriorityScore(), message: 'Información del prospecto actualizada' } }
@description(` Registra una objeción expresada por el prospecto durante la conversación. Debe ser llamada cada vez que el prospecto manifieste una preocupación, duda o razón por la que no puede avanzar con la compra. `) async addProspectObjection(req: AddProspectObjectionRequest) { const prospect = await this.resolveProspect()
prospect.addObjection({ type: req.objectionType, description: req.objectionDescription, resolved: false, timestamp: new Date().toISOString(), })
prospect.updateLastInteraction() await this.prospectRepository.update(prospect)
return { prospectId: prospect.id, hasUnresolvedObjections: prospect.hasUnresolvedObjections(), message: 'Objeción registrada' } }
@description(` Marca una objeción como resuelta cuando se ha manejado exitosamente durante la conversación y el prospecto ha aceptado la respuesta o solución proporcionada. `) async resolveObjection(req: ResolveObjectionRequest) { const prospect = await this.resolveProspect()
prospect.resolveObjection(req.objectionType, req.resolution) prospect.updateLastInteraction() await this.prospectRepository.update(prospect)
return { prospectId: prospect.id, hasUnresolvedObjections: prospect.hasUnresolvedObjections(), message: 'Objeción resuelta exitosamente' } }
private async resolveProspect(): Promise<Prospect> { let prospect = await this.prospectRepository.findByChatId(this.chat.id)
if (!prospect) { prospect = new Prospect({ chatId: this.chat.id, interestLevel: 'medium', status: 'new', contactSource: 'whatsapp', contactInfo: [], objections: [], notes: [], lastInteractionDate: new Date().toISOString(), createdAt: new Date().toISOString(), }) await this.prospectRepository.create(prospect) }
return prospect }}src/modules/requests/CreateProspectRequest.ts
import { description, isString, isNotEmpty } from '@wabot-dev/framework'
export class CreateProspectRequest { @isString() @isNotEmpty() @description(` Primer nombre del prospecto extraído de la conversación. `) firstName: string = ''
@isString() @description(` Apellido del prospecto si fue mencionado en la conversación. `) lastName: string = ''
@isString() @description(` Nombre de la empresa u organización del prospecto si fue mencionado. `) companyName: string = ''
@isString() @description(` Industria o sector al que pertenece el prospecto. Usar valores estandarizados en inglés: 'technology', 'retail', 'healthcare', 'finance', 'manufacturing', 'education', 'other'. `) industry: string = ''
@isString() @description(` Posición o cargo del prospecto en su empresa, como 'CEO', 'Director de Ventas', 'Gerente de Marketing', etc. `) position: string = ''
@isString() @isNotEmpty() @description(` Fuente de contacto del prospecto. Usar valores estandarizados: 'whatsapp', 'telegram', 'website', 'referral', 'social_media'. `) contactSource: string = ''}src/modules/requests/UpdateProspectInfoRequest.ts
import { description, isString } from '@wabot-dev/framework'
export class UpdateProspectInfoRequest { @isString() @description(` Nivel de interés del prospecto basado en sus respuestas. Valores válidos: 'high' - Muy interesado, quiere avanzar rápidamente 'medium' - Interesado pero evaluando opciones 'low' - Poco interés o dudas significativas 'not_qualified' - No cumple con el perfil deseado `) interestLevel: string = ''
@isString() @description(` Estado actual del prospecto en el proceso de venta. Valores válidos: 'new' - Primer contacto 'contacted' - Ya se estableció comunicación 'qualified' - Cumple con los criterios de calificación 'negotiating' - En proceso de negociación 'won' - Cerró la venta exitosamente 'lost' - No se concretó la venta `) status: string = ''
@isString() @description(` Rango de presupuesto disponible del prospecto. Valores válidos: 'under_5k' - Menos de $5,000 USD '5k_to_15k' - Entre $5,000 y $15,000 USD '15k_to_50k' - Entre $15,000 y $50,000 USD 'over_50k' - Más de $50,000 USD 'not_disclosed' - No ha revelado su presupuesto `) budgetRange: string = ''
@isString() @description(` Tipo de información de contacto: 'email', 'phone', 'linkedin', 'website' `) contactType: string = ''
@isString() @description(` Valor de la información de contacto (el email, teléfono, URL, etc.) `) contactValue: string = ''}src/modules/requests/AddProspectObjectionRequest.ts
import { description, isString, isNotEmpty } from '@wabot-dev/framework'
export class AddProspectObjectionRequest { @isString() @isNotEmpty() @description(` Tipo de objeción expresada por el prospecto. Usar categorías estandarizadas en minúsculas y en inglés, como: 'price_too_high', 'need_more_time', 'budget_not_available', 'competitor_comparison', 'technical_concerns', 'implementation_complexity', 'contract_terms', 'roi_uncertainty'. `) objectionType: string = ''
@isString() @isNotEmpty() @description(` Descripción detallada de la objeción tal como fue expresada por el prospecto. Capturar el contexto completo para poder darle seguimiento apropiado. `) objectionDescription: string = ''}src/modules/requests/ResolveObjectionRequest.ts
import { description, isString, isNotEmpty } from '@wabot-dev/framework'
export class ResolveObjectionRequest { @isString() @isNotEmpty() @description(` Tipo de objeción que se está resolviendo. Debe coincidir con el tipo usado al registrar la objeción originalmente. `) objectionType: string = ''
@isString() @isNotEmpty() @description(` Explicación de cómo se resolvió la objeción, qué solución se ofreció, o qué información adicional convenció al prospecto. Esta información es valiosa para casos futuros similares. `) resolution: string = ''}Uso de los módulos en un mindset
Para utilizar estos módulos en tu mindset, simplemente inyéctalos en el constructor:
import { BaseMindset, IMindset } from '@wabot-dev/framework'import { OfferManagementModule } from '@/modules/OfferManagementModule'import { ProspectManagementModule } from '@/modules/ProspectManagementModule'
export class SalesBotMindset extends BaseMindset implements IMindset { constructor( private offerManagement: OfferManagementModule, private prospectManagement: ProspectManagementModule ) { super() }
async workflow(): Promise<string> { return ` - Al inicio de la conversación, si es un prospecto nuevo, usa la función createProspect del módulo de gestión de prospectos.
- Durante la calificación, registra toda la información relevante usando updateProspectInfo (nivel de interés, presupuesto, datos de contacto).
- Cuando el prospecto exprese objeciones, regístralas con addProspectObjection y, una vez resueltas, márcalas como tales con resolveObjection.
- Cuando el prospecto esté calificado y muestre interés en un producto, usa createOffer del módulo de gestión de ofertas para crear una propuesta.
- Durante la negociación, actualiza el estado de la oferta con updateOfferStatus y registra notas importantes con addOfferNote.
- Si el prospecto solicita modificaciones, usa updateOfferTerms para ajustar la oferta según lo negociado. ` }
// ... resto de la implementación del mindset}Mejores prácticas
-
Descripciones claras: Las descripciones en
@descriptiondeben ser lo suficientemente detalladas para que el LLM comprenda exactamente cuándo invocar cada función y qué datos extraer. -
Validaciones apropiadas: Usa los decoradores de validación (
@isString,@isNumber,@min,@max, etc.) para asegurar la integridad de los datos antes de procesarlos. -
Valores por defecto: Siempre proporciona valores por defecto en las propiedades de las clases Request para evitar errores de inicialización.
-
Nombres descriptivos: Usa nombres de clases y métodos que describan claramente su propósito (ej:
CreateOfferRequest,updateOfferStatus). -
Manejo de errores: Implementa validaciones y manejo de errores apropiado en los métodos del módulo para proporcionar retroalimentación útil cuando algo falla.
-
Documentación adicional: Aunque las descripciones son para el LLM, considera agregar comentarios adicionales en el código para otros desarrolladores que trabajen en el proyecto.
Siguiente paso
Aprende a validar y transformar datos de entrada con el sistema de validación: Validación y Mapper.