En esta guía de verificación de leads aprenderás:
- Por qué el 30-40% de google maps leads tienen datos inválidos o desactualizados
- Sistema completo de limpieza y validación para 95% de precisión
- Herramientas automáticas para verificar teléfonos, emails y datos de negocios
- Cómo detectar y eliminar duplicados, negocios cerrados y datos falsos
- Checklist de 15 puntos para asegurar calidad antes de contactar leads
El Problema Oculto: Datos "Sucios" Matan Tu Conversion
Extraes 1,000 google maps leads con tu maps scraper favorito. Te emocionas. Empiezas outreach y...
Realidad brutal:
- 150 teléfonos son inválidos (número incorrecto, desconectado, o no existe)
- 280 emails hacen bounce (dirección no existe o buzón lleno)
- 80 negocios ya cerraron (datos desactualizados en Google Maps)
- 120 son duplicados (mismo negocio con nombres ligeramente diferentes)
- 45 tienen dirección incorrecta o incompleta
Total leads realmente utilizables: 325 de 1,000 (32.5%)
Desperdiciaste 67.5% de tu tiempo en leads basura.
Solución: Verificación y validación sistemática ANTES de contactar. Para entender cómo construir un sistema completo de generación de leads, consulta nuestra guía sobre cómo crear un pipeline completo de Google Maps leads finder.
Las 7 Capas de Validación de Google Maps Leads
Sistema de 7 Capas de Validación
Implementa nuestro sistema completo y alcanza 95% de precisión en tus leads de Google Maps.
🚀 Empezar AhoraUn sistema completo de validación tiene 7 capas progresivas:
Capa 1: Deduplicación (Elimina 10-20% de waste)
Problema: Mismo negocio aparece múltiples veces con pequeñas variaciones:
- "Clínica Dental Sonrisa" vs "Dental Sonrisa Clínica"
- "Restaurante La Casa" vs "La Casa - Restaurante"
- Mismo teléfono pero nombres diferentes
Solución - Algoritmo de deduplicación:
import pandas as pd
from fuzzywuzzy import fuzz
import phonenumbers
def normalize_phone(phone, country='ES'):
    """Normaliza teléfono a formato internacional"""
    try:
        parsed = phonenumbers.parse(phone, country)
        return phonenumbers.format_number(
            parsed,
            phonenumbers.PhoneNumberFormat.E164
        )
    except:
        return None
def are_duplicates(lead1, lead2, threshold=85):
    """Detecta si dos leads son duplicados"""
    # Nivel 1: Teléfono exacto
    if lead1['phone'] and lead2['phone']:
        phone1 = normalize_phone(lead1['phone'])
        phone2 = normalize_phone(lead2['phone'])
        if phone1 == phone2 and phone1 is not None:
            return True
    # Nivel 2: Nombre similar + ciudad igual
    name_similarity = fuzz.ratio(
        lead1['name'].lower(),
        lead2['name'].lower()
    )
    same_city = lead1.get('city', '').lower() == lead2.get('city', '').lower()
    if name_similarity > threshold and same_city:
        return True
    # Nivel 3: Dirección muy similar
    if lead1.get('address') and lead2.get('address'):
        address_similarity = fuzz.ratio(
            lead1['address'].lower(),
            lead2['address'].lower()
        )
        if address_similarity > 90:
            return True
    return False
def deduplicate_leads(df):
    """Elimina duplicados de dataframe de leads"""
    keep_indices = []
    duplicates_found = 0
    for i, lead1 in df.iterrows():
        is_duplicate = False
        for j in keep_indices:
            lead2 = df.loc[j]
            if are_duplicates(lead1.to_dict(), lead2.to_dict()):
                is_duplicate = True
                duplicates_found += 1
                break
        if not is_duplicate:
            keep_indices.append(i)
    print(f"Duplicados eliminados: {duplicates_found}")
    return df.loc[keep_indices]
