Negocios7 min de lectura·30 de abril de 2025

Scoring automático de leads: cómo calificamos prospectos con IA

Recibir 50 leads al mes suena bien hasta que te das cuenta de que la mitad son estudiantes haciendo una tarea, el 20% son competidores curiosos y solo el 30% son prospectos reales con presupuesto. Este artículo explica cómo construimos un sistema que clasifica automáticamente cada lead con un puntaje de 0 a 100.

E

Edward Díaz

Edwsystem

El problema con los leads sin filtro

Cuando el formulario de contacto empieza a llegar bien posicionado, aparece un problema nuevo: demasiados leads de calidad variable. El mes pasado recibimos 50 formularios. De esos, 12 tenían presupuesto definido, 8 describieron el proyecto con detalle suficiente para cotizar, y solo 5 terminaron siendo clientes. Los otros 45 requirieron tiempo —llamadas, correos, respuestas— para descubrir que no eran el cliente adecuado.

Como fundador único, ese tiempo es el recurso más escaso. Si paso dos horas del lunes respondiendo leads fríos, son dos horas que no estoy construyendo producto o cerrando proyectos reales. La solución obvia es priorizar automáticamente: que el sistema me diga cuál lead merece atención inmediata y cuál puede esperar.

Qué es lead scoring (y por qué funciona)

Lead scoring es asignarle un puntaje numérico a cada prospecto basado en señales de intención de compra. No es magia ni IA complicada en su forma básica: es una función que recibe los datos del formulario y devuelve un número entre 0 y 100.

La intuición es simple: alguien que especifica su presupuesto, describe el proyecto con detalle y viene de la herramienta de cotización está mucho más cerca de comprar que alguien que escribe "hola necesito una app" sin más contexto. El scoring formaliza ese juicio en código reproducible y consistente.

Regla empírica: en servicios B2B de software, un lead que completa más de 3 campos opcionales del formulario convierte 4× más que uno que solo llena los obligatorios. El esfuerzo al llenar el formulario es una señal real de intención.

Nuestros criterios de puntuación

Después de revisar los últimos 150 leads y cruzarlos con quiénes terminaron siendo clientes, identificamos las variables con mayor correlación. Cada criterio suma puntos al score base de 0:

SeñalPuntos
Proporcionó presupuesto+20
Presupuesto > $5,000 USD+15
Tipo de proyecto especificado (no "otro")+15
Proporcionó número de teléfono+10
Mensaje de más de 100 caracteres+10
Mensaje de más de 200 caracteres+15 (acumulativo)
Fuente: herramienta de estimación+20
Fuente: chatbot+15
Fuente: formulario de contacto+10

La fuente importa mucho. Alguien que llega a la herramienta de estimación de presupuesto ya pasó por tres pasos intencionales: buscó el servicio, encontró la herramienta y la usó. Ese comportamiento vale el doble que alguien que llenó el formulario de contacto genérico.

Clasificación por temperatura

El número solo tiene valor si guía una acción concreta. Por eso mapeamos rangos a temperaturas con protocolos de seguimiento distintos:

HOT ≥70

Responder dentro de 1 hora. Llamada directa si hay teléfono. Este lead tiene alta probabilidad de cierre y probablemente está evaluando otras opciones en paralelo. La velocidad de respuesta es diferenciador.

WARM 40–69

Responder dentro del día. Hay intención pero falta información. El correo de respuesta debe incluir preguntas concretas para calificar mejor: ¿tienes fecha objetivo? ¿ya tienes diseño? ¿es un proyecto nuevo o una mejora?

COLD <40

Responder dentro de 48–72 horas con una respuesta automatizada que pide más contexto. Si no responden o el contexto adicional no mejora el score, entran a una secuencia de nurturing por correo.

La implementación: TypeScript puro

Para el scoring base no necesitas ningún modelo de IA. Es una función determinista que corre en milisegundos. Aquí están los tipos y la función completa que usamos:

// types/lead.ts

export type LeadSource = 'estimator' | 'chatbot' | 'contact_form' | 'direct'

export type LeadTemperature = 'HOT' | 'WARM' | 'COLD'

export interface Lead {
  id: string
  name: string
  email: string
  phone?: string
  message: string
  projectType?: string
  budget?: number
  source: LeadSource
  createdAt: Date
  score?: number
  temperature?: LeadTemperature
}
// lib/lead-scoring.ts

import type { Lead, LeadTemperature } from '@/types/lead'

interface ScoringResult {
  score: number
  temperature: LeadTemperature
  breakdown: Record<string, number>
}

export function scoreLead(lead: Lead): ScoringResult {
  const breakdown: Record<string, number> = {}
  let score = 0

  // Presupuesto
  if (lead.budget !== undefined && lead.budget > 0) {
    breakdown['budget_provided'] = 20
    score += 20

    if (lead.budget > 5000) {
      breakdown['budget_high'] = 15
      score += 15
    }
  }

  // Tipo de proyecto especificado
  if (lead.projectType && lead.projectType !== 'other' && lead.projectType !== 'otro') {
    breakdown['project_type'] = 15
    score += 15
  }

  // Teléfono proporcionado
  if (lead.phone && lead.phone.trim().length > 0) {
    breakdown['phone_provided'] = 10
    score += 10
  }

  // Longitud del mensaje
  const msgLength = lead.message.trim().length
  if (msgLength > 200) {
    breakdown['message_detailed'] = 15
    score += 15
  } else if (msgLength > 100) {
    breakdown['message_medium'] = 10
    score += 10
  }

  // Fuente
  const sourcePoints: Record<string, number> = {
    estimator: 20,
    chatbot: 15,
    contact_form: 10,
    direct: 5,
  }
  const sourceScore = sourcePoints[lead.source] ?? 0
  breakdown[`source_${lead.source}`] = sourceScore
  score += sourceScore

  // Clamp 0–100
  score = Math.min(100, Math.max(0, score))

  const temperature: LeadTemperature =
    score >= 70 ? 'HOT' : score >= 40 ? 'WARM' : 'COLD'

  return { score, temperature, breakdown }
}

