Cómo migramos de Navision y Magento a Odoo en Rehabmedic

Caso real de migración e-commerce en sector salud: del stack fragmentado Navision + Magento a Odoo Multi-web con ETL a medida, zero downtime, réplica PostgreSQL en vivo y Top 3 en Google en 60 días.

Migrar un negocio con ERP y tienda online funcionando —con pedidos entrando cada día y un equipo que depende del sistema para operar— es el escenario que más se parece a cambiar el motor de un avión en vuelo. El caso de Rehabmedic, empresa del sector de equipamiento de rehabilitación médica, fue exactamente eso: una migración de Microsoft Dynamics NAV (Navision) y Magento hacia Odoo en condiciones de operativa real, sin margen para la caída del servicio y con el objetivo adicional de lanzar una nueva unidad de negocio de alquiler posicionada desde el primer día en Google. Lo que sigue es la descripción técnica de cómo se planificó y ejecutó.

Contexto: el stack heredado y por qué no escalaba

Navision como ERP: potente pero cerrado

Microsoft Dynamics NAV —conocido históricamente como Navision— era el sistema de gestión central de Rehabmedic: contabilidad, inventario, proveedores, facturación y toda la operativa del negocio de venta de equipamiento médico. Es un ERP sólido para el segmento al que apunta, pero presenta un perfil de problemas bien conocido cuando la empresa necesita integración moderna:

  • API limitada: la integración con sistemas externos en versiones anteriores de Dynamics NAV es costosa en mantenimiento, frágil ante actualizaciones y requiere conectores propietarios o middleware adicional.
  • Coste de licencia por usuario: escalar el acceso al sistema tiene un coste directo y creciente en licencias, lo que desincentiva dar acceso a roles operativos que podrían beneficiarse de visibilidad sobre el ERP.
  • Personalización cerrada: adaptar Navision al modelo de negocio específico de Rehabmedic —con sus particularidades en el proceso de alquiler, la gestión de equipos en préstamo y la facturación periódica— requería desarrollo en AL (Application Language), el lenguaje propietario de Microsoft, con su coste y dependencia asociados.

Magento como tienda: potente pero desconectada

La tienda online de Rehabmedic corría sobre Magento: una plataforma de comercio electrónico con una capacidad funcional notable, pero que en la práctica operaba como un sistema separado del ERP. La sincronización de inventario, precios y pedidos entre Magento y Navision se sostenía sobre integraciones a medida con fragilidad inherente. Cada actualización de cualquiera de los dos sistemas era un potencial punto de ruptura.

El síntoma más claro del problema: cuando el equipo de operaciones actualizaba el stock en Navision, la tienda no lo reflejaba en tiempo real. Los pedidos en Magento generaban trabajo manual de volcado al ERP. Los precios especiales gestionados en Navision para determinados clientes no eran visibles en la tienda. En la práctica, Magento y Navision eran dos sistemas que convivían pero no colaboraban.

La nueva unidad de negocio: el detonante del cambio

El catalizador de la migración fue la decisión estratégica de lanzar Alquiler Rehabmedic: una unidad de negocio independiente para el alquiler de equipamiento de rehabilitación médica. Camas articuladas, sillas de ruedas eléctricas, grúas de traslado, scooters de movilidad: un catálogo de productos que se alquilan por días, semanas o meses con su propio ciclo de facturación, gestión de depósitos, control de estado del equipo y logística de entrega y recogida.

Esta unidad de negocio no podía construirse sobre el stack existente sin multiplicar la complejidad. Montar un tercer sistema para el alquiler encima de Navision y Magento habría creado tres silos desconectados. La decisión fue migrar a una plataforma unificada que resolviera simultáneamente la gestión ERP, el e-commerce de venta y el portal de alquiler desde una única base de datos y una única instancia de aplicación.

La estrategia de migración: fases, prioridades y criterios de éxito

Principio de diseño: zero downtime operativo

El criterio de éxito no negociable desde el inicio fue que la tienda no podía cerrar en ningún momento del proceso de migración. Los pedidos online son una fuente de ingresos continua y cualquier interrupción tiene un coste directo cuantificable. Este principio dictó toda la arquitectura de la migración: operación paralela durante la fase de transición, validación incremental por módulos y un plan de rollback probado antes del corte definitivo.

Secuencia de migración

La migración se organizó en cuatro fases con sus propias fechas de corte independientes:

  1. Fase 0 — Preparación y configuración de Odoo: instalación de la instancia Odoo, configuración de la localización española (l10n_es), chart of accounts, configuración del módulo Multi-web para los dos sitios (venta + alquiler), staging de datos de prueba.
  2. Fase 1 — Migración de maestros: clientes, proveedores y catálogo de productos de Navision. Operar en paralelo: los pedidos siguen procesándose en Navision mientras Odoo se valida con datos reales.
  3. Fase 2 — Migración del e-commerce Magento: catálogo web, categorías, pedidos pendientes y clientes registrados de Magento. Corte a Odoo eCommerce con mantenimiento del sitio Magento en modo solo-lectura durante 48 horas de seguridad.
  4. Fase 3 — Corte ERP y lanzamiento de Alquiler: corte operativo definitivo de Navision, migración de saldos contables y cartera de efectos, activación del portal de alquiler en producción con Schema.org completo desde el día uno.

Mapeo de datos: de Navision y Magento a Odoo

Clientes y contactos: Navision → Odoo

El modelo de clientes de Navision tiene particularidades que requieren decisiones de mapeo explícitas antes de cualquier extracción:

Campo Navision (tabla Customer)Campo Odoo (res.partner)Observaciones de transformación
No. (código cliente)refConservar como referencia interna para trazabilidad
NamenameNormalizar: trim, eliminar dobles espacios
VAT Registration No.vatAñadir prefijo ES si no está presente; validar formato NIF/CIF
Address / Address 2street / street2Mapeo directo; verificar longitudes
Post Code / Cityzip / cityNormalizar municipios vs catálogo INE si se requiere geocoding
Phone No. / Mobile Phone No.phone / mobileNormalizar a formato E.164 (+34...)
E-MailemailValidar sintaxis; descartar nulos e inválidos
Payment Terms Codeproperty_payment_term_idCrear tabla de equivalencia previa; crear condiciones de pago en Odoo
Customer Price Groupproperty_product_pricelistMapear a listas de precio de Odoo; recrear reglas de descuento
Salesperson Codeuser_idRequerir que los comerciales estén creados en Odoo antes de la carga
Balance (saldo pendiente)Apunte contable aperturaNo crear como campo de partner; migrar como asiento de apertura

Catálogo de productos: Navision → Odoo

El catálogo de equipamiento médico de Rehabmedic tiene características específicas del sector: referencias de fabricante obligatorias para trazabilidad, gestión de números de serie para equipos de alto valor, y unidades de medida mixtas (unidades, kits, consumibles). El mapeo del modelo de producto de Navision hacia Odoo requiere decisiones sobre variantes y rastreabilidad:

Campo Navision (tabla Item)Campo Odoo (product.template / product.product)Observaciones
No. (referencia interna)default_codeReferencia interna del producto en Odoo
DescriptionnameNombre del producto; enriquecer con descripción larga en website_description para SEO
Item Category Codecateg_idRecrear jerarquía de categorías en Odoo antes de la carga
Base Unit of Measureuom_id / uom_po_idVerificar existencia en Odoo; crear UoMs específicas si no existen
Unit Pricelist_pricePrecio de venta base; los precios especiales van a pricelists
Unit Coststandard_priceCoste estándar; afecta a valoración de inventario
Item Tracking Code (serial/lot)tracking ('serial' / 'lot' / 'none')Equipos de alto valor: tracking=serial; consumibles: tracking=none
Vendor No. + Vendor Item No.product.supplierinfoCrear registro supplierinfo por cada proveedor; es una tabla separada en Odoo
Sales VAT Bus. Posting Grouptaxes_idMapear a impuestos de la localización española (IVA 21%, 10%, 4%, 0%)

Pedidos Magento → Odoo eCommerce

La migración del historial de pedidos de Magento no se planteó como migración completa de todos los pedidos históricos —un coste alto y valor bajo— sino selectiva: pedidos de los últimos 12 meses con estado activo (en proceso, pendiente de envío, devueltos en gestión) más la cartera de clientes registrados con su historial reciente. Los pedidos anteriores se mantuvieron en Magento en modo consulta.

El esquema de Magento y el de Odoo tienen diferencias estructurales relevantes en el modelo de pedido:

Entidad MagentoEntidad OdooObservaciones de transformación
sales_ordersale.orderMapeo directo de cabecera; verificar estados (pending/processing/complete → sale/done/cancel)
sales_order_itemsale.order.lineVerificar que product_id existe en Odoo antes de importar líneas
customer_entityres.partner (customer_rank=1)Deduplicar contra los clientes ya migrados de Navision por email y VAT
customer_addressres.partner (child, type='delivery')Modelo hijo-padre en Odoo; una dirección por registro hijo
catalog_productproduct.templateVincular por SKU / default_code; no duplicar productos ya migrados de Navision
catalog_categorywebsite_page + product.categoryRecrear árbol de categorías web en Odoo Website
cms_page (páginas CMS)website.page (Odoo Website)Migrar contenido HTML; revisar URLs para mantener slugs y evitar 404

El punto más crítico de la migración de Magento fue la preservación de URLs: las páginas de producto y categoría indexadas por Google tenían URLs con su propia estructura en Magento. Para no perder la autoridad acumulada ni generar 404 que destruyeran el posicionamiento, se generó un mapa de redirecciones 301 completo antes del corte, mapeando cada URL de Magento a su equivalente en Odoo eCommerce, y se configuró en Nginx antes de activar el nuevo sitio.

El proceso ETL: extracción, transformación y carga

Extracción desde Navision

Dynamics NAV expone su base de datos a través de SQL Server. Con acceso de solo lectura al servidor de base de datos, la extracción es directa mediante queries. El esquema de tablas de Navision sigue una convención de nombres que incluye el nombre de la empresa como prefijo:

-- Ejemplo: extraer clientes activos de Navision (SQL Server)
-- El prefijo de tabla varía según el nombre configurado de la empresa
SELECT
    c.[No_]                     AS navision_code,
    c.[Name]                    AS name,
    c.[VAT Registration No_]    AS vat,
    c.[Address]                 AS street,
    c.[Address 2]               AS street2,
    c.[Post Code]               AS zip,
    c.[City]                    AS city,
    c.[Phone No_]               AS phone,
    c.[Mobile Phone No_]        AS mobile,
    c.[E-Mail]                  AS email,
    c.[Payment Terms Code]      AS payment_term_code,
    c.[Customer Price Group]    AS pricelist_code,
    c.[Salesperson Code]        AS salesperson_code,
    c.[Blocked]                 AS blocked
FROM
    [RehabmedicDB].[dbo].[Rehabmedic$Customer] c
WHERE
    c.[Blocked] = 0   -- Solo clientes no bloqueados
ORDER BY
    c.[No_];

Para los productos y el stock, el mismo patrón de acceso directo a las tablas Item, Item Ledger Entry y Value Entry permite extraer el catálogo completo y el inventario actual con una sola fase de extracción consistente (punto de control en el momento de la extracción).

Extracción desde Magento

Magento almacena sus datos en MySQL con un esquema EAV (Entity-Attribute-Value) que hace las extracciones directas más complejas que en un esquema relacional convencional. Para los atributos de producto que se almacenan en tablas EAV (catalog_product_entity_varchar, catalog_product_entity_decimal, etc.) la extracción requiere joins con la tabla de atributos:

-- Extraer productos de Magento (MySQL) con sus atributos principales
-- Magento 1.x / 2.x (adaptar según versión)
SELECT
    e.entity_id                         AS magento_id,
    e.sku                               AS sku,
    e.type_id                           AS product_type,
    va_name.value                       AS name,
    va_desc.value                       AS description,
    va_url.value                        AS url_key,
    de_price.value                      AS price,
    de_cost.value                       AS cost,
    ins.qty                             AS stock_qty
FROM
    catalog_product_entity e
    LEFT JOIN catalog_product_entity_varchar va_name
        ON va_name.entity_id = e.entity_id
        AND va_name.attribute_id = (SELECT attribute_id FROM eav_attribute
            WHERE attribute_code = 'name' AND entity_type_id = 4)
    LEFT JOIN catalog_product_entity_text va_desc
        ON va_desc.entity_id = e.entity_id
        AND va_desc.attribute_id = (SELECT attribute_id FROM eav_attribute
            WHERE attribute_code = 'description' AND entity_type_id = 4)
    LEFT JOIN catalog_product_entity_varchar va_url
        ON va_url.entity_id = e.entity_id
        AND va_url.attribute_id = (SELECT attribute_id FROM eav_attribute
            WHERE attribute_code = 'url_key' AND entity_type_id = 4)
    LEFT JOIN catalog_product_entity_decimal de_price
        ON de_price.entity_id = e.entity_id
        AND de_price.attribute_id = (SELECT attribute_id FROM eav_attribute
            WHERE attribute_code = 'price' AND entity_type_id = 4)
    LEFT JOIN catalog_product_entity_decimal de_cost
        ON de_cost.entity_id = e.entity_id
        AND de_cost.attribute_id = (SELECT attribute_id FROM eav_attribute
            WHERE attribute_code = 'cost' AND entity_type_id = 4)
    LEFT JOIN cataloginventory_stock_item ins
        ON ins.product_id = e.entity_id
WHERE
    e.type_id IN ('simple', 'configurable')
ORDER BY
    e.entity_id;

Transformación y limpieza

El pipeline de transformación en Python era el núcleo del proceso ETL. Las tareas principales:

import pandas as pd
import re

# --- Transformación de clientes (Navision) ---

def normalizar_vat_es(vat_raw):
    """Normaliza NIF/CIF español: quita guiones/espacios, añade prefijo ES."""
    if not vat_raw or str(vat_raw).strip() == '':
        return False
    vat = re.sub(r'[\s\-\.]+', '', str(vat_raw).upper())
    if not vat.startswith('ES'):
        vat = 'ES' + vat
    return vat

def normalizar_telefono_es(tel_raw):
    """Intenta normalizar a formato +34XXXXXXXXX."""
    if not tel_raw or str(tel_raw).strip() == '':
        return False
    digits = re.sub(r'\D', '', str(tel_raw))
    if digits.startswith('34') and len(digits) == 11:
        return '+' + digits
    if len(digits) == 9 and digits[0] in ('6', '7', '8', '9'):
        return '+34' + digits
    return False  # No normalizable automáticamente

df_nav = pd.read_csv('navision_customers_extract.csv', encoding='utf-8')
df_nav['vat_odoo']   = df_nav['vat'].apply(normalizar_vat_es)
df_nav['phone_odoo'] = df_nav['phone'].apply(normalizar_telefono_es)
df_nav['name_odoo']  = df_nav['name'].str.strip().str.title()

# Detectar clientes bloqueados (no migrar como activos)
assert 'blocked' in df_nav.columns
df_nav_activos = df_nav[df_nav['blocked'] == 0].copy()
print(f"Clientes a migrar: {len(df_nav_activos)} / {len(df_nav)} total")

# --- Deduplicación cruzada Navision + Magento ---
# Los clientes que compraron también en la tienda Magento
# pueden existir en ambos sistemas con distintos códigos.
# Clave de deduplicación: VAT (más fiable) o email (segundo nivel)

df_mag_customers = pd.read_csv('magento_customers_extract.csv', encoding='utf-8')
df_mag_customers['vat_odoo'] = df_mag_customers['taxvat'].apply(normalizar_vat_es)

# Marcar cuáles de Magento ya existen en Navision (no duplicar en Odoo)
mag_vats = set(df_mag_customers[df_mag_customers['vat_odoo'] != False]['vat_odoo'])
nav_vats = set(df_nav_activos[df_nav_activos['vat_odoo'] != False]['vat_odoo'])
solapados_vat = mag_vats & nav_vats
print(f"Clientes en ambos sistemas (por VAT): {len(solapados_vat)}")

# Sólo cargar en Odoo los clientes Magento que NO estén ya en Navision
df_mag_nuevos = df_mag_customers[
    ~df_mag_customers['vat_odoo'].isin(solapados_vat) |
    (df_mag_customers['vat_odoo'] == False)
].copy()
print(f"Clientes Magento nuevos a crear en Odoo: {len(df_mag_nuevos)}")

Carga en Odoo vía XML-RPC

La carga se ejecutó mediante la API XML-RPC de Odoo, que permite crear y actualizar registros de forma programática con trazabilidad completa. El script de carga incorporaba control de errores, log de cada operación y la posibilidad de relanzar solo los registros fallidos sin re-procesar los exitosos:

import xmlrpc.client
import json
import logging

logging.basicConfig(level=logging.INFO,
    format='%(asctime)s %(levelname)s %(message)s',
    handlers=[logging.FileHandler('migration_load.log'), logging.StreamHandler()])
log = logging.getLogger('migration')

ODOO_URL  = 'https://odoo-staging.rehabmedic.com'  # Primero staging, luego prod
DB        = 'rehabmedic_odoo'
USER      = 'migration@rehabmedic.com'
PASSWORD  = '[REDACTED]'

common = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/common')
uid    = common.authenticate(DB, USER, PASSWORD, {})
models = xmlrpc.client.ServerProxy(f'{ODOO_URL}/xmlrpc/2/object')

# Obtener IDs de países, comunidades autónomas, etc. para los campos many2one
spain_id = models.execute_kw(DB, uid, PASSWORD, 'res.country', 'search',
    [[('code', '=', 'ES')]])[0]

def cargar_cliente(row, pricelist_map, payment_term_map):
    vals = {
        'name':          row['name_odoo'],
        'ref':           str(row['navision_code']),
        'customer_rank': 1,
        'lang':          'es_ES',
        'country_id':   spain_id,
    }
    for campo_src, campo_dst in [
        ('vat_odoo',   'vat'),
        ('phone_odoo', 'phone'),
        ('street',     'street'),
        ('zip',        'zip'),
        ('city',       'city'),
        ('email',      'email'),
    ]:
        val = row.get(campo_src)
        if val and str(val).strip() not in ('', 'False', 'nan'):
            vals[campo_dst] = str(val).strip()
    if row.get('pricelist_code') in pricelist_map:
        vals['property_product_pricelist'] = pricelist_map[row['pricelist_code']]
    if row.get('payment_term_code') in payment_term_map:
        vals['property_payment_term_id'] = payment_term_map[row['payment_term_code']]
    return models.execute_kw(DB, uid, PASSWORD, 'res.partner', 'create', [vals])

errores = []
ok = 0
for idx, row in df_nav_activos.iterrows():
    try:
        pid = cargar_cliente(row, pricelist_map, payment_term_map)
        ok += 1
        if ok % 200 == 0:
            log.info(f'Progreso: {ok} clientes cargados')
    except Exception as exc:
        errores.append({'idx': idx, 'code': row['navision_code'], 'error': str(exc)})
        log.error(f'Error en cliente {row["navision_code"]}: {exc}')

log.info(f'Carga completada. OK: {ok} | Errores: {len(errores)}')
if errores:
    with open('errores_carga.json', 'w') as f:
        json.dump(errores, f, ensure_ascii=False, indent=2)

La capa SEO: Odoo Multi-web y Schema.org desde el día uno

El lanzamiento del portal de alquiler coincidió con la fase final de la migración. La decisión de implementar Schema.org completo desde el primer día —no como optimización posterior, sino como parte de la arquitectura desde el inicio— fue lo que hizo posible el posicionamiento acelerado.

Odoo Multi-web permitió gestionar dos sitios web independientes desde la misma instancia: la tienda de venta de equipamiento (que continuaba con su operativa habitual) y el nuevo portal de alquiler, con su propio dominio, su propia identidad visual y su propia arquitectura de contenidos. El ERP unificado era la fuente de datos de ambos: el mismo catálogo de productos, el mismo inventario, los mismos clientes, pero expuesto de forma distinta en cada sitio según su lógica de negocio.

El marcado Schema.org implementado en las páginas de producto del portal de alquiler cubría los tipos que Google mejor interpreta para este tipo de contenido:

  • Product con offers (precio de alquiler, disponibilidad, moneda), brand, image y aggregateRating en cada ficha de producto.
  • Organization con los datos completos de Rehabmedic, coordenadas de contacto y área de servicio.
  • LocalBusiness con la cobertura geográfica del servicio de alquiler.
  • FAQPage en las páginas de categoría respondiendo las preguntas de mayor volumen de búsqueda (proceso de alquiler, plazos, depósitos, cobertura).
  • BreadcrumbList en toda la jerarquía de navegación para facilitar la comprensión estructural por parte de los rastreadores.

El resultado es verificable y está documentado: el portal de alquiler alcanzó posiciones en el Top 3 de Google para las keywords objetivo del mercado español en menos de 60 días desde el lanzamiento, sin inversión en publicidad de pago y sin campaña de link building.

Infraestructura: réplica PostgreSQL en vivo y observabilidad con Telegram

Alta disponibilidad con réplica streaming

La infraestructura sobre la que corre Odoo en Rehabmedic está diseñada para que un fallo del nodo primario no signifique tiempo de inactividad para el usuario. El elemento central es la réplica PostgreSQL streaming en tiempo real: el servidor secundario recibe el WAL (Write-Ahead Log) del primario de forma continua, manteniendo una copia fiel del estado de la base de datos con un lag mínimo.

La arquitectura cubre además la distribución de carga: las consultas de solo lectura —que en un sitio de e-commerce representan la mayoría del tráfico— se pueden enrutar a la réplica, reduciendo la presión sobre el nodo primario y mejorando los tiempos de respuesta en momentos de pico.

La monitorización del estado de la réplica forma parte del sistema de observabilidad y se puede verificar directamente desde PostgreSQL:

-- Verificar estado de la replicación en el nodo primario
SELECT
    client_addr,
    state,
    sent_lsn,
    write_lsn,
    flush_lsn,
    replay_lsn,
    (sent_lsn - replay_lsn)::bigint   AS replication_lag_bytes,
    write_lag,
    flush_lag,
    replay_lag
FROM
    pg_stat_replication
ORDER BY
    client_addr;

-- Verificar desde la réplica que está recibiendo WAL correctamente
SELECT
    status,
    receive_start_lsn,
    received_lsn,
    last_msg_send_time,
    last_msg_receipt_time,
    sender_host,
    sender_port
FROM
    pg_stat_wal_receiver;

Observabilidad con bots de Telegram

El sistema de alertas se implementó con un enfoque pragmático: no el dashboard más sofisticado, sino la alerta más rápida cuando algo requiere atención. Los bots de Telegram son el canal de notificación porque son inmediatos, ubicuos (funcionan en el móvil sin aplicación adicional) y permiten estructurar alertas por severidad en distintos canales.

Las alertas cubren múltiples capas del stack:

  • Retraso de replicación PostgreSQL superior al umbral configurado (indicador temprano de sobrecarga del primario o problema de red).
  • Worker crashes de Odoo detectados en los logs del servidor (fallos en procesos de fondo, cron jobs, workers web).
  • Tiempo de respuesta HTTP del sitio por encima del umbral de alerta (degradación perceptible por el usuario antes de que se convierta en caída).
  • Uso de CPU y memoria del servidor acercándose a límites configurados.
  • Fallos en jobs de backup automático (copia de seguridad de base de datos).

El resultado operativo es una postura proactiva: los problemas se detectan y resuelven antes de que el usuario final los experimente. Durante el período de operación posterior al lanzamiento, incluyendo picos de tráfico por campañas y momentos de alta demanda estacional, la plataforma mantuvo disponibilidad completa.

Validación: cómo verificamos que la migración fue correcta

La carga de datos en Odoo no es el final del proceso de migración. La validación es el paso que determina si los datos son correctos o si hay errores que solo aparecerán en producción días o semanas después. Las verificaciones que se ejecutaron antes de cada corte:

  • Conteos de registros: el número de clientes, productos y proveedores en Odoo debe coincidir con el recuento del sistema de origen, menos los registros explícitamente excluidos (bloqueados, inactivos, duplicados). Toda diferencia se documenta y justifica.
  • Reconciliación de saldos contables: el saldo total de cuentas por cobrar en Odoo debe cuadrar con el balance de cierre de Navision a la fecha de corte. Cualquier diferencia superior al umbral de tolerancia bloquea el go-live.
  • Verificación de inventario: el valor total del stock en Odoo versus Navision. Muestreo de productos de alta rotación y alto valor para verificación individual.
  • Prueba de ciclo completo: crear un pedido real de principio a fin en Odoo (pedido → confirmación → preparación → albarán → factura → cobro) con un cliente y producto reales del sistema migrado antes de activar el acceso a usuarios.
  • Validación con usuarios clave: el responsable de ventas revisa su cartera de clientes y tarifas; el responsable de almacén verifica el stock; la administración confirma los saldos contables. Los usuarios operativos detectan problemas que ningún script automatizado encontrará.
  • Verificación de URLs y redireccionamiento: recorrido automatizado del sitemap de Magento verificando que cada URL redirige correctamente a su equivalente en Odoo con código HTTP 301, sin 404.

Resultados verificables

  • Zero downtime operativo durante todo el proceso de migración. La tienda de venta no interrumpió su servicio en ningún momento. Los pedidos se procesaron sin interrupción.
  • Top 3 en Google orgánico en menos de 60 días para las keywords objetivo del portal de alquiler, sin inversión en publicidad de pago, gracias a la arquitectura Schema.org implementada desde el lanzamiento.
  • Plataforma unificada: la sustitución de Navision y Magento por Odoo eliminó el coste operativo de mantener dos sistemas desconectados y la fricción de sincronización manual entre ellos.
  • Alta disponibilidad real: la réplica PostgreSQL en vivo absorbió la carga de lectura durante los picos de tráfico sin impacto en el rendimiento del nodo primario. Las alertas de Telegram permitieron resolver incidencias menores de forma proactiva.
  • Nueva unidad de negocio operativa: Alquiler Rehabmedic lanzado sobre Odoo Multi-web con su propio portal, su propia lógica de negocio y su propio posicionamiento SEO, sin infraestructura adicional.

Lecciones aplicables a tu migración

1. El mapa de redirecciones es tan importante como el mapa de datos

En una migración de e-commerce, perder las URLs indexadas es perder posicionamiento acumulado. El inventario de URLs de Magento y su mapeo a las URLs equivalentes en Odoo debe hacerse antes del corte, no después. Un 301 mal configurado o ausente puede costar meses de recuperación SEO.

2. La deduplicación cruzada entre sistemas es el trabajo más laborioso

Cuando el mismo cliente tiene una ficha en el ERP (Navision) y otra en la tienda (Magento), con datos parcialmente distintos, decidir cuál es la fuente de verdad y cómo fusionar los registros requiere criterio de negocio, no solo lógica técnica. Es el trabajo que no se puede automatizar completamente y el que más tiempo consume en la práctica.

3. Schema.org no es un detalle de SEO: es arquitectura de plataforma

Si los datos del producto existen correctamente en el ERP —precio, disponibilidad, categoría, descripción— el marcado Schema.org es el resultado natural de hacer fluir esos datos hasta la capa HTML con la estructura correcta. El coste de implementarlo desde el inicio es mínimo. El coste de no haberlo hecho desde el inicio, cuando los datos ya están en producción, es una refactorización completa de las plantillas.

4. La réplica PostgreSQL es seguro de vida, no lujo de arquitectura avanzada

Una instancia Odoo sin replicación de base de datos en producción es una apuesta: la apuesta de que el disco no va a fallar, de que el servidor no va a caerse, de que el backup manual del viernes va a ser suficiente cuando el problema ocurra el miércoles. La replicación streaming en PostgreSQL es madura, bien documentada y tiene un coste de implementación muy inferior al de una caída en producción.

5. La observabilidad proactiva es rentable desde el primer día

Un bot de Telegram que notifica cuando el lag de replicación supera los 30 segundos cuesta unas horas de configuración. Detectar ese mismo problema cuando ya ha causado inconsistencia de datos o caída del servicio cuesta días de análisis y recuperación, más el coste comercial del tiempo de inactividad. La relación coste-beneficio no admite discusión.

¿Tienes una migración similar pendiente?

Solicitar auditoría técnica gratuita

Web scraping masivo en Python para el CRM de Odoo (+2M referencias)
Arquitectura real de un sistema de extracción, normalización e ingesta masiva de datos en Odoo CRM: de las fuentes externas al lead cualificado, con más de dos millones de referencias gestionadas en producción