← SCRAM AI Lab

Herramientas IA

Migración Mautic → CRM propio sin downtime

Patrón dual-write durante 30 días, backfill batch de 10K contactos por batch, redirect 301 al final. 47K contactos migrados en 6h sin interrumpir capturas.

May 21, 2026

10 lecturas

El cutover hard es el patrón que te corre el director comercial al día siguiente

Llevamos cinco años viendo migraciones de Mautic terminar mal: cutover de viernes en la noche, lunes el equipo de marketing descubre que 8% de los contactos no llegaron, 15% de los segments se rompieron porque los filtros no mapean uno-a-uno, y el formulario de la landing principal estuvo apuntando al endpoint viejo durante 36 horas. El patrón que sí funciona tiene tres fases y dura un mes — pero nadie te despierta a las 3 AM.

Fase 1: Dual-write con CRM nuevo leyendo

Los primeros 15 días el CRM nuevo es la fuente de verdad para lectura, pero todo lo que se escribe (forms, tracker, sync ERP) se duplica a Mautic en background. Mautic queda en read-only para UI humana — nadie puede tocarlo manualmente. Esto compra ventana de rollback: si algo explota, apuntas las lecturas a Mautic en 5 minutos.

// services/contact-write.service.ts
async function captureContact(data: ContactInput) {
  // Primario: CRM nuevo
  const contact = await prisma.crmContact.create({ data });

  // Espejo a Mautic (best-effort, no bloquea)
  this.mauticMirrorQueue.add('mirror-contact', {
    crmId: contact.id,
    email: data.email,
    phone: data.phone,
    payload: data,
  }, { attempts: 3, backoff: 'exponential' });

  return contact;
}

Fase 2: Backfill batch con dedup

En paralelo, un job nocturno baja contactos históricos de Mautic en batches de 10K. La parte crítica es la deduplicación compuesta: email exacto OR phone normalizado (sin lada, sin espacios). Mautic permite duplicados; tu CRM nuevo no debe.

async function backfillBatch(offset: number) {
  const mauticContacts = await mautic.getContacts({
    limit: 10000,
    offset,
    orderBy: 'date_modified',
  });

  for (const m of mauticContacts) {
    const phoneNorm = normalizePhone(m.phone); // +52 5512345678
    const existing = await prisma.crmContact.findFirst({
      where: {
        OR: [
          { email: m.email.toLowerCase() },
          phoneNorm ? { phone: phoneNorm } : undefined,
        ].filter(Boolean),
      },
    });

    if (existing) {
      await prisma.crmContact.update({
        where: { id: existing.id },
        data: { mauticId: m.id, ...mergeFields(existing, m) },
      });
    } else {
      await prisma.crmContact.create({ data: mapMauticToCrm(m) });
    }
  }
}

Números reales del caso AWALAB: 47K contactos, 12K segments, 6 horas de ejecución total. El cuello de botella no fue Postgres ni Mautic — fue la normalización de teléfonos con libphonenumber para detectar duplicados.

Fase 3: Apagar Mautic con redirect 301

Después de 30 días de dual-write sin incidentes, dropeamos las escrituras a Mautic. Los endpoints viejos (/form/submit/mautic, /mtc.js) reciben redirect 301 a los equivalentes del CRM nuevo. Los 301 los procesa Traefik en el edge sin tocar Mautic — para entonces Mautic ya está apagado, solo Traefik conoce el mapeo.

# traefik dynamic config
http:
  routers:
    mautic-legacy:
      rule: "Host(`mautic.scram2k.com`) || PathPrefix(`/mtc.js`)"
      service: noop@internal
      middlewares: [redirect-to-crm]
  middlewares:
    redirect-to-crm:
      redirectRegex:
        regex: "^https://mautic\\.scram2k\\.com/(.*)"
        replacement: "https://api.scram2k.com/v1/tracker/$1"
        permanent: true

El anti-patrón que vimos morir

Equipo que conozco intentó "lift and shift" en un fin de semana: export CSV de Mautic, import al CRM nuevo, apagar Mautic. Resultado: 12% de contactos con teléfono malformado quedaron como duplicados, segments dinámicos se convirtieron en listas estáticas (perdiendo la lógica), tracking JS apuntaba a endpoint inexistente por 4 días hasta que alguien notó que el formulario de demo no estaba capturando leads. Cutover hard sin shadow period es la receta para el postmortem.

La pregunta de seguimiento: ¿cuánto del código de espejo a Mautic se reusa para futuras migraciones (HubSpot, Salesforce)? Spoiler: el 80% si abstraes el adapter desde el día uno.

mautic
migracion
crm
← Volver a SCRAM AI Lab