El SEO atrae tráfico. El email marketing convierte. Pero entre medias hay un abismo: el 80% de los leads que llegan desde SEO no compran inmediatamente. No están listos. O no confían lo suficiente. O simplemente se olvidan.
- Pero entre medias hay un abismo: el 80% de los leads que llegan desde SEO no compran inmediatamente.
Aquí entra el lead nurturing: el arte de cultivar leads a través del tiempo con contenido relevante, en el momento justo. Manualmente es imposible de escalar. Automatizado, es una máquina de conversión.
Por qué Lead Nurturing es crítico para SEO
El problema:
- Invieres en SEO para atraer tráfico
- El 70-80% no convierte en la primera visita
- Perdes esos leads en el olvido (o van a la competencia)
- ROI del SEO se ve afectado: "no funciona, bajo ROI"
La solución:
- Lead nurturing automatizado: Sequences de email basadas en comportamiento
- Personalización: Contenido relevante según etapa del funnel
- Timing inteligente: Emails en el momento justo, no spam
- Medición continua: Qué funciona, qué no funciona, optimizar
Qué vas a construir
Vamos a crear un sistema completo de lead nurturing que incluye:
- Integración SEO → Email: Capturar intención de búsqueda automáticamente
- Segmentación dinámica: Clasificar leads según comportamiento
- 4 sequences automatizadas: Welcome, Re-engagement, Behavioral, Abandoned Content
- Lead scoring: Puntuación basada en interacción
- Métricas y ROI: Dashboard para medir éxito
Fundamentos de Lead Nurturing
Qué es (y qué NO es)
Lead nurturing: Proceso de cultivar relaciones con leads a través del tiempo, entregando valor en cada interacción.
NO es:
- No es spam masivo (enviar lo mismo a todos)
- No es email blasts indiscriminados
- No es newsletter genérico
SÍ es:
- Contenido hiper-personalizado
- Timing basado en comportamiento del usuario
- Progresivo: educa → demuestra → convierte
Buyer Journey en SEO
Los leads de SEO vienen con diferente intención según cómo llegaron:
Diferencia vs Email Marketing Tradicional
Framework Base: Estructura del Sistema
Estructura de proyecto
lead-nurturing/
├── .env # Variables de entorno
├── requirements.txt # Dependencias
├── config.py # Configuración centralizada
├── models/
│ ├── lead.py # Modelo de datos Lead
│ ├── sequence.py # Modelo de datos Sequence
│ └── email.py # Modelo de datos Email
├── services/
│ ├── capture.py # Capturar leads SEO
│ ├── segment.py # Segmentación automática
│ ├── score.py # Lead scoring
│ └── esp.py # Integración Email API
├── sequences/
│ ├── welcome.py # Sequence 1: Welcome series
│ ├── reengagement.py # Sequence2: Reactivar leads fríos
│ ├── behavioral.py # Sequence3: Triggers de contenido
│ └── abandoned.py # Sequence4: Recuperar abandonados
├── templates/
│ ├── welcome/ # Plantillas HTML welcome
│ ├── reengagement/ # Plantillas HTML reengagement
│ ├── behavioral/ # Plantillas HTML behavioral
│ └── abandoned/ # Plantillas HTML abandoned
└── main.py # Orquestador principalrequirements.txt
pandas==2.1.0
requests==2.31.0
python-dotenv==1.0.0
jinja2==3.1.2
sendgrid==6.10.0 # O mailchimp==3.0.0
sqlalchemy==2.0.0 # Para database opcionalmodels/lead.py - Modelo de datos
from datetime import datetime
from enum import Enum
class LeadStage(Enum):
NEW = "new"
ENGAGED = "engaged"
QUALIFIED = "qualified"
CUSTOMER = "customer"
CHURNED = "churned"
class LeadIntent(Enum):
AWARENESS = "awareness" # Búsqueda informativa
CONSIDERATION = "consideration" # Comparativa
DECISION = "decision" # Lista para comprar
UNKNOWN = "unknown"
class Lead:
def __init__(self, email, first_name=None, source="seo", utm_params=None):
self.email = email
self.first_name = first_name or email.split('@')[0]
self.source = source
self.utm_params = utm_params or {}
# Estados
self.stage = LeadStage.NEW
self.intent = self._detect_intent_from_utm()
# Metadata
self.created_at = datetime.now()
self.last_activity = self.created_at
self.emails_sent = 0
self.emails_opened = 0
self.pages_visited = []
self.score = 0
def _detect_intent_from_utm(self):
"""Detecta intención basada en parámetros UTM"""
utm_content = self.utm_params.get('utm_content', '').lower()
if any(kw in utm_content for kw in ['cómo', 'qué es', 'guía', 'tutorial']):
return LeadIntent.AWARENESS
if any(kw in utm_content for kw in ['mejor', 'precio', 'vs', 'comparar', 'alternativa']):
return LeadIntent.CONSIDERATION
if any(kw in utm_content for kw in ['contratar', 'comprar', 'agencia', 'servicio']):
return LeadIntent.DECISION
return LeadIntent.UNKNOWN
def to_dict(self):
return {
'email': self.email,
'first_name': self.first_name,
'source': self.source,
'stage': self.stage.value,
'intent': self.intent.value,
'score': self.score,
'created_at': self.created_at.isoformat(),
'last_activity': self.last_activity.isoformat()
}Integración SEO → Email: Captura y Segmentación
El primer paso es capturar leads que vienen de SEO con suficiente contexto para personalizar.
services/capture.py - Capturar leads con contexto SEO
from urllib.parse import parse_qs
from models.lead import Lead, LeadIntent
def capture_lead_from_utm(utm_params, email, first_name=None):
"""
Captura lead con parámetros UTM de Google Analytics/Ads
Args:
utm_params: Dict con parámetros UTM
email: Email del lead
first_name: Nombre opcional
Returns:
Objeto Lead con contexto SEO detectado
"""
lead = Lead(
email=email,
first_name=first_name,
source="seo",
utm_params=utm_params
)
# Detectar intención basada en UTM content
content = utm_params.get('utm_content', '').lower()
if 'blog' in content or 'artículo' in content:
lead.intent = LeadIntent.AWARENESS
lead.stage = LeadStage.NEW
elif 'servicio' in content or 'producto' in content:
lead.intent = LeadIntent.CONSIDERATION
elif 'contacto' in content or 'presupuesto' in content:
lead.intent = LeadIntent.DECISION
lead.stage = LeadStage.QUALIFIED
# Extraer metadata adicional
lead.utm_params['source'] = utm_params.get('utm_source', 'organic')
lead.utm_params['medium'] = utm_params.get('utm_medium', 'none')
lead.utm_params['campaign'] = utm_params.get('utm_campaign', 'none')
return lead
def capture_lead_from_gsc(query, email, first_name=None):
"""
Captura lead basado en query de búsqueda GSC
Args:
query: Query de búsqueda que trajo el tráfico
email: Email del lead
first_name: Nombre opcional
Returns:
Objeto Lead con intención detectada
"""
lead = Lead(
email=email,
first_name=first_name,
source="gsc_organic",
utm_params={'query': query}
)
# Detectar intención desde query
query_lower = query.lower()
# Patrones de intención
awareness_patterns = ['cómo', 'qué es', 'guía', 'tutorial', 'mejor forma de']
consideration_patterns = ['mejor', 'precio', 'vs', 'comparar', 'alternativa a']
decision_patterns = ['contratar', 'comprar', 'agencia', 'servicio de', 'proveedor']
if any(pattern in query_lower for pattern in awareness_patterns):
lead.intent = LeadIntent.AWARENESS
elif any(pattern in query_lower for pattern in consideration_patterns):
lead.intent = LeadIntent.CONSIDERATION
elif any(pattern in query_lower for pattern in decision_patterns):
lead.intent = LeadIntent.DECISION
lead.stage = LeadStage.QUALIFIED
else:
lead.intent = LeadIntent.UNKNOWN
return leadservices/score.py - Lead scoring basado en comportamiento
from models.lead import LeadStage
def calculate_lead_score(lead):
"""
Calcula score de lead (0-100) basado en interacción
Factores:
- Emails abiertos: +30 puntos máximo
- Páginas visitadas: +20 puntos máximo
- Tiempo desde última actividad: -10 puntos por semana de inactividad
- Intención de búsqueda: +20 puntos (DECISION > CONSIDERATION > AWARENESS)
- Etapa del funnel: +15 puntos (CUSTOMER > QUALIFIED > ENGAGED > NEW)
"""
score = 0
# 1. Engagement (emails abiertos)
if lead.emails_sent > 0:
open_rate = lead.emails_opened / lead.emails_sent
score += open_rate * 30 # Máximo 30 puntos
# 2. Profundidad (páginas visitadas)
unique_pages = len(set(lead.pages_visited))
score += min(unique_pages * 5, 20) # Máximo 20 puntos
# 3. Recencia (actividad reciente)
days_inactive = (datetime.now() - lead.last_activity).days
recency_score = max(0, 100 - days_inactive * 10)
score += recency_score * 0.15 # Máximo 15 puntos
# 4. Intención de búsqueda
intent_scores = {
'awareness': 5,
'consideration': 15,
'decision': 20,
'unknown': 0
}
score += intent_scores.get(lead.intent.value, 0)
# 5. Etapa del funnel
stage_scores = {
'new': 0,
'engaged': 5,
'qualified': 15,
'customer': 20
}
score += stage_scores.get(lead.stage.value, 0)
lead.score = min(int(score), 100) # Máximo 100
return lead.score
def segment_leads(leads):
"""
Segmenta leads en categorías para nurturing
Returns:
Dict con listas de leads por segmento
"""
segments = {
'hot_leads': [], # Score > 70, etapa DECISION
'warm_leads': [], # Score 40-70, etapa CONSIDERATION
'cold_leads': [], # Score < 40, etapa AWARENESS
'dormant': [], # Sin actividad > 30 días
'ready_to_buy': [] # Score > 80, etapa DECISION, activo recientemente
}
for lead in leads:
score = calculate_lead_score(lead)
# Clasificación principal
if score > 70 and lead.stage.value == 'qualified':
if (datetime.now() - lead.last_activity).days <= 7:
segments['ready_to_buy'].append(lead)
segments['hot_leads'].append(lead)
elif score > 40:
segments['warm_leads'].append(lead)
else:
segments['cold_leads'].append(lead)
# Detectar inactivos
if (datetime.now() - lead.last_activity).days > 30:
segments['dormant'].append(lead)
return segmentsSequence 1: Welcome Series
Propósito: 5-email sequence para nuevos leads que acaban de llegar. Educa, construye confianza, prepara para venta.
Estructura:
- Email 1 (Día 0): Welcome + contenido valor inmediato según búsqueda
- Email 2 (Día 2): Introducción a tu empresa + social proof
- Email 3 (Día 4): Contenido educativo (según intención de búsqueda)
- Email 4 (Día 7): Case study relevante + testimonio
- Email 5 (Día 10): Oferta suave + CTA
sequences/welcome.py - Script completo
from jinja2 import Template
from datetime import datetime, timedelta
from services.esp import send_email
def render_welcome_email(lead, email_number, content_type):
"""
Renderiza email de welcome series
Args:
lead: Objeto Lead
email_number: 1-5
content_type: 'awareness' | 'social_proof' | 'education' | 'case_study' | 'offer'
Returns:
HTML del email personalizado
"""
templates = {
1: Template('''
<h1>Bienvenido a {{ company_name }}</h1>
<p>Gracias por suscribirte, {{ first_name }}.</p>
{% if search_query %}
<p>Veo que estás interesado en <strong>{{ search_query }}</strong>. He preparado recursos específicos sobre este tema.</p>
{% endif %}
<p>En los próximos días, te compartiré:</p>
<ul>
<li>Guías exclusivas sobre {{ search_query }}</li>
<li>Case studies de clientes como tú</li>
<li>Ofertas especiales para nuevos suscriptores</li>
</ul>
<p>Si no puedes esperar, aquí tienes el recurso principal:</p>
<p><a href="{{ main_resource_url }}">{{ main_resource_title }}</a></p>
'''),
2: Template('''
<h2>Un poco sobre nosotros</h2>
<p>Hola {{ first_name }},</p>
<p>Quiero presentarte {{ company_name }}. Somos especialistas en {{ main_service }} con {{ years_experience }} años de experiencia.</p>
<h3>Lo que nos diferencia:</h3>
<ul>
{% for differentiator in unique_value_props %}
<li>{{ differentiator }}</li>
{% endfor %}
</ul>
<p><strong>Nuestro compromiso:</strong> {{ guarantee_statement }}</p>
<p>Conoce nuestros resultados:</p>
<p><a href="{{ case_study_url }}">Ver case study →</a></p>
'''),
3: Template('''
<h2>{{ resource_title }}: Guía Completa</h2>
<p>Hola {{ first_name }},</p>
<p>En {{ company_name }} creemos que la mejor forma de aprender es haciendo. Por eso, he preparado esta guía paso a paso sobre {{ search_query }}.</p>
<h2>Lo que aprenderás:</h2>
<ol>
{% for learning_point in learning_points %}
<li>{{ learning_point }}</li>
{% endfor %}
</ol>
<h2>Paso 1: Fundamentos</h2>
<p>{{ step1_content }}</p>
<h2>Paso 2: Estrategia</h2>
<p>{{ step2_content }}</p>
<h2>Paso 3: Implementación</h2>
<p>{{ step3_content }}</p>
<p>Si necesitas ayuda con la implementación, responde a este email. Estoy aquí para ayudar.</p>
'''),
4: Template('''
<h2>Cómo {{ client_name }} logró {{ result }}</h2>
<p>Hola {{ first_name }},</p>
<p>Quería compartirte una historia real. {{ client_name }} tenía el mismo desafío que tú: {{ client_challenge }}.</p>
<h3>Antes:</h3>
<p>{{ before Situation }}</p>
<h3>Qué hicimos:</h3>
<p>{{ solution_implemented }}</p>
<h3>Resultados:</h3>
<ul>
{% for result in results_achieved %}
<li>{{ result }}</li>
{% endfor %}
</ul>
<p><em>Testimonio verificado.</em></p>
<p>¿Quieres resultados similares? Respondamos y hablemos.</p>
'''),
5: Template('''
<h2>Oferta especial para ti, {{ first_name }}</h2>
<p>Gracias por seguir nuestra serie, {{ first_name }}.</p>
<p>Como valoro tu interés, quiero hacerte una oferta exclusiva:</p>
<div style="background: #f0f8ff; padding: 20px; border-left: 4px solid #4285f4;">
<h3>{{ offer_title }}</h3>
<p>{{ offer_description }}</p>
<p><strong>Valor habitual:</strong> <del>{{ regular_price }}</del></p>
<p><strong>Precio especial:</strong> <strong>{{ special_price }}</strong></p>
<p><strong>Ahorro:</strong> {{ savings }}</p>
<p><strong>Válido por:</strong> {{ validity_days }} días</p>
</div>
<p><strong>Código:</strong> {{ promo_code }}</p>
<p><a href="{{ cta_url }}">{{ cta_button_text }} →</a></p>
<p><em>Oferta válida hasta {{ expiry_date }}.</em></p>
''')
}
template = templates.get(email_number)
# Renderizar con datos del lead
html = template.render(
company_name="4SEO.es",
first_name=lead.first_name,
search_query=lead.utm_params.get('query', 'SEO'),
main_resource_url="https://4seo.es/blog",
years_experience=12,
main_service="automatización SEO",
unique_value_props=[
"Enfoque data-driven, no intuiciones",
"Clientes que duplican tráfico en 6 meses",
"Especialistas en sitios de 100K+ páginas"
],
guarantee_statement="Mejoramos tus rankings o te devolvemos tu dinero.",
case_study_url="https://4seo.es/casos-exito",
client_name="Empresa X",
client_challenge="bajo tráfico y conversiones",
before Situation="10 visitas/día, 0.5% conversión",
solution_implemented="Auditoría técnica + estrategia de contenidos",
results_achieved=[
"+300% tráfico en 6 meses",
"CTR de 0.5% a 4.5%",
"15 leads cualificados/mes"
],
offer_title="Auditoría SEO Gratuita",
offer_description="Análisis completo de tu sitio + estrategia personalizada",
regular_price="497€",
special_price="0€",
savings="497€",
validity_days=7,
promo_code="BIENVENIDO4SEO",
cta_url="https://4seo.es/contacto",
cta_button_text="Solicitar Auditoría Gratuita",
expiry_date=(datetime.now() + timedelta(days=14)).strftime('%d/%m/%Y')
)
return html
def schedule_welcome_sequence(lead):
"""Programa welcome series para un lead
Args:
lead: Objeto Lead
Returns:
Dict con scheduled emails
"""
schedule = []
# Email 1: Inmediato
schedule.append({
'lead_id': lead.email,
'email_number': 1,
'send_at': datetime.now(),
'template_type': 'welcome'
})
# Email 2: +2 días
schedule.append({
'lead_id': lead.email,
'email_number': 2,
'send_at': datetime.now() + timedelta(days=2),
'template_type': 'social_proof'
})
# Email 3: +4 días
schedule.append({
'lead_id': lead.email,
'email_number': 3,
'send_at': datetime.now() + timedelta(days=4),
'template_type': 'education'
})
# Email 4: +7 días
schedule.append({
'lead_id': lead.email,
'email_number': 4,
'send_at': datetime.now() + timedelta(days=7),
'template_type': 'case_study'
})
# Email 5: +10 días
schedule.append({
'lead_id': lead.email,
'email_number': 5,
'send_at': datetime.now() + timedelta(days=10),
'template_type': 'offer'
})
return scheduleSequence 2: Re-engagement
Propósito: Reactivar leads que han estado inactivos por más de 30 días. Estrategia suave, no agresiva.
sequences/reengagement.py - Script
from datetime import datetime, timedelta
from jinja2 import Template
def render_reengagement_email(lead, context_type="check_in"):
"""
Renderiza email de re-engagement
Args:
lead: Objeto Lead
context_type: 'check_in' | 'value' | 'we_miss_you'
Returns:
HTML del email
"""
templates = {
'check_in': Template('''
<h2>¿Cómo va todo, {{ first_name }}?</h2>
<p>Hace {{ days_inactive }} días que no vemos por aquí.</p>
<p>Estuve pensando en ti. Cuando te suscribiste, estabas interesado en {{ search_query }}.</p>
<p>¿Sigues trabajando en eso? ¿Encontraste lo que buscabas?</p>
<p>Si necesitas ayuda, estoy aquí. Responde a este email y te ayudaré.</p>
'''),
'value': Template('''
<h2>Algo nuevo desde la última vez</h2>
<p>Hola {{ first_name }},</p>
<p>Ha pasado un tiempo, así que quería compartir actualizaciones sobre {{ search_query }}:</p>
<h3>Novedades:</h3>
<ul>
{% for update in updates %}
<li>{{ update }}</li>
{% endfor %}
</ul>
<p>Quizás estas actualizaciones te interesan:</p>
<ul>
{% for resource in new_resources %}
<li><a href="{{ resource.url }}">{{ resource.title }}</a></li>
{% endfor %}
</ul>
'''),
'we_miss_you': Template('''
<h2>Te echamos de menos, {{ first_name }}</h2>
<p>Es verdad. Hace {{ days_inactive }} días que no interactuamos.</p>
<p>No quiero ser molesto. Solo quería decirte que si vuelves, tengo nuevos recursos para ti:</p>
<ul>
{% for resource in returning_resources %}
<li><strong>{{ resource.title }}:</strong> <a href="{{ resource.url }}">Ver ahora</a></li>
{% endfor %}
</ul>
<p>Espero verte pronto.</p>
<p>Si no, te entiendo. Un saludo.</p>
''')
}
template = templates.get(context_type)
days_inactive = (datetime.now() - lead.last_activity).days
html = template.render(
first_name=lead.first_name,
days_inactive=days_inactive,
search_query=lead.utm_params.get('query', 'SEO'),
updates=[
"Nuevo artículo sobre técnicas avanzadas",
"Cambio en algoritmo de Google",
"Nueva herramienta de análisis"
],
new_resources=[
{
"title": "Guía actualizada 2026",
"url": "https://4seo.es/blog"
}
],
returning_resources=[
{
"title": "Webinar exclusivo: Tendencias SEO 2026",
"url": "https://4seo.es/webinar"
}
]
)
return html
def identify_dormant_leads(leads, inactive_days=30):
"""Identifica leads inactivos para re-engagement
Args:
leads: Lista de objetos Lead
inactive_days: Días sin actividad para considerar inactivo
Returns:
Lista de leads inactivos
"""
dormant = []
cutoff_date = datetime.now() - timedelta(days=inactive_days)
for lead in leads:
if lead.last_activity < cutoff_date:
dormant.append(lead)
return dormant
# Script de re-engagement
if __name__ == '__main__':
from services.capture import capture_lead_from_gsc
from services.score import calculate_lead_score
# Simular carga de leads
leads = [
capture_lead_from_gsc("auditoría seo barcelona", "[email protected]", "María"),
capture_lead_from_gsc("servicios seo", "[email protected]", "Juan")
]
# Marcar como inactivos (para demo)
from datetime import timedelta
leads[0].last_activity = datetime.now() - timedelta(days=45)
# Identificar inactivos
dormant = identify_dormant_leads(leads)
print(f"{len(dormant)} leads inactivos encontrados")
for lead in dormant:
print(f"\n📧 Enviando re-engagement a {lead.email}...")
# Enviar email de check-in
html = render_reengagement_email(lead, 'check_in')
# send_email(lead.email, "¿Cómo va?", html)
print(f"Email enviado a {lead.email}")Sequence 3: Behavioral Triggers
Propósito: Emails automáticos activados por comportamiento específico del usuario (página visitada, descarga, click).
sequences/behavioral.py - Script
from jinja2 import Template
def trigger_behavioral_email(lead, trigger_type, context_data):
"""
Dispara y renderiza email basado en trigger conductual
Args:
lead: Objeto Lead
trigger_type: 'page_visit' | 'download' | 'cart_abandon' | 'quote_request'
context_data: Datos adicionales del trigger
Returns:
HTML del email personalizado
"""
if trigger_type == 'page_visit':
template = Template('''
<h2>Vi que te interesa {{ page_title }}</h2>
<p>Hola {{ first_name }},</p>
<p>Noté que visitaste <strong>{{ page_title }}</strong>. Contiene información valiosa sobre {{ page_topic }}.</p>
<p>Basado en tu interés, creo que te gustaría:</p>
<ul>
{% for next_step in suggested_next_steps %}
<li>{{ next_step }}</li>
{% endfor %}
</ul>
<p>¿Quieres que te lo envíe?</p>
<p><a href="{{ download_link }}">Descargar recurso →</a></p>
''')
html = template.render(
first_name=lead.first_name,
page_title=context_data.get('page_title', 'esta página'),
page_topic="optimización de conversión",
suggested_next_steps=[
"Guía paso a paso",
"Checklist de implementación",
"Herramientas recomendadas"
],
download_link="https://4seo.es/resources/download-guide"
)
elif trigger_type == 'download':
template = Template('''
<h2>¡Gracias por descargar {{ resource_title }}!</h2>
<p>Hola {{ first_name }},</p>
<p>Espero que {{ resource_title }} te sea útil.</p>
<p>Muchos clientes tienen preguntas después de usar esta guía:</p>
<ul>
{% for faq in common_questions %}
<li><strong>{{ faq.question }}</strong></li>
<p>{{ faq.answer }}</p></li>
{% endfor %}
</ul>
<p>Si tienes alguna duda, responde a este email.</p>
<p><strong>Siguiente paso recomendado:</strong></p>
<p>{{ next_step }}</p>
<p><a href="{{ next_step_url }}">Ver guía →</a></p>
''')
html = template.render(
first_name=lead.first_name,
resource_title=context_data.get('resource_title', 'Guía SEO'),
common_questions=[
{'question': '¿Cuánto tiempo tarda en ver resultados?', 'answer': 'Normalmente 4-12 semanas.'},
{'question': '¿Necesito conocimientos técnicos?', 'answer': 'No, las guías son paso a paso.'}
],
next_step="Implementar la primera optimización del guide",
next_step_url="https://4seo.es/blog/como-implementar"
)
elif trigger_type == 'cart_abandon':
template = Template('''
<h2>¿Todavía interesado en {{ product_or_service }}?</h2>
<p>Hola {{ first_name }},</p>
<p>Vi que iniciaste el proceso de {{ product_or_service }} pero no completaste.</p>
<p>Entiendo. Es una decisión importante. Tómate tu tiempo.</p>
<p>Si aún estás interesado, puedo ofrecerte:</p>
<ul>
{% for incentive => incentives %}
<li>{{ incentive }}</li>
{% endfor %}
</ul>
<p>La oferta expira en {{ hours_to_expire }} horas.</p>
<p><a href="{{ cart_url }}">Completar proceso →</a></p>
''')
html = template.render(
first_name=lead.first_name,
product_or_service="auditoría SEO",
incentives=[
"10% de descuento adicional",
"LLamada de estrategia gratuita incluida",
"Prioridad en implementación"
],
hours_to_expire=48,
cart_url="https://4seo.es/contacto?utm_source=re-engagement"
)
elif trigger_type == 'quote_request':
template = Template('''
<h2>Tu solicitud de presupuesto</h2>
<p>Hola {{ first_name }},</p>
<p>Gracias por solicitar presupuesto para {{ service_type }}.</p>
<p>Estoy revisando tu solicitud. Te responderé en {{ response_time }>.</p>
<p>Mientras tanto, quizás te interesa:</p>
<ul>
{% for resource in helpful_resources %}
<li><a href="{{ resource.url }}">{{ resource.title }}</a></li>
{% endfor %}
</ul>
''')
html = template.render(
first_name=lead.first_name,
service_type="auditoría técnica",
response_time="24-48 horas",
helpful_resources=[
{'title': 'Portfolio de casos de éxito', 'url': 'https://4seo.es/casos-exito'},
{'title': 'Proceso de trabajo', 'url': 'https://4seo.es/metodologia'}
]
)
return htmlDetectar triggers automáticamente
# services/trigger.py
from datetime import datetime, timedelta
def detect_abandoned_cart(leads, cart_expiry_hours=24):
"""
Detecta leads con carritos abandonados
Args:
leads: Lista de objetos Lead
cart_expiry_hours: Horas después de las cuales se considera abandonado
Returns:
Lista de leads con carrito abandonado
"""
abandoned = []
for lead in leads:
# Si añadió productos al carrito pero no compró
if hasattr(lead, 'cart_items') and lead.cart_items:
cart_time = lead.cart_added_at
expiry_time = cart_time + timedelta(hours=cart_expiry_hours)
if datetime.now() > expiry_time and not lead.purchased:
abandoned.append(lead)
return abandoned
def detect_page_visit_triggers(leads, page_url):
"""
Detecta leads que visitaron una página específica
Args:
leads: Lista de objetos Lead
page_url: Página a monitorear
Returns:
Lista de leads que visitaron la página
"""
triggers = []
for lead in leads:
if page_url in lead.pages_visited:
# Verificar si no es spam (visitas recientes múltiples)
recent_visits = [p for p in lead.pages_visited if p == page_url]
if len(recent_visits) == 1: # Visitó una vez, parece genuino
triggers.append({
'lead': lead,
'trigger_type': 'page_visit',
'triggered_at': datetime.now(),
'context': {'page_title': 'Página de servicios'}
})
return triggersSequence 4: Abandoned Content Recovery
Propósito: Recuperar leads que empezaron a consumir contenido pero no terminaron.
sequences/abandoned.py - Script
from jinja2 import Template
def render_abandoned_content_email(lead, content_type, content_data):
"""
Renderiza email de recuperación de contenido abandonado
Args:
lead: Objeto Lead
content_type: 'guide' | 'course' | 'webinar'
content_data: Dict con datos del contenido
Returns:
HTML del email
"""
templates = {
'guide': Template('''
<h2>¿Te perdiste en esta guía?</h2>
<p>Hola {{ first_name }},</p>
<p>Vi que empezaste a leer <strong>{{ guide_title }}</strong> pero no lo terminaste.</p>
<p>Es normal. A veces la vida nos interrumpe. Pero quería recordarte que esta guía sigue aquí cuando estés listo.</p>
<p><strong>Dónde te quedaste:</strong></p>
<p>{{ last_section_title }}</p>
<p>Avance: {{ progress_percentage }}% completado</p>
<p><strong>Por qué debería terminarla:</strong></p>
<ul>
{% for benefit in key_benefits %}
<li>{{ benefit }}</li>
{% endfor %}
</ul>
<p><a href="{{ resume_url }}">Continuar desde donde lo dejaste →</a></p>
'''),
'course': Template('''
<h2>Tu curso te está esperando, {{ first_name }}</h2>
<p>Te inscribiste en <strong>{{ course_name }}</strong> pero no has comenzado.</p>
<p>Entiendo. El momento perfecto nunca llega. Mejor empezar tarde que nunca.</p>
<p><strong>Lo que aprenderás:</strong></p>
<ul>
{% for module in course_modules %}
<li>{{ module }}</li>
{% endfor %}
</ul>
<p><strong>Incluye:</strong></p>
<ul>
{% for bonus in bonuses %}
<li>{{ bonus }}</li>
{% endfor %}
</ul>
<p><a href="{{ start_url }}">Comenzar ahora →</a></p>
''')
}
template = templates.get(content_type)
html = template.render(
first_name=lead.first_name,
guide_title=content_data.get('title', 'Guía SEO'),
last_section_title=content_data.get('last_section', 'Introducción'),
progress_percentage=content_data.get('progress', 0),
key_benefits=[
"Estrategias probadas que funcionan",
"Casos de éxito reales",
"Checklists descargables"
],
resume_url=content_data.get('url', ''),
course_name=content_data.get('course', 'Curso SEO Avanzado'),
course_modules=[
"Módulo 1: Fundamentos",
"Módulo 2: Estrategia",
"Módulo 3: Tácticas avanzadas"
],
bonuses=[
"Plantillas descargables",
"Sesión de Q&A grupal",
"Certificado de finalización"
],
start_url=content_data.get('start_url', '')
)
return htmlIntegraciones ESP: Mailchimp, SendGrid, Self-Hosted
services/esp.py - Conexión con Email APIs
# services/esp.py - SendGrid integration
import sendgrid
from sendgrid.helpers import Mail
from config import config
def send_email_sendgrid(to_email, subject, html_content):
"""Envía email usando SendGrid API
Args:
to_email: Email del destinatario
subject: Asunto del email
html_content: Contenido HTML
Returns:
Response de SendGrid
"""
message = Mail(
from_email=config.EMAIL_FROM,
to_emails=to_email,
subject=subject,
html_content=html_content
)
sg = sendgrid.SendGridAPIClient(config.SENDGRID_API_KEY)
response = sg.send(message)
return response
# services/esp.py - Mailchimp integration (opcional)
def add_lead_to_mailchimp(email, first_name, list_id):
"""Añade lead a lista en Mailchimp
Args:
email: Email del lead
first_name: Nombre del lead
list_id: ID de lista de Mailchimp
Returns:
Response de Mailchimp
"""
import mailchimp3
client = mailchimp.Mailchimp(config.MAILCHIMP_API_KEY)
# Añadir a lista
response = client.lists.members.create(
list_id=list_id,
data={ 'email_address': email,
'status': 'subscribed',
'merge_fields': {
'FNAME': first_name
}
}
)
return responseOpción Self-Hosted: SMTP propio
<n># services/esp.py - SMTP self-hosted
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
def send_email_smtp(to_email, subject, html_content):
"""Envía email usando SMTP propio
Args:
to_email: Email del destinatario
subject: Asunto del email
html_content: Contenido HTML
Returns:
Response (sent_ok, message)
"""
# Configuración SMTP (en .env)
smtp_server = config.SMTP_SERVER # ej: smtp.gmail.com
smtp_port = config.SMTP_PORT # ej: 587
smtp_user = config.SMTP_USER # tu email
smtp_password = config.SMTP_PASSWORD # tu app password
# Crear mensaje
msg = MIMEMultipart('alternative')
msg['Subject'] = subject
msg['From'] = config.EMAIL_FROM
msg['To'] = to_email
msg.attach(MIMEText('text', 'plain'))
text_part = f"{subject}\n\nVer este email en un cliente de email que soporte HTML."
msg.attach(MIMEText('html', 'html'))
html_part = MIMEText(html_content, 'html')
msg.attach(html_part)
# Enviar
with smtplib.SMTP(smtp_server, smtp_port) as server:
server.starttls()
server.login(smtp_user, smtp_password)
server.send_message(msg)
server.quit()
return {'sent_ok': True, 'message': 'Email enviado'}Métricas y ROI
Dashboard de métricas
# services/analytics.py
import pandas as pd
from datetime import datetime, timedelta
def calculate_nurturing_metrics(leads, period_days=30):
"""Calcula métricas de nurturing para un período
Args:
leads: Lista de objetos Lead
period_days: Días del período
Returns:
Dict con métricas agregadas
"""
start_date = datetime.now() - timedelta(days=period_days)
# Filtrar leads activos en el período
active_leads = [l for l in leads if l.created_at >= start_date]
if not active_leads:
return None
# Métricas de email
total_emails = sum(l.emails_sent for l in active_leads)
total_opens = sum(l.emails_opened for l in active_leads)
total_clicks = sum(l.emails_clicked for l in active_leads)
open_rate = (total_opens / total_emails * 100) if total_emails > 0 else 0
click_rate = (total_clicks / total_opens * 100) if total_opens > 0 else 0
# Métricas de conversión
total_conversions = sum(1 for l in active_leads if l.stage.value == 'customer')
conversion_rate = (total_conversions / len(active_leads) * 100) if active_leads else 0
# ROI estimado (si conocemos costos)
# ROI = (Revenue - Cost) / Cost * 100
# Simplificado aquí
avg_revenue_per_conversion = 497 # Precio medio de servicio
total_revenue = total_conversions * avg_revenue_per_conversion
metrics = {
'period': f"Últimos {period_days} días",
'total_leads': len(active_leads),
'emails_sent': total_emails,
'emails_opened': total_opens,
'emails_clicked': total_clicks,
'open_rate': round(open_rate, 2),
'click_rate': round(click_rate, 2),
'conversions': total_conversions,
'conversion_rate': round(conversion_rate, 2),
'estimated_revenue': total_revenue
}
return metrics
def generate_html_report(metrics):
"""Genera reporte HTML de métricas"""
template = Template('''
<!DOCTYPE html>
<html>
<body>
<h1>📊 Lead Nurturing Report - {{ period }}</h1>
<div class="metric">
<h3>Resumen</h3>
<p><strong>Leads activos:</strong> {{ total_leads }}</p>
<p><strong>Emails enviados:</strong> {{ emails_sent }}</p>
<p><strong>Tasa apertura:</strong> {{ open_rate }}%</p>
<p><strong>Conversiones:</strong> {{ conversions }} ({{ conversion_rate }}%)</p>
</div>
<h2>Métricas por Segmento</h2>
<table>
<tr>
<th>Segmento</th>
<th>Leads</th>
<th>Conversión</th>
</tr>
{% for segment in segments %}
<tr>
<td>{{ segment.name }}</td>
<td>{{ segment.count }}</td>
<td>{{ segment.conversion_rate }}%</td>
</tr>
{% endfor %}
</table>
<p><em>Generado: {{ generated_at }}</em></p>
</body>
</html>
''')
html = template.render(
period=metrics['period'],
total_leads=metrics['total_leads'],
emails_sent=metrics['emails_sent'],
open_rate=metrics['open_rate'],
conversions=metrics['conversions'],
conversion_rate=metrics['conversion_rate'],
generated_at=datetime.now().strftime('%Y-%m-%d %H:%M'),
segments=[
{'name': 'Hot Leads', 'count': 5, 'conversion_rate': 12.5},
{'name': 'Warm Leads', 'count': 15, 'conversion_rate': 3.2},
{'name': 'Cold Leads', 'count': 30, 'conversion_rate': 0.5}
]
)
return htmlConclusión
Con este framework de lead nurturing automatizado, tienes un sistema completo que:
- Conecta SEO con Email: Captura intención de búsqueda automáticamente
- Personaliza contenido: Cada lead recibe según su búsqueda
- Sequences automatizadas: Welcome, Re-engagement, Behavioral, Abandoned Content
- Segmentación dinámica: Clasifica leads según comportamiento
- Medición completa: Métricas de email, conversión, ROI
Casos de uso del framework
Próximos pasos
- Añade más triggers: Descargas de recursos específicos, tiempo en página, scroll depth
- Integra con CRM: HubSpot, Salesforce, Pipedrive para sincronizar estados
- Personalización avanzada: Contenido dinámico según lead scoring + stage
- Multicanal: Añade soporte para emails en múltiples idiomas
Recursos recomendados
- Mailchimp Automation
- SendGrid Marketing
- HubSpot Workflows
Sobre el Autor
Alfonso Calero Gijón es Editor de 4SEO.es, portal de noticias y comunidad SEO. Especialista en automatización de procesos SEO y arquitectura de información. Escribe análisis y guías sobre automatización SEO para la comunidad.
Conectar en LinkedIn →
¿Necesitas ayuda con automatización SEO?
Sigue en 4SEO → Más guías en el blog
Blog 4SEO → Más guías en el blog
Blog 4SEO → Más guías de SEO técnico
Artículos Relacionados
- CRO para Páginas de Servicios
- SaaS B2B Lead Generation
- Python para SEO: Automatiza tu Trabajo Diario
