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:
- 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.
- 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.
- 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.
- 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) | ref | Conservar como referencia interna para trazabilidad |
| Name | name | Normalizar: trim, eliminar dobles espacios |
| VAT Registration No. | vat | Añadir prefijo ES si no está presente; validar formato NIF/CIF |
| Address / Address 2 | street / street2 | Mapeo directo; verificar longitudes |
| Post Code / City | zip / city | Normalizar municipios vs catálogo INE si se requiere geocoding |
| Phone No. / Mobile Phone No. | phone / mobile | Normalizar a formato E.164 (+34...) |
| Validar sintaxis; descartar nulos e inválidos | ||
| Payment Terms Code | property_payment_term_id | Crear tabla de equivalencia previa; crear condiciones de pago en Odoo |
| Customer Price Group | property_product_pricelist | Mapear a listas de precio de Odoo; recrear reglas de descuento |
| Salesperson Code | user_id | Requerir que los comerciales estén creados en Odoo antes de la carga |
| Balance (saldo pendiente) | Apunte contable apertura | No 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_code | Referencia interna del producto en Odoo |
| Description | name | Nombre del producto; enriquecer con descripción larga en website_description para SEO |
| Item Category Code | categ_id | Recrear jerarquía de categorías en Odoo antes de la carga |
| Base Unit of Measure | uom_id / uom_po_id | Verificar existencia en Odoo; crear UoMs específicas si no existen |
| Unit Price | list_price | Precio de venta base; los precios especiales van a pricelists |
| Unit Cost | standard_price | Coste 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.supplierinfo | Crear registro supplierinfo por cada proveedor; es una tabla separada en Odoo |
| Sales VAT Bus. Posting Group | taxes_id | Mapear 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 Magento | Entidad Odoo | Observaciones de transformación |
|---|---|---|
| sales_order | sale.order | Mapeo directo de cabecera; verificar estados (pending/processing/complete → sale/done/cancel) |
| sales_order_item | sale.order.line | Verificar que product_id existe en Odoo antes de importar líneas |
| customer_entity | res.partner (customer_rank=1) | Deduplicar contra los clientes ya migrados de Navision por email y VAT |
| customer_address | res.partner (child, type='delivery') | Modelo hijo-padre en Odoo; una dirección por registro hijo |
| catalog_product | product.template | Vincular por SKU / default_code; no duplicar productos ya migrados de Navision |
| catalog_category | website_page + product.category | Recrear á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,imageyaggregateRatingen 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.