main content
< Volver a blog sobre aplicaciones móviles

Migrar datos entre versiones de Drupal con Migrate API

Cómo migrar contenidos y datos entre versiones de Drupal con la Migrate API: guía técnica paso a paso

He visto esto muchas veces: un cliente llega con un Drupal 7 que lleva funcionando ocho o diez anos, un equipo que le ha perdido el miedo a la base de datos a base de queries directas, y la necesidad urgente de dar el salto a Drupal 10. O quiza no es una migracion entre versiones, sino un ERP que escupe CSVs con catálogos de producto. Sea cual sea el escenario, la Migrate API es la herramienta que Drupal trae de serie para esto.

Esta guía recorre la arquitectura de la Migrate API, muestra configuraciones YAML reales, comandos drush y las estrategias que funcionan cuando tienes que mover cientos de miles de registros.

Qué es la Migrate API y por qué usarla frente a otras alternativas

La Migrate API forma parte del núcleo de Drupal desde la versión 8. Sigue el patrón ETL (Extract, Transform, Load): extrae datos de un origen, los transforma mediante plugins de proceso y los carga en entidades Drupal de destino.

Si estás tentado de hacerlo con scripts SQL directos o importaciones manuales, te vas a arrepentir. Feeds vale para cosas sencillas, pero se queda corto cuando la complejidad crece. La Migrate API gana por estas razones:

  • Repetibilidad: ejecutas la migracion las veces que haga falta sin duplicar contenido. El sistema de mapeo relaciona cada ID de origen con su ID de destino y sabe lo que ya ha procesado.
  • Modularidad: la arquitectura de plugins te permite reutilizar componentes y crear transformaciones a medida sin tocar el núcleo.
  • Rollback nativo: cualquier migracion ejecutada se puede revertir por completo. Se eliminan las entidades creadas y se limpian las tablas de mapeo. Asi de sencillo.
  • Integración con drush: lanzar, detener, resetear y monitorizar migraciones desde la línea de comandos. Esto facilita la automatización y la integración con pipelines de despliegue.

Orígenes de datos que soporta la Migrate API

Drupal 7 como origen

El módulo migrate_drupal (incluido en el core) proporciona plugins de origen para prácticamente todas las entidades de Drupal 7: nodos, usuarios, taxonomías, campos, menús, bloques y variables de configuración. Para que funcione, lo unico que necesitas es configurar una conexion a la base de datos de Drupal 7 en el settings.php del sitio destino:

$databases['migrate']['default'] = [
  'driver' => 'mysql',
  'database' => 'drupal7_db',
  'username' => 'db_user',
  'password' => 'db_pass',
  'host' => 'localhost',
  'prefix' => '',
];

Drupal 8 y 9 como origen

Las migraciones entre versiones más recientes suelen ser menos dolorosas porque la estructura de entidades es compatible. Ojo, que "menos dolorosas" no significa triviales. Siempre hay cambios en campos de media, configuración de vistas o módulos contribuidos que requieren plugins de proceso específicos.

Bases de datos externas

La Migrate API puede leer directamente de cualquier base de datos que PDO soporte: MySQL, PostgreSQL, SQLite, SQL Server. El plugin de origen SqlBase te permite escribir consultas SQL arbitrarias que alimentan la migración.

Archivos CSV, JSON y XML

Los módulos migrate_source_csv, migrate_source_json y migrate_plus (con su plugin Url) permiten usar archivos planos o endpoints de API como origen. Esto va de maravilla para importar catálogos de productos, listados de empleados o contenidos procedentes de CMS propietarios que expongan sus datos en estos formatos.

Arquitectura de plugins: source, process y destination

Cada migración se define como una combinación de tres tipos de plugins. Vamos con cada uno.

Source plugins

Definen de dónde vienen los datos y qué campos están disponibles. Un plugin de origen para una tabla SQL personalizada tendria este aspecto:

namespace Drupal\mi_modulo\Plugin\migrate\source;

use Drupal\migrate\Plugin\migrate\source\SqlBase;
use Drupal\migrate\Row;

/**
 * @MigrateSource(id = "productos_legacy")
 */
class ProductosLegacy extends SqlBase {