El breakdown es clave para debugging: cuando un lead tiene score inesperadamente bajo, puedes ver exactamente qué criterios no cumplió. También sirve para mejorar el formulario con el tiempo —si notas que el 80% de los leads no especifica tipo de proyecto, quizás ese campo debería ser obligatorio.

Cuándo agregar IA al scoring

La función de arriba es suficiente si tienes menos de 200 leads al mes. Pero hay señales que los campos estructurados no capturan bien: el tono del mensaje, si mencionan un competidor ("ya estamos trabajando con X pero queremos migrar"), si describen urgencia ("necesitamos lanzar antes de diciembre") o si el mensaje sugiere que el proyecto ya tiene financiamiento.

Para esos casos, después del scoring base, enviamos el texto del mensaje a un modelo de lenguaje que devuelve un ajuste de ±10 puntos con una justificación en una oración. El prompt es intencionalmente simple:

// lib/ai-scoring.ts

export async function aiScoreAdjustment(message: string): Promise<{
  adjustment: number  // -10 a +10
  reason: string
}> {
  const prompt = `
Analiza este mensaje de un prospecto de software y devuelve un ajuste de score.

Mensaje: "${message}"

Responde SOLO con JSON válido en este formato exacto:
{
  "adjustment": <número entre -10 y 10>,
  "reason": "<una oración explicando el ajuste>"
}

Criterios:
- +10: urgencia clara, presupuesto implícito alto, proyecto concreto ya iniciado
- +5: buena descripción técnica, cliente parece experimentado
- 0: mensaje neutro, sin señales adicionales
- -5: proyecto muy pequeño o experimental, sin claridad
- -10: señales de que no es un cliente real (testing, spam, competidor)
`

  const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      model: 'anthropic/claude-haiku',
      messages: [{ role: 'user', content: prompt }],
      max_tokens: 100,
    }),
  })

  const data = await response.json()
  const text = data.choices[0].message.content

  try {
    return JSON.parse(text)
  } catch {
    return { adjustment: 0, reason: 'Error al procesar el análisis' }
  }
}

Costo real: Claude Haiku procesa 200 mensajes por aproximadamente $0.04 USD. El costo de no responder a tiempo a un lead HOT es órdenes de magnitud mayor. Activamos el análisis AI solo para leads con score base mayor a 30 —los COLD extremos no necesitan análisis adicional.

El dashboard: leads ordenados por urgencia

El scoring no vale nada si no lo ves. Construimos un panel interno en Next.js donde los leads aparecen siempre ordenados por score descendente. La vista principal muestra solo los HOT y WARM de las últimas 72 horas —esos son los que importan ahora mismo.

La query es directa:

// Leads prioritarios para el día
const urgentLeads = await db.lead.findMany({
  where: {
    temperature: { in: ['HOT', 'WARM'] },
    createdAt: { gte: subHours(new Date(), 72) },
    respondedAt: null,
  },
  orderBy: { score: 'desc' },
})

Cuando el respondedAt es null y el lead es HOT con más de 2 horas de antigüedad, aparece una alerta visual en el panel. Eso elimina el "se me olvidó responder" como excusa.

Resultados después de 60 días

Antes del sistema, el tiempo de respuesta promedio era inconsistente: algunos leads recibían respuesta en 30 minutos, otros esperaban dos días dependiendo de qué tan ocupado estuviera. Sin un sistema, priorizaba por orden de llegada, que es la peor estrategia posible.

<1h

Tiempo respuesta HOT

3.2×

Mejora en tasa de cierre HOT

40%

Reducción en tiempo de ventas

El número más importante no es la tasa de cierre sino el tiempo recuperado. Al no perseguir leads COLD con el mismo esfuerzo que los HOT, ahorramos aproximadamente 6 horas semanales que antes se iban en correos que nunca iban a ningún lado.

El insight clave: un número beats el instinto

El instinto de un vendedor experimentado puede superar a un modelo básico. Pero un fundador único que también construye producto, responde soporte y hace demos no tiene el ancho de banda para aplicar ese instinto de forma consistente a las 11 PM de un martes.

Un score de 82 en la bandeja de entrada me dice, sin que yo tenga que pensar nada: este lead necesita atención ahora. Un score de 18 me dice que puedo responder mañana con la plantilla estándar. Ese contexto instantáneo vale más que cualquier dashboard sofisticado.

Si quieres implementar esto: empieza con la función de scoring puro en TypeScript —no necesitas IA todavía. Ponla en producción, observa los scores por 30 días y ajusta los pesos según qué leads terminan convirtiéndose. Después, y solo si el volumen lo justifica, agrega el análisis de texto con IA.

El código de este artículo es el mismo que corre en producción en Edwsystem. Si quieres implementar algo similar para tu negocio o integrarlo en tu CRM actual, cuéntanos sobre tu proyecto y lo armamos juntos.

¿Te fue útil este artículo?

Déjame tu email y te aviso cuando publique nuevos artículos técnicos.