Sando Capital es un intermediario financiero en Mallorca. Javier, su fundador, no tenía web, ni sistema de captación, ni base de datos de clientes. Los leads llegaban por WhatsApp y cada conversación necesitaba 30 minutos de llamada manual para entender el perfil del inversor antes de saber si tenía sentido agendar una cita.
En 3 semanas le construimos un sistema completo: web a medida, agendado con Cal.com, un agente IA que cualifica leads antes de la llamada, y un CRM con base de datos centralizada.
Este artículo explica la arquitectura y las decisiones técnicas detrás de ese sistema.
El problema
El conocimiento del cliente vivía en WhatsApp. Cada lead era una conversación efímera que se perdía en chats sueltos. No había manera de:
- Saber cuántos leads llegaban al mes
- Recordar el perfil de un inversor sin volver a preguntarle
- Cualificar automáticamente antes de gastar 30 minutos en una llamada
El problema no era el producto. Era la ausencia de arquitectura.
Arquitectura del sistema
El stack:
- Nuxt 4 para la web pública (SSG para SEO, SPA para el panel CRM)
- NestJS 11 como API backend (REST + webhooks)
- PostgreSQL 17 para la base de datos de leads e inversores
- Redis + BullMQ para la cola de tareas del agente IA
- LangChain.js para el agente de calificación
- Cal.com para agendado (vía API + webhook)
- Prisma ORM para el acceso a datos
Todo construido sobre un monorepo template interno (Nuxt + NestJS + Postgres + Redis + BullMQ + Stripe + LangChain preconfigurados). No empezar de cero es lo que permite entregar en 3 semanas.
Componente 1: Web a medida + captación de leads
La web pública está construida con Nuxt 4 en modo SSG. Una sola página con un único CTA: agendar una cita. Sin menús complejos, sin blog, sin ruido.
El formulario de contacto envía un POST al backend de NestJS:
// nestjs/src/modules/leads/leads.controller.ts
@Controller('api/leads')
export class LeadsController {
constructor(private readonly leadsService: LeadsService) {}
@Post()
async createLead(@Body() dto: CreateLeadDto) {
const lead = await this.leadsService.create(dto);
// Encola el agente IA para calificación asíncrona
await this.leadsService.enqueueQualification(lead.id);
return { success: true, leadId: lead.id };
}
}
Cada lead se guarda en PostgreSQL con estado nuevo y se encola una tarea en BullMQ para que el agente IA lo cualifique.
Componente 2: Agendado con Cal.com
En vez de construir un sistema de calendario desde cero, integramos Cal.com via API. El inversor que llega a la web ve las franjas disponibles de Javier y elige una.
El webhook de Cal.com notifica al backend cuando se agenda una cita:
// nestjs/src/modules/calendly/calendly.controller.ts
@Controller('api/webhooks/cal')
export class CalWebhookController {
constructor(private readonly leadsService: LeadsService) {}
@Post('webhook')
async handleWebhook(@Body() payload: CalWebhookDto) {
if (payload.event === 'meeting.created') {
await this.leadsService.markScheduled(payload.data.email);
}
}
}
El lead pasa de estado cualificado a citado automáticamente. Javier no toca nada.
Componente 3: Agente IA de calificación con LangChain
Este es el componente que ahorra el 70% del tiempo de Javier. Antes de agendar, el inversor conversa con un agente IA que le pregunta sobre:
- Capital disponible
- Horizonte temporal (corto, medio, largo plazo)
- Experiencia previa con inversión
- Tolerancia al riesgo
- Objetivos financieros
El agente analiza las respuestas, sugiere el producto de inversión más adecuado y — si el perfil no encaja — descarta el lead sin agendar cita.
// nestjs/src/modules/ai/qualification.agent.ts
import { ChatOpenAI } from '@langchain/openai';
import { AgentExecutor, createToolCallingAgent } from 'langchain/agents';
import { z } from 'zod';
const qualificationSchema = z.object({
capital: z.number().describe('Capital disponible en euros'),
horizonte: z.enum(['corto', 'medio', 'largo']),
experiencia: z.enum(['ninguna', 'basica', 'avanzada']),
toleranciaRiesgo: z.enum(['conservador', 'moderado', 'agresivo']),
productoSugerido: z.string().describe('Producto de inversión sugerido'),
descarta: z.boolean().describe('True si el lead no encaja'),
});
const llm = new ChatOpenAI({ modelName: 'gpt-4o', temperature: 0.3 });
const executor = new AgentExecutor({
agent: createToolCallingAgent({ llm, tools, prompt }),
tools: [],
});
async function qualifyLead(leadId: string, conversation: string) {
const result = await executor.invoke({
input: `Analiza esta conversación con un inversor y cualifícalo:\n${conversation}`,
});
const parsed = qualificationSchema.parse(JSON.parse(result.output));
// Guarda el análisis en el lead
await prisma.lead.update({
where: { id: leadId },
data: {
estado: parsed.descarta ? 'descartado' : 'cualificado',
cualificacion: parsed,
},
});
return parsed;
}
El agente corre en un worker de BullMQ para no bloquear la API. Javier recibe la ficha del inversor ya analizada antes de la llamada.
Componente 4: CRM + base de datos centralizada
El panel CRM está construido con Nuxt 4 en modo SPA. Muestra:
- Lista de leads con estado (nuevo, cualificado, citado, cerrado, descartado)
- Ficha de cada inversor con su cualificación
- Historial de interacciones
- Producto sugerido por el agente IA
Todo se alimenta del mismo PostgreSQL. Una única fuente de verdad. Nada en WhatsApp, nada en Excel.
// prisma/schema.prisma
model Lead {
id String @id @default(cuid())
nombre String
email String @unique
telefono String?
estado Estado @default(nuevo)
cualificacion Json?
citaId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Estado {
nuevo
cualificado
citado
cerrado
descartado
}
Por qué 3 semanas y no 3 meses
No es velocidad por velocidad. Es no repetir trabajo que ya está hecho. La autenticación, la cola de tareas, el ORM, los webhooks de Cal.com, la capa de IA: todos existen como módulos en el monorepo template. Se ensambla y se adapta.
Si quieres ver más detalles de la arquitectura completa, escribí un post más extenso en el blog de SOM-OS donde cubro también las decisiones de pricing y cómo estructuramos el mantenimiento.
Lección clave
El agente IA funciona porque hay arquitectura debajo. Sin base de datos, sin Cal.com, sin web — el agente no tendría dónde leer ni dónde escribir. No puedes enchufar IA sobre caos. Primero ordenas, luego digitalizas, luego despliegas inteligencia.
Si estás construyendo un sistema similar y quieres comparar enfoques, tenemos un mapa de procesos y herramientas que te puede servir como punto de partida.
Si quieres ver cómo aplicamos esto en sistemas reales, échale un vistazo a SOM-OS — construimos sistemas operativos de negocio con IA en Mallorca.
Top comments (0)