  public function query() {
    return $this->select('productos', 'p')
      ->fields('p', ['id', 'nombre', 'descripcion', 'precio', 'categoria_id']);
  }

  public function fields() {
    return [
      'id' => $this->t('ID del producto'),
      'nombre' => $this->t('Nombre'),
      'descripcion' => $this->t('Descripción'),
      'precio' => $this->t('Precio'),
      'categoria_id' => $this->t('ID de categoría'),
    ];
  }

  public function getIds() {
    return ['id' => ['type' => 'integer']];
  }
}

Heredas de SqlBase, defines la query, los campos y los identificadores. Limpio.

Process plugins

Aquí es donde ocurre la magia de la transformación. Drupal incluye docenas de plugins de proceso en el core: get, default_value, migration_lookup, callback, static_map, format_date, substr, concat, entre otros.

Cuando ninguno de los existentes te sirve, te creas el tuyo. Por ejemplo, he tenido que limpiar HTML heredado de Drupal 7 más veces de las que me gustaria reconocer:

namespace Drupal\mi_modulo\Plugin\migrate\process;

use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;

/**
 * @MigrateProcessPlugin(id = "limpiar_html_legacy")
 */
class LimpiarHtmlLegacy extends ProcessPluginBase {

  public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
    // Eliminar atributos style inline del HTML antiguo
    $value = preg_replace('/\s+style="[^"]*"/', '', $value);
    // Convertir <b> a <strong>
    $value = str_replace(['<b>', '</b>'], ['<strong>', '</strong>'], $value);
    return $value;
  }
}

Destination plugins

Determinan qué tipo de entidad se crea en Drupal. Los más habituales son entity:node, entity:user, entity:taxonomy_term, entity:media y entity:file. En la práctica, rara vez necesitas crear un plugin de destino personalizado.

Escribir migraciones en YAML: estructura y ejemplos

Las migraciones se definen como archivos YAML dentro de la carpeta migrations/ de un módulo personalizado (o en config/install/ si prefieres que se importen como configuración). Un ejemplo que lo ilustra bien:

id: migrar_articulos
label: 'Migración de artículos desde base de datos legacy'
migration_group: mi_proyecto
source:
  plugin: productos_legacy
  key: migrate
process:
  type:
    plugin: default_value
    default_value: articulo
  title: nombre
  body/value: descripcion
  body/format:
    plugin: default_value
    default_value: full_html
  field_precio: precio
  field_categoria:
    plugin: migration_lookup
    migration: migrar_categorias
    source: categoria_id
destination:
  plugin: entity:node
migration_dependencies:
  required:
    - migrar_categorias

Fíjate en los puntos clave:

  • id: identificador único que drush usará para ejecutar la migración.
  • source: referencia al plugin de origen y la clave de base de datos.
  • process: mapea cada campo de destino a un campo de origen, aplicando transformaciones cuando toca.
  • destination: tipo de entidad Drupal que se creará.
  • migration_dependencies: garantiza que las categorías se migren antes que los artículos que las referencian. Esto no es opcional; si te lo saltas, vas a tener problemas de integridad.

Gestión de entity references y relaciones entre contenidos

Las referencias entre entidades son, con diferencia, el aspecto más peliagudo de cualquier migración. Un artículo puede referenciar categorías de taxonomía, autores, imágenes y otros nodos. Todo está conectado.

El plugin migration_lookup es la pieza central para resolver estas dependencias. Consulta la tabla de mapeo de otra migración para obtener el ID de destino correspondiente a un ID de origen:

field_autor:
  plugin: migration_lookup
  migration: migrar_usuarios
  source: autor_id
  no_stub: false

El parámetro no_stub: false (valor por defecto) permite que, si el usuario referenciado aún no se ha migrado, Drupal cree un "stub" -- una entidad provisional que se completará cuando se ejecute la migración de usuarios. Esto evita errores de integridad referencial y permite cierta flexibilidad en el orden de ejecución. Aun asi, respeta siempre las dependencias declaradas. Te ahorrarás dolores de cabeza.

Migración de archivos y media entities

En Drupal 7, los archivos se gestionaban como file entities. A partir de Drupal 8, el modelo recomendado son las media entities. Esto implica que una migración de D7 a D10 necesita dos pasos:

  1. Migrar los archivos físicos: copiarlos del directorio sites/default/files del sitio antiguo al nuevo, o moverlos a un CDN. El plugin de proceso file_copy gestiona la copia:
