---
title: "Limpiar duplicados de CRM con Python 2026"
description: "Guía 2026 para limpiar duplicados de CRM con Python: fuzzy matching, dedup por phone normalizado, recordlinkage y caso real con 6,889 suppliers."
slug: "como-limpiar-duplicados-de-crm-con-python"
url: "https://catalizadora.ai/blog/como-limpiar-duplicados-de-crm-con-python"
cluster: "datos-sistemas/limpiar-duplicados-crm"
author: "Pablo Estrada"
published_at: "2026-05-11T12:00:00+00:00"
updated_at: "2026-06-19T19:59:51.42746+00:00"
read_minutes: "5"
lang: "es"
---
# Limpiar duplicados de CRM con Python 2026

> Guía 2026 para limpiar duplicados de CRM con Python: fuzzy matching, dedup por phone normalizado, recordlinkage y caso real con 6,889 suppliers.

Limpiar duplicados de CRM con Python 2026 sigue un patrón claro: normalizar phone a formato E.164 con la librería phonenumbers, normalizar email (lowercase, trim, sin dot tricks de Gmail), aplicar fuzzy matching con RapidFuzz o fuzzywuzzy sobre nombre con threshold 85 por ciento, y usar recordlinkage o dedupe para casos probabilísticos complejos. El stack típico es pandas más phonenumbers más RapidFuzz, con script reproducible que produce un CSV de duplicados sugeridos para revisión humana antes de hacer merge en el CRM. Para evitar que vuelvan, hay que pegar un proceso continuo en la entrada de cada nuevo contact. Pyme LATAM típica encuentra entre 10 y 30 por ciento de duplicados en su CRM.

Si manejas HubSpot, Pipedrive, Zoho, Salesforce, Mercado Libre o cualquier base de contacts y sospechas que tienes duplicados, esta guía te da el patrón técnico ganador.

## Paso 1: Inventario y métricas iniciales

Antes de tocar datos:

- Exportar contacts completos a CSV o Parquet
- Conteo total de registros
- Conteo de registros con phone vacío y email vacío
- Distribución de phone format (cuántos sin código país, cuántos con guiones, etc.)
- Distribución de email format (cuántos con typos comunes, gmial.com, etc.)

Sin estos números base, no puedes medir si el dedup funcionó.

## Paso 2: Normalizar phone a E.164

E.164 es el formato internacional estándar: plus52 55 1234 5678 se vuelve plus5215512345678. La librería phonenumbers de Python (port del Google libphonenumber):

```python
import phonenumbers

def normalize_phone(raw, default_country='MX'):
    try:
        parsed = phonenumbers.parse(raw, default_country)
        if not phonenumbers.is_valid_number(parsed):
            return None
        return phonenumbers.format_number(parsed, phonenumbers.PhoneNumberFormat.E164)
    except Exception:
        return None

df['phone_e164'] = df['phone_raw'].apply(normalize_phone)
```

Importante: pasar default_country correcto por origen del lead (MX, AR, CO, CL, PE, GT). Sin eso, phonenumbers no sabe interpretar números locales sin código país.

## Paso 3: Normalizar email

Email es sensible:

```python
def normalize_email(raw):
    if not raw or '@' not in raw:
        return None
    email = raw.lower().strip()
    local, domain = email.split('@', 1)
    if domain == 'gmail.com':
        local = local.replace('.', '').split('+')[0]
    return f"{local}@{domain}"

df['email_norm'] = df['email_raw'].apply(normalize_email)
```

Para Gmail, eliminar dots y plus tricks porque Gmail los ignora pero usuarios crean cuentas distintas con esos trucos sin querer.

## Paso 4: Detectar duplicados duros

Duplicados duros son los que coinciden en phone o email:

```python
import pandas as pd

# Duplicados por phone normalizado
dup_by_phone = df[df.duplicated(subset=['phone_e164'], keep=False)]
dup_by_phone = dup_by_phone.sort_values(['phone_e164', 'created_at'])

# Duplicados por email normalizado
dup_by_email = df[df.duplicated(subset=['email_norm'], keep=False)]

# Marcar
df['is_dup_phone'] = df['phone_e164'].duplicated(keep='first')
df['is_dup_email'] = df['email_norm'].duplicated(keep='first')
```

Pyme LATAM típica encuentra 5 a 15 por ciento de duplicados duros en este paso.

## Paso 5: Fuzzy matching sobre nombre

Para contacts sin phone o email coincidente pero con nombre parecido:

```python
from rapidfuzz import process, fuzz

def find_similar_names(name, candidates, threshold=85):
    matches = process.extract(name, candidates, scorer=fuzz.ratio, limit=5)
    return [m for m in matches if m[1] >= threshold and m[0] != name]
```

Threshold 85 captura "Juan Perez", "Juan Pérez", "Juan F Perez" como similares. Threshold 95 es más estricto. Threshold 75 da muchos falsos positivos.

## Paso 6: recordlinkage para casos probabilísticos

Cuando dos contacts tienen nombre similar, ciudad similar, empresa similar pero phone y email distintos, recordlinkage da un score probabilístico:

