← SCRAM AI Lab
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
276 lecturas

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.
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;
}
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.
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
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.
Artículos relacionados
Kimi K3, GLM-5.3 y DeepSeek V4-Flash: la frontera abierta de agosto 2026
El ranking open-weight se reordenó en tres semanas: Kimi K3 (Moonshot, 27 jul) lidera como todo-terreno con 2.8T de parámetros, 1M de contexto y 88.3 en Terminal-Bench 2.1; Z.ai lanzó GLM-5.3 el 14 de agosto con post-training escalado; y DeepSeek V4-Flash queda a 2.3 puntos de Opus 4.8 costando $0.14/$0.28 por millón de tokens. Análisis de cuál conviene según tu carga — y qué licencia trae cada uno.
GLM-5.2: la apuesta de Z.ai por 1M de contexto para agentes de coding
Z.ai lanzó GLM-5.2 el 13 de junio de 2026: contexto extendido de 200K a 1M tokens sin pérdida y dos niveles de thinking effort. Pensado para codebases enormes en una sola pasada. Aquí qué cambia para quien enruta varios modelos en producción.
Sakana Fugu: orquestar modelos frontera como un solo modelo
Sakana AI lanzó Fugu (jun 2026): un foundation model que enruta cada tarea al mejor LLM de un pool intercambiable. Fugu Ultra rivaliza con Fable 5. Qué cambia para quien construye sistemas multi-modelo.