uri:
  plugin: file_copy
  source:
    - filepath_origen
    - filepath_destino
  file_exists: rename
  1. Crear media entities: un segundo YAML que toma los file entities migrados y los envuelve en media entities del tipo adecuado (imagen, documento, vídeo).

Un consejo práctico: para sitios con miles de archivos, usa file_exists: skip para evitar copias innecesarias en ejecuciones repetidas. Y verifica los permisos del directorio de destino antes de lanzar la migración. He visto migraciones que fallan silenciosamente por un directorio sin permisos de escritura.

Migración de contenido multilingüe

Si el sitio origen tiene contenido traducido, la migración se complica un punto. La estrategia que mejor funciona:

  1. Migra primero todo el contenido en el idioma por defecto.
  2. En una segunda pasada, migra las traducciones usando el plugin de destino con translations: true:
destination:
  plugin: entity:node
  translations: true
  1. Vincula cada traducción con su nodo base mediante migration_lookup sobre la migración del idioma principal.

Para sitios con muchos idiomas, el módulo migrate_plus ofrece el plugin de proceso entity_lookup, que permite buscar entidades existentes por un campo concreto (por ejemplo, el UUID original) en lugar de depender exclusivamente de la tabla de mapeo.

Comandos drush para gestionar migraciones

El módulo migrate_tools proporciona los comandos drush que vas a usar todos los dias. Tenlos a mano:

  • drush migrate:status -- muestra todas las migraciones registradas, su estado y el número de elementos pendientes, procesados y con errores.
  • drush migrate:import migrar_articulos -- ejecuta una migración específica.
  • drush migrate:import --group=mi_proyecto -- ejecuta todas las migraciones de un grupo en orden de dependencia.
  • drush migrate:import migrar_articulos --limit=100 -- procesa solo los primeros 100 elementos. Imprescindible para pruebas.
  • drush migrate:import migrar_articulos --update -- reprocesa elementos ya migrados para aplicar cambios en la configuración.
  • drush migrate:rollback migrar_articulos -- elimina todas las entidades creadas por esa migración y limpia la tabla de mapeo.
  • drush migrate:reset-status migrar_articulos -- resetea el estado si la migración quedó bloqueada a mitad de ejecución.
  • drush migrate:messages migrar_articulos -- muestra los mensajes de error y advertencia generados durante la ejecución.

Estrategias de rollback y migraciones incrementales

Rollback controlado

El rollback funciona en cascada si has definido correctamente las dependencias. Al revertir la migración de artículos, Drupal elimina los nodos creados pero no toca las categorías (que tienen su propia migración). Para revertir todo un grupo:

drush migrate:rollback --group=mi_proyecto

Un matiz que mucha gente pasa por alto: los rollbacks son tu mejor amigo durante desarrollo y pruebas. Pero una vez que el sitio está en producción con los datos migrados, olvidate. Los contenidos ya tienen URLs indexadas, comentarios asociados y referencias desde otros sistemas. Revertir a esas alturas no es viable.

Migraciones incrementales

Esto es oro para sitios que siguen activos durante el período de migración. Configuras el plugin de origen con la propiedad highWaterProperty:

source:
  plugin: productos_legacy
  key: migrate
  high_water_property:
    name: fecha_modificacion
    alias: p

Cada ejecución procesa solo los registros cuya fecha_modificacion sea posterior a la última ejecución. Te permite mantener sincronizados ambos sistemas durante semanas o meses hasta el corte definitivo. Funciona de maravilla.

Rendimiento en migraciones con grandes volúmenes

Cuando hablamos de cientos de miles de nodos o millones de registros, el rendimiento pasa a ser lo que manda:

  • Batch size: drush procesa en lotes. Ajusta el tamaño con --limit y scripts que ejecuten la migración en tramos.
  • Desactivar hooks innecesarios: durante la migración, módulos como pathauto, search_api o xmlsitemap ralentizan cada operación de guardado. Desactívalos temporalmente o configúralos para ejecución diferida.
  • Memoria: las migraciones de archivos grandes se comen la RAM. Ajusta memory_limit en php.ini o pasa el parámetro a drush: drush migrate:import migrar_archivos --limit=500 -v.
  • Índices en la base de datos de origen: si el plugin source ejecuta consultas pesadas, asegúrate de que las tablas de origen tienen índices adecuados en las columnas de filtro y ordenación. Parece obvio, pero se olvida más de lo que crees.
  • Procesamiento por cola: para migraciones que tardan horas, el módulo migrate_queue_importer distribuye el trabajo mediante el sistema de colas de Drupal. Puedes reiniciar el proceso sin perder el progreso.

Depuración de migraciones: herramientas y técnicas

Cuando una migración falla o produce resultados raros, esto es lo que hago:

drush migrate:messages: siempre el primer sitio donde miro. Muestra los errores por fila, indicando el ID de origen y el mensaje de error.

Xdebug + breakpoints en plugins: coloca breakpoints en los métodos prepareRow() del plugin source y transform() de los plugins process. Ver los datos en tiempo real no tiene precio cuando estás persiguiendo un bug.

Log condicional en prepareRow(): cuando tienes miles de filas pero solo unas pocas fallan, añade logging selectivo:

public function prepareRow(Row $row) {
  if ($row->getSourceProperty('id') == 12345) {
    $this->messenger()->addMessage('Datos fila 12345: ' . print_r($row->getSource(), TRUE));
  }
  return parent::prepareRow($row);
}

Flag --verbose en drush: drush migrate:import migrar_articulos -v muestra información detallada del procesamiento de cada fila.

Tabla migrate_map_*: cada migración crea una tabla con el prefijo migrate_map_ que almacena la correspondencia entre IDs de origen y destino, junto con el hash del origen. Consultarla directamente te permite verificar qué se ha migrado y detectar registros huérfanos.

Checklist de planificación para una migración real

Antes de escribir la primera línea de YAML, pasa por esta lista. En serio, no te la saltes:

  1. Inventario de contenidos: documenta todos los tipos de contenido, campos, taxonomías, usuarios, archivos y configuraciones que necesitas migrar. Cuenta los registros de cada tipo.
  2. Mapa de campos: crea una hoja de cálculo que relacione cada campo del origen con su equivalente en el destino. Identifica campos que desaparecen, campos nuevos y transformaciones necesarias.
  3. Orden de migraciones: dibuja un grafo de dependencias. Taxonomías y usuarios se migran antes que los nodos. Archivos antes que media entities. Media entities antes que los nodos que las referencian.
  4. Entorno de pruebas: configura un entorno de staging con una copia de la base de datos de origen. Nunca, jamas, desarrolles migraciones contra producción.
  5. Pruebas con subconjuntos: ejecuta cada migración con --limit=50 antes de lanzar el conjunto completo. Verifica manualmente que los primeros registros migrados son correctos.
  6. Plan de corte: define la ventana de tiempo: cuándo dejar de crear contenido en el sitio antiguo, cuándo ejecutar la migración final, cuándo redirigir el dominio.
  7. Redirecciones: si las URLs cambian, configura redirecciones 301 para mantener el posicionamiento en buscadores. El módulo redirect de Drupal permite importar redirecciones masivas.
  8. Validación post-migración: compara conteos de registros entre origen y destino, verifica que las imágenes se muestran correctamente y que los enlaces internos funcionan.

De la planificación a la ejecución sin sorpresas

Migrar contenidos entre versiones de Drupal o desde sistemas externos es un proyecto técnico que requiere planificación, pruebas y bastante paciencia. La Migrate API te da un framework sólido para hacerlo de forma controlada, pero la complejidad real no está en la herramienta. Está en los datos: campos mal tipados, relaciones inconsistentes, archivos huérfanos y configuraciones heredadas que nadie se molestó en documentar.

La diferencia entre una migración que se arrastra durante meses y una que se cierra en semanas está en la fase de análisis previa. Y en contar con un equipo que ya haya pasado por esto antes.

Si tu organización necesita migrar un sitio Drupal con garantías, contacta con Tangram Consulting y te ayudamos a diseñar un plan de migración realista, ejecutar las migraciones y validar los resultados antes del corte a producción.

Contacta con nosotros
Fila 1