```python
import recordlinkage
from recordlinkage.preprocessing import clean

indexer = recordlinkage.Index()
indexer.block('zip_code')
candidate_pairs = indexer.index(df)

compare = recordlinkage.Compare()
compare.string('name_clean', 'name_clean', method='jarowinkler', threshold=0.85, label='name')
compare.string('company_clean', 'company_clean', method='jarowinkler', threshold=0.85, label='company')
compare.exact('city', 'city', label='city')

features = compare.compute(candidate_pairs, df)
matches = features[features.sum(axis=1) >= 2.5]
```

Esto devuelve pares candidatos con score acumulado. Score mayor a 2.5 sobre 3 indica alta probabilidad de duplicado.

## Paso 7: Merge guiado por humano

NO mergear automático. Después de tener candidatos:

1. Exportar pares a CSV con columnas: id_A, id_B, score, motivo
2. Equipo de ventas o de operaciones revisa lote de 50 a 100 a la vez
3. Decide quién es master y quién se mergea
4. Script aplica merge llamando a API del CRM con el master_id

HubSpot, Pipedrive y Salesforce todos tienen endpoint merge contacts en su API. Zoho también.

## Paso 8: Prevenir duplicados futuros

Sin proceso continuo, duplicados vuelven en 3 a 6 meses:

1. Webhook en formulario llama a tu API con phone más email crudos
2. API normaliza con misma lógica del script de limpieza
3. API busca match en CRM por phone normalizado o email normalizado
4. Si match: actualiza contact existente con datos nuevos
5. Si no match: crea contact nuevo

Esto es lo que evita que el CRM se vuelva a contaminar.

## El caso real: HubSpot rate limit y dedup phone variantes

Una escuela educativa en Huixquilucan tenía HubSpot saturado con duplicados por phone variants. Catalizadora implementó:

- Phone normalization en pre check antes de crear contact en HubSpot
- 5 variantes phone consolidadas en una sola call API con filterGroups OR
- Búsqueda por nombre cross match para meeting booking detection
- 80 por ciento reducción API calls por ciclo (de 100 más a 20)
- Zero 429 errors después del deploy
- Cache 15 minutos para variantes ya buscadas
- Inversión: 1 día, 0 USD directo, incluido en honorarios mensuales

Resultado: dashboard CEO con atribución multi canal demostrable y 7 de 7 inscritos trazables.

## Lo que NO debes hacer

1. Mergear automático sin revisión humana: pierdes datos importantes (notas de cliente A que no estaban en cliente B)
2. Sin normalizar phone antes de comparar: dedup falla en 30 a 50 por ciento de casos LATAM
3. Sin Git para tu script: cuando algo falla en producción, no sabes qué cambió
4. Sin proceso continuo: limpias hoy y duplicados vuelven en 6 meses

## Próximos pasos

Si tu CRM LATAM (HubSpot, Pipedrive, Salesforce, Zoho, Mercado Libre, propio) tiene 10 a 30 por ciento de duplicados, un script Python con phonenumbers más RapidFuzz más recordlinkage los detecta en 1 a 4 semanas según volumen.

Catalizadora arma el diagnóstico en una llamada de 30 minutos, sin pitch deck, conversación real sobre tu operación.

- [MAGIA Core](https://catalizadora.ai/magia/core) construye sistemas a medida con dedup, integraciones profundas y dashboards en 12 semanas por 15,000 USD. Código a tu nombre, sin retainers ni licencias atadas.
- Para pymes pequeñas con CRM propio listo en 15 días, [MAGIA Solo](https://catalizadora.ai/magia/solo) cubre desde 4,500 USD.
## Preguntas frecuentes

### ¿Por qué se llenan los CRM de duplicados?

Tres razones típicas: leads entran por múltiples canales (formulario, bot, importación manual), nadie limpia phone format (con guiones, espacios, código país), y emails se escriben con typos. Resultado: 10 a 30 por ciento de tu CRM son duplicados encubiertos.

### ¿Qué librerías Python sirven para dedup?

pandas para manipulación base. fuzzywuzzy o RapidFuzz para fuzzy matching de nombres. phonenumbers para normalizar teléfonos a E.164. recordlinkage para dedup probabilístico avanzado. dedupe (de DataMade) para casos complejos con scoring.

### ¿Cuál es el patrón ganador para dedupear contacts?

Normalizar phone a E.164 más email lowercase trim como clave fuerte. Si ambos coinciden son duplicados claros. Si solo coincide phone o solo email, fuzzy match sobre nombre con threshold 85 por ciento. Si los tres coinciden parciales, es duplicado para revisión humana.

### ¿Y los teléfonos LATAM con todos sus formatos?

Usa la librería phonenumbers de Python. parse(numero, country_code) más format_number en formato E.164 normaliza 555 1234, plus52 55 12345678 y 5215512345678 al mismo valor. Sin esto, dedup falla en 30 a 50 por ciento de casos LATAM.

### ¿Conviene script una sola vez o proceso continuo?

Ambos. Script una sola vez para limpiar el legado (puede tomar 1 a 4 semanas según volumen). Proceso continuo: cada nuevo contact pasa por validación antes de insertar (idempotency por phone más email). Sin proceso continuo, duplicados vuelven en 3 a 6 meses.


---

Source: https://catalizadora.ai/blog/como-limpiar-duplicados-de-crm-con-python
Author: Pablo Estrada — AI Catalyst, LLC (catalizadora.ai)
