# Construyendo un Nodo n8n para Wasapi: De Caos a Arquitectura SOLID
Tabla de contenidos
Cómo transformé un proyecto caótico de integración de WhatsApp en una arquitectura SOLID que superó los primeros checks de verificación de n8n
El Desafío
Necesitaba integrar Wasapi (API de WhatsApp Business) con n8n, pero no existían nodos comunitarios. La documentación era escasa y cada error me llevaba a horas de debugging.
Los números que me motivaron:
- 25+ operaciones de WhatsApp Business API
- 0 nodos comunitarios existentes para Wasapi
Los Primeros Días: El Caos Total
Error #1: Todo en un Solo Archivo
// ❌ MI PRIMER INTENTO (DESASTRE TOTAL)export class WasapiNode { async execute() { // 500+ líneas de código mezclado // Validaciones hardcodeadas // Lógica de negocio mezclada con UI // Imposible de mantener }}¿Qué salió mal?
- Código imposible de leer
- Testing era una pesadilla
- Cada cambio rompía todo
- Debugging era como buscar una aguja en un pajar
El Momento Eureka: Descubriendo los DTOs
Después de evaluar varias opciones, me topé con el patrón DTO (Data Transfer Object).
// ✅ MI NUEVA APROXIMACIÓN CON DTOsexport class ContactDTO { static create(executeFunctions: IExecuteFunctions, index: number): ContactData { return { first_name: executeFunctions.getNodeParameter('first_name', index) as string, last_name: executeFunctions.getNodeParameter('last_name', index) as string, email: executeFunctions.getNodeParameter('email', index) as string, phone: executeFunctions.getNodeParameter('phone', index) as string, notes: executeFunctions.getNodeParameter('notes', index) as string, labels: executeFunctions.getNodeParameter('labels', index) as number[], custom_fields: {}, }; }}¿Por qué esto resolvió el problema?
- Separación de responsabilidades: Los DTOs solo se encargan de transformar datos
- Reutilización: Un DTO puede ser usado en múltiples operaciones
- Testing: Fácil de testear de forma aislada
- Mantenibilidad: Cambios en la estructura de datos se centralizan
Aplicando Principios SOLID
- Single Responsibility Principle (SRP)
// ✅ CADA CLASE TIENE UNA RESPONSABILIDAD ESPECÍFICAexport class ContactDTO { // Solo se encarga de transformar datos de contacto}
export class ContactValidator { // Solo se encarga de validar datos de contacto}
export class ContactService { // Solo se encarga de la lógica de negocio de contactos}- Open/Closed Principle (OCP)
// ✅ FÁCIL DE EXTENDER SIN MODIFICAR CÓDIGO EXISTENTEexport class OperationFactory { private static operations: Map<string, OperationHandler> = new Map([ [OPERATION_KEYS.CONTACT_CREATE, { execute: executeContactCreate }], [OPERATION_KEYS.CONTACT_GET, { execute: executeGetContact }], // Fácil agregar nuevas operaciones sin tocar las existentes ]);}- Dependency Inversion Principle (DIP)
// ✅ DEPENDENCIA DE ABSTRACCIONESexport class ContactService { constructor(private client: WasapiClient) {} // Depende de la interfaz, no de la implementación
async createContact(data: ContactData): Promise<any> { return await this.client.contacts.create(data); }}Los Desafíos Más Grandes
Desafío #1: La Naturaleza No Reactiva de n8n El problema: n8n no es reactivo como Make. No puedes crear campos dinámicos de la misma manera.
// ❌ LO QUE QUERÍA HACER (como en Make)const dynamicFields = await api.getFields();
// ✅ LO QUE TUVE QUE HACER EN n8nexport const contactCreateProperties: INodeProperties[] = [ { displayName: 'Custom Fields', name: 'custom_fields', type: 'fixedCollection', // ... configuración estática }];La solución: Crear un sistema de loadOptions robusto:
export async function getCustomFields(this: ILoadOptionsFunctions) { const client = await createClient(this);
if (!client) { return [{ name: '❌ First Configure Credentials', value: '' }]; }
try { const response = await client.customFields.getAll(); return response.data.map((field: any) => ({ name: field.field_name, value: field.field_name, })); } catch (error: any) { return handleLoadOptionsError(error); }}Desafío #2: Separación de Propiedades El problema: Con 25+ operaciones, las propiedades se volvieron un desastre.
La solución: Arquitectura modular:
// ✅ ARQUITECTURA MODULAR DE PROPIEDADESexport const allProperties: INodeProperties[] = [ resourceOptions, ...campaignsProperties, ...contactProperties, ...customFieldsProperties, ...labelsProperties, ...whatsappProperties, ...userProperties, ...agentsProperties,];Desafío #3: Migración del SDK a HTTP Directo
// ❌ MI PRIMER ENFOQUE (SDK)import { WasapiClient } from '@wasapi/js-sdk';
// ✅ ENFOQUE FINAL (HTTP DIRECTO)export class N8nHttpClient implements IN8nHttpClient { private baseURL: string; private headers: Record<string, string>;
constructor(apiKey: string, baseURL?: string, private executeFunctions?: IExecuteFunctions) { // Validate that API key is not empty if (!apiKey || apiKey.trim() === '') { throw new Error('WasAPI: API key is required and cannot be empty'); }
// Set default baseURL if not provided this.baseURL = baseURL || 'https://api-ws.wasapi.io/api/v1';
// Set headers this.headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }; }
// ...}Lecciones Aprendidas
-
Los DTOs No Son Solo para Transferir Datos Son la base de una arquitectura limpia y mantenible.
-
Los Principios SOLID No Son Teoría Son herramientas prácticas que resuelven problemas reales.
-
n8n No Es Reactivo Por Diseño - Y Eso Está Bien No intentes luchar contra la arquitectura de n8n. Abrázala y úsala a tu favor.
-
LoadOptions Requieren Planificación Cuidadosa No son reactivos, se ejecutan en tiempo de ejecución. Planifica bien tu flujo de datos.
-
La Separación de Propiedades Es Crítica Con 25+ operaciones, un archivo de propiedades se vuelve inmanejable rápidamente.
Reflexiones Finales
Este proyecto me enseñó que la arquitectura de software no es solo para empresas grandes. Incluso en proyectos pequeños, aplicar principios sólidos y patrones de diseño puede marcar la diferencia entre un código mantenible y un desastre total.
Los DTOs y los principios SOLID no son solo teoría - son herramientas prácticas que resuelven problemas reales y hacen que el desarrollo sea más placentero y eficiente.
Lo Más Valioso que Aprendí:
-
n8n tiene una filosofía diferente a Make - y eso está bien. Cada plataforma tiene sus fortalezas.
-
Los loadOptions requieren planificación cuidadosa - no son reactivos, pero con la arquitectura correcta, son increíblemente poderosos.
-
La separación de propiedades es crítica - con 25+ operaciones, la organización modular no es opcional, es necesaria.
-
A veces HTTP directo es mejor que SDKs - cuando necesitas control granular sobre errores y timeouts.
El Impacto Real:
- ✅ 25+ operaciones funcionando perfectamente
- ✅ Arquitectura escalable que puede crecer
- ✅ Código mantenible que otros pueden entender
- ✅ Primeros checks aprobados por el equipo de n8n
- 🟡 En proceso de verificación para la comunidad
¿Quieres Ver el Código? El proyecto completo está disponible en GitHub: n8n-nodes-wasapi