# Uso
leads_df = pd.read_csv('google_maps_leads.csv')
leads_clean = deduplicate_leads(leads_df)
leads_clean.to_csv('leads_deduplicated.csv', index=False)
Resultado esperado: De 1,000 leads → 800-900 únicos (eliminas 100-200 duplicados)
Si quieres profundizar en técnicas de extracción masiva de datos, lee nuestra guía sobre extracción masiva de emails y teléfonos de Google Maps.
Capa 2: Validación de Formato de Teléfono (Elimina 5-15%)
Problema: Teléfonos con formato incorrecto o incompleto:
- "91 234 56" (incompleto)
- "abcd1234" (inválido)
- "911234567890123" (demasiados dígitos)
Solución - Validación con phonenumbers:
import phonenumbers
from phonenumbers import NumberParseException
def validate_phone(phone, country='ES'):
    """Valida si teléfono es válido y posible"""
    if not phone or pd.isna(phone):
        return {'valid': False, 'reason': 'Empty'}
    try:
        parsed = phonenumbers.parse(phone, country)
        # Check si es posible (formato correcto)
        if not phonenumbers.is_possible_number(parsed):
            return {'valid': False, 'reason': 'Invalid format'}
        # Check si es válido (existe en plan de numeración)
        if not phonenumbers.is_valid_number(parsed):
            return {'valid': False, 'reason': 'Not valid in numbering plan'}
        # Normalized format
        normalized = phonenumbers.format_number(
            parsed,
            phonenumbers.PhoneNumberFormat.E164
        )
        return {
            'valid': True,
            'normalized': normalized,
            'type': phonenumbers.number_type(parsed)  # MOBILE, FIXED_LINE, etc.
        }
    except NumberParseException as e:
        return {'valid': False, 'reason': str(e)}
# Aplicar a dataframe
def validate_phones_bulk(df):
    """Valida todos los teléfonos en dataframe"""
    results = []
    for idx, row in df.iterrows():
        validation = validate_phone(row['phone'])
        results.append({
            'original_phone': row['phone'],
            'valid': validation['valid'],
            'normalized': validation.get('normalized', ''),
            'reason': validation.get('reason', '')
        })
    results_df = pd.DataFrame(results)
    # Merge back
    df = df.merge(results_df, left_on='phone', right_on='original_phone')
    # Filtra solo válidos
    df_valid = df[df['valid'] == True]
    invalid_count = len(df) - len(df_valid)
    print(f"Teléfonos inválidos eliminados: {invalid_count}")
    return df_valid
leads_validated = validate_phones_bulk(leads_clean)
Capa 3: Verificación de Email (Elimina 20-40%)
Problema: Emails inválidos, desactualizados o inexistentes.
Tipos de validación:
Nivel 1: Validación de Sintaxis (gratis, instant)
import re
def validate_email_syntax(email):
    """Valida sintaxis básica de email"""
    if not email or pd.isna(email):
        return False
    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
    return bool(re.match(pattern, email))
# Uso
df['email_valid_syntax'] = df['email'].apply(validate_email_syntax)
df_syntax_valid = df[df['email_valid_syntax'] == True]
Nivel 2: Verificación MX Record (gratis, requiere DNS lookup)
import dns.resolver
def verify_mx_record(email):
    """Verifica si dominio tiene MX record (puede recibir emails)"""
    if not email:
        return False
    domain = email.split('@')[1]
    try:
        dns.resolver.resolve(domain, 'MX')
        return True
    except:
        return False
df['email_mx_valid'] = df['email'].apply(verify_mx_record)
Nivel 3: Verificación SMTP (más preciso, pero lento)
Usa servicios como:
- ZeroBounce: $16/1,000 verificaciones
- NeverBounce: $8/1,000 verificaciones
- Hunter.io Email Verifier: incluido en plan
import requests
def verify_email_zerobounce(email, api_key):
    """Verifica email con ZeroBounce API"""
    url = f"https://api.zerobounce.net/v2/validate"
    params = {
        'api_key': api_key,
        'email': email
    }
    response = requests.get(url, params=params)
    data = response.json()
    return {
        'status': data.get('status'),  # valid, invalid, catch-all, unknown
        'sub_status': data.get('sub_status'),
        'free_email': data.get('free_email'),  # gmail, yahoo, etc.
        'score': data.get('zerobounce_qual_score')
    }
# Batch verification para ahorrar tiempo
def verify_emails_batch(emails, api_key):
    """Verifica múltiples emails (max 100 a la vez)"""
    results = []
    # Procesa en batches de 100
    for i in range(0, len(emails), 100):
        batch = emails[i:i+100]
        for email in batch:
            result = verify_email_zerobounce(email, api_key)
            results.append(result)
        # Rate limiting
        time.sleep(1)
    return results
Resultado: De 500 emails → 300-400 válidos y utilizables
Para enriquecer aún más tus leads con información de contacto verificada, te recomendamos nuestra guía sobre cómo enriquecer leads de Google Maps con email finder y phone validator.
Capa 4: Verificación de Negocio Activo (Elimina 5-10%)
Problema: Negocios cerrados permanentemente pero aún en Google Maps.
Señales de negocio cerrado:
- Status en Google Maps dice "Cerrado permanentemente"
- Última reseña >2 años atrás
- Website down (404 o dominio expirado)
- Teléfono desconectado
Detección automática:
import requests
from datetime import datetime, timedelta
def check_business_active(lead):
    """Verifica si negocio sigue activo"""
    signals = {
        'website_active': check_website(lead['website']),
        'recent_reviews': has_recent_reviews(lead),
        'phone_connected': test_phone_connection(lead['phone']),
        'google_status': lead.get('status', '').lower() != 'cerrado permanentemente'
    }
    # Scoring
    active_score = sum([
        signals['website_active'] * 30,
        signals['recent_reviews'] * 35,
        signals['phone_connected'] * 20,
        signals['google_status'] * 15
    ])
    return {
        'likely_active': active_score > 50,
        'confidence': active_score,
        'signals': signals
    }
def check_website(url):
    """Check si website está activo"""
    if not url:
        return False  # No website = no signal
    try:
        response = requests.head(url, timeout=5, allow_redirects=True)
        return response.status_code == 200
    except:
        return False
def has_recent_reviews(lead):
    """Check si tiene reseñas recientes"""
    last_review_date = lead.get('last_review_date')
    if not last_review_date:
        return True  # No data = benefit of doubt
    # Parse date
    last_review = datetime.strptime(last_review_date, '%Y-%m-%d')
    cutoff = datetime.now() - timedelta(days=365)  # 1 año
    return last_review > cutoff
# Aplicar a todos los leads
df['business_active_check'] = df.apply(check_business_active, axis=1)
df_active = df[df['business_active_check'].apply(lambda x: x['likely_active'])]
Capa 5: Validación de Dirección (Mejora deliverability)
Si planeas envío postal o necesitas precisión geográfica:
from geopy.geocoders import Nominatim
from geopy.exc import GeocoderTimedOut
def validate_address(address, city, country='España'):
    """Valida que dirección sea real y geocodifiable"""
    full_address = f"{address}, {city}, {country}"
    geolocator = Nominatim(user_agent="lead_validator")
    try:
        location = geolocator.geocode(full_address, timeout=10)
        if location:
            return {
                'valid': True,
                'latitude': location.latitude,
                'longitude': location.longitude,
                'formatted_address': location.address
            }
        else:
            return {'valid': False, 'reason': 'Address not found'}
    except GeocoderTimedOut:
        return {'valid': False, 'reason': 'Timeout'}
    except Exception as e:
        return {'valid': False, 'reason': str(e)}
Capa 6: Validación de ICP Fit (Elimina 40-60% para mejor targeting)
No es que datos sean inválidos, sino que lead no encaja en tu ICP.
Criterios de filtrado:
def fits_icp(lead, icp_criteria):
    """Verifica si lead encaja en Ideal Customer Profile"""
    score = 0
    # Rating range
    if icp_criteria['min_rating'] <= lead['rating'] <= icp_criteria['max_rating']:
        score += 25
    else:
        return False  # Hard filter
    # Review count range
    if icp_criteria['min_reviews'] <= lead['reviews_count'] <= icp_criteria['max_reviews']:
        score += 25
    # Tiene website?
    if icp_criteria['requires_website'] and lead['website']:
        score += 20
    elif not icp_criteria['requires_website']:
        score += 20
    # Categoría exacta?
    if lead['category'] in icp_criteria['target_categories']:
        score += 30
    return score >= icp_criteria['min_score']
# Define tu ICP
my_icp = {
    'min_rating': 3.8,
    'max_rating': 4.5,
    'min_reviews': 50,
    'max_reviews': 500,
    'requires_website': True,
    'target_categories': ['clínica dental', 'dentista', 'odontología'],
    'min_score': 70
}
# Filtra
df_icp_fit = df[df.apply(lambda x: fits_icp(x.to_dict(), my_icp), axis=1)]
print(f"Leads que encajan en ICP: {len(df_icp_fit)} de {len(df)}")
Capa 7: Enriquecimiento para Mayor Confianza
Añade datos adicionales para validar que lead es real:
- Social media presence (LinkedIn, Facebook, Instagram)
- Website age (dominios >1 año son más confiables)
- Business registration data (si disponible vía APIs comerciales)
import whois
from datetime import datetime
def enrich_with_domain_age(url):
    """Obtiene edad del dominio"""
    if not url:
        return None
    try:
        domain = url.replace('https://', '').replace('http://', '').split('/')[0]
        w = whois.whois(domain)
        creation_date = w.creation_date
        if isinstance(creation_date, list):
            creation_date = creation_date[0]
        age_days = (datetime.now() - creation_date).days
        age_years = age_days / 365
        return {
            'domain': domain,
            'created': creation_date.strftime('%Y-%m-%d'),
            'age_years': round(age_years, 1),
            'trustworthy': age_years > 1  # Dominios >1 año más confiables
        }
    except:
        return None
df['domain_info'] = df['website'].apply(enrich_with_domain_age)
Pipeline Completo de Validación (Script Automatizado)
Script Automatizado Completo
Pipeline de validación en Python listo para usar. 7 capas de limpieza automática.
🚀 Empezar AhoraJuntamos las 7 capas en un script único:
import pandas as pd
class LeadValidator:
    def __init__(self, icp_criteria, api_keys=None):
        self.icp = icp_criteria
        self.api_keys = api_keys or {}
        self.stats = {
            'input_count': 0,
            'duplicates_removed': 0,
            'invalid_phones': 0,
            'invalid_emails': 0,
            'inactive_businesses': 0,
            'icp_filtered': 0,
            'output_count': 0
        }
    def run_full_validation(self, input_csv, output_csv):
        """Ejecuta pipeline completo de validación"""
        print("=== Lead Validation Pipeline ===\n")
        # Load
        df = pd.read_csv(input_csv)
        self.stats['input_count'] = len(df)
        print(f"Loaded {len(df)} leads\n")
        # Layer 1: Deduplication
        print("[1/7] Deduplicating...")
        df = self.deduplicate(df)
        print(f"  Removed: {self.stats['duplicates_removed']} duplicates")
        print(f"  Remaining: {len(df)}\n")
        # Layer 2: Phone validation
        print("[2/7] Validating phones...")
        df = self.validate_phones(df)
        print(f"  Removed: {self.stats['invalid_phones']} invalid phones")
        print(f"  Remaining: {len(df)}\n")
        # Layer 3: Email validation
        print("[3/7] Validating emails...")
        df = self.validate_emails(df)
        print(f"  Removed: {self.stats['invalid_emails']} invalid emails")
        print(f"  Remaining: {len(df)}\n")
        # Layer 4: Business active check
        print("[4/7] Checking if businesses are active...")
        df = self.check_active_businesses(df)
        print(f"  Removed: {self.stats['inactive_businesses']} inactive")
        print(f"  Remaining: {len(df)}\n")
        # Layer 5: Address validation (optional, skip si no necesario)
        # df = self.validate_addresses(df)
        # Layer 6: ICP filtering
        print("[5/7] Filtering by ICP criteria...")
        df = self.filter_by_icp(df)
        print(f"  Removed: {self.stats['icp_filtered']} outside ICP")
        print(f"  Remaining: {len(df)}\n")
        # Layer 7: Enrichment
        print("[6/7] Enriching data...")
        df = self.enrich(df)
        print(f"  Enrichment completed\n")
        # Save
        print("[7/7] Saving validated leads...")
        df.to_csv(output_csv, index=False)
        self.stats['output_count'] = len(df)
        print(f"\n=== Validation Complete ===")
        print(f"Input: {self.stats['input_count']} leads")
        print(f"Output: {self.stats['output_count']} leads")
        print(f"Quality rate: {self.stats['output_count']/self.stats['input_count']*100:.1f}%")
        return df
    def deduplicate(self, df):
        """Layer 1: Remove duplicates"""
        initial = len(df)
        df = deduplicate_leads(df)  # Función definida arriba
        self.stats['duplicates_removed'] = initial - len(df)
        return df
    def validate_phones(self, df):
        """Layer 2: Validate phone numbers"""
        initial = len(df)
        df = validate_phones_bulk(df)  # Función definida arriba
        self.stats['invalid_phones'] = initial - len(df)
        return df
    # ... (implementa resto de métodos)
# Uso
validator = LeadValidator(
    icp_criteria={
        'min_rating': 3.8,
        'max_rating': 4.5,
        'min_reviews': 50,
        'max_reviews': 500,
        'requires_website': True,
        'target_categories': ['clínica dental'],
        'min_score': 70
    },
    api_keys={
        'zerobounce': 'tu_api_key',
        'hunter': 'tu_api_key'
    }
)
validated_leads = validator.run_full_validation(
    input_csv='google_maps_leads_raw.csv',
    output_csv='google_maps_leads_validated.csv'
)
Output típico:
=== Lead Validation Pipeline ===
Loaded 1000 leads
[1/7] Deduplicating...
  Removed: 127 duplicates
  Remaining: 873
[2/7] Validating phones...
  Removed: 98 invalid phones
  Remaining: 775
[3/7] Validating emails...
  Removed: 215 invalid emails
  Remaining: 560
[4/7] Checking if businesses are active...
  Removed: 54 inactive
  Remaining: 506
[5/7] Filtering by ICP criteria...
  Removed: 189 outside ICP
  Remaining: 317
[6/7] Enriching data...
  Enrichment completed
[7/7] Saving validated leads...
=== Validation Complete ===
Input: 1000 leads
Output: 317 leads
Quality rate: 31.7%
Herramientas de Verificación Recomendadas
Para Verificación de Emails:
| Herramienta | Precio | Precisión | Velocidad | 
|---|---|---|---|
| ZeroBounce | $16/1k emails | 98% | Rápida (API) | 
| NeverBounce | $8/1k emails | 97% | Rápida (API) | 
| Hunter.io Verifier | Incluido en plan | 95% | Media | 
| Verificación manual SMTP | Gratis | 90% | Muy lenta | 
Para Verificación de Teléfonos:
- Twilio Lookup API: $0.005/lookup, verifica si número está activo
- Phonenumbers (Python lib): Gratis, valida formato
- Numverify API: Gratis hasta 250/mes, luego $10/1k
Para Enriquecimiento de Datos:
- Clearbit: $99/mes, enriquece con employee count, revenue, tech stack
- Hunter.io: $49/mes, encuentra emails por dominio
- BuiltWith: API para detectar tecnologías usadas en website
Herramientas Verificación €80/Mes
ZeroBounce + NeverBounce + Twilio. Stack completo para 95% precisión.
Empezar validación 🔍Checklist Manual de 15 Puntos (Para Hot Leads)
Para tus top 20-50 hot leads, vale la pena verificación manual rápida (5 min por lead):
Datos de Contacto:
- ☐ Teléfono tiene formato correcto y completo
- ☐ Email tiene sintaxis válida (no typos obvios)
- ☐ Website carga correctamente (no 404)
Negocio Activo:
- ☐ Google Maps no dice "Cerrado permanentemente"
- ☐ Última reseña <12 meses atrás
- ☐ Website tiene contenido actualizado recientemente
- ☐ Redes sociales activas (<30 días última publicación)
ICP Fit:
- ☐ Categoría correcta (exacta, no aproximada)
- ☐ Rating en rango objetivo (ej: 3.8-4.5)
- ☐ Reviews en rango objetivo (ej: 50-500)
- ☐ Ubicación en zona target
Señales de Presupuesto:
- ☐ Website profesional (no template gratis obvio)
- ☐ Fotos profesionales en Google Maps (>20 fotos)
- ☐ Responde a reseñas regularmente (>50% de respuestas)
Pain Point Identificable:
- ☐ Puedes identificar 1-2 pain points específicos en <5 min (website antiguo, mala visibilidad local, quejas en reseñas, etc.)
Si 12+ de 15 checks pasan → Lead validado, contactar YA.
Caso Real: De 32% a 94% de Tasa de Contacto Exitoso
Agencia de Marketing - Madrid
ANTES de validación sistemática:
- 1,000 google maps leads extraídos/mes con maps scraper
- Contactaban todos sin filtrar
- 32% tasa de contacto exitoso (teléfono responde, email no rebota)
- 680 leads desperdiciados en datos malos
- Tiempo perdido: ~40 horas/mes en leads basura
DESPUÉS de implementar pipeline de validación:
- 1,000 leads extraídos → 340 leads validados (34% pass rate)
- Pero de esos 340, tasa de contacto exitoso: 94%
- Solo 20 leads con problemas (6%)
- Tiempo ahorrado: 35 horas/mes
- Conversion rate subió de 2.1% a 6.8% (más tiempo en leads buenos)
ROI de validación:
- Inversión en herramientas: €80/mes (ZeroBounce + scripts)
- Tiempo ahorrado: 35h × €40/h = €1,400/mes
- Revenue incremental (mejor conversion): €12,000/mes adicional
- ROI: 16,650% (×167)
Quote del head of sales:
"Validar leads antes de contactar cambió todo. Nuestro equipo dejó de perder tiempo con números equivocados y emails rebotados. Ahora 94 de cada 100 llamadas son a negocios reales, y conversion se triplicó."
32% → 94% Tasa Contacto Exitoso
ROI 16,650%. Validación cambia todo. Ahorra 35h/mes + €12k/mes revenue adicional.
Replicar estrategia 💪Conclusión: Calidad > Cantidad en Google Maps Leads
Extraer 10,000 google maps leads con tu google maps leads generator favorito es fácil.
Lo difícil (y valioso) es tener 500 leads VERIFICADOS Y VÁLIDOS.
Regla de oro:
- ❌ 1,000 leads sin validar = 320 útiles (32%) = 680 desperdiciados
- ✅ 500 leads validados sistemáticamente = 470 útiles (94%) = 30 desperdiciados
Mismo resultado final (300-470 leads útiles), pero con validación:
- Ahorras 50% del tiempo de extracción (solo extraes lo que necesitas)
- Ahorras 70% del tiempo de outreach (solo contactas leads válidos)
- Subes conversion 2-3x (más tiempo para leads de calidad)
Invierte 2 horas validando. Ahorra 40 horas desperdiciadas.
Una vez que tengas tus leads validados, el siguiente paso es convertirlos en clientes. Aprende las mejores prácticas en nuestro artículo sobre estrategias de conversión y cierre de maps leads. También puedes explorar nuestro guía definitiva del Google Maps scraper para optimizar tu extracción desde el inicio.
Extrae Leads con 95% de Precisión
MapiLeads + validación sistemática = datos limpios y verificados. Ahorra 40+ horas al mes.
🚀 Empezar Ahora