DEV Community

Cover image for De 0 a sistema en 3 semanas: cómo construimos un CRM con agente IA para un intermediario financiero
Adrián Colom
Adrián Colom

Posted on

De 0 a sistema en 3 semanas: cómo construimos un CRM con agente IA para un intermediario financiero

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 };
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)