Com vam migrar de Navision i Magento a Odoo a Rehabmedic

Cas real de migració e-commerce en el sector salut: de l'stack fragmentat Navision + Magento a Odoo Multi-web amb ETL a mida, zero downtime, rèplica PostgreSQL en viu i Top 3 a Google en 60 dies.

Migrar un negoci amb ERP i botiga en línia en funcionament —amb comandes entrant cada dia i un equip que depèn del sistema per operar— és l'escenari que més s'assembla a canviar el motor d'un avió en vol. El cas de Rehabmedic, empresa del sector d'equipament de rehabilitació mèdica, va ser exactament això: una migració de Microsoft Dynamics NAV (Navision) i Magento cap a Odoo en condicions d'operativa real, sense marge per a la caiguda del servei i amb l'objectiu addicional de llançar una nova unitat de negoci de lloguer posicionada a Google des del primer dia. El que segueix és la descripció tècnica de com es va planificar i executar.

Context: l'stack heretat i per què no escalava

Navision com a ERP: potent però tancat

Microsoft Dynamics NAV —conegut històricament com a Navision— era el sistema de gestió central de Rehabmedic: comptabilitat, inventari, proveïdors, facturació i tota l'operativa del negoci de venda d'equipament mèdic. És un ERP sòlid per al segment al qual apunta, però presenta un perfil de problemes ben conegut quan l'empresa necessita integració moderna:

  • API limitada: la integració amb sistemes externs en versions anteriors de Dynamics NAV és costosa en manteniment, fràgil davant actualitzacions i requereix connectors propietaris o middleware addicional.
  • Cost de llicència per usuari: escalar l'accés al sistema té un cost directe i creixent en llicències, la qual cosa desincentiva donar accés a rols operatius que podrien beneficiar-se de visibilitat sobre l'ERP.
  • Personalització tancada: adaptar Navision al model de negoci específic de Rehabmedic —amb les seves particularitats en el procés de lloguer, la gestió d'equips en préstec i la facturació periòdica— requeria desenvolupament en AL (Application Language), el llenguatge propietari de Microsoft, amb el seu cost i dependència associats.

Magento com a botiga: potent però desconnectada

La botiga en línia de Rehabmedic funcionava sobre Magento: una plataforma de comerç electrònic amb una capacitat funcional notable, però que a la pràctica operava com un sistema separat de l'ERP. La sincronització d'inventari, preus i comandes entre Magento i Navision se sustentava sobre integracions a mida amb fragilitat inherent. Cada actualització de qualsevol dels dos sistemes era un potencial punt de ruptura.

El símptoma més clar del problema: quan l'equip d'operacions actualitzava l'estoc a Navision, la botiga no ho reflectia en temps real. Les comandes a Magento generaven treball manual d'abocament a l'ERP. Els preus especials gestionats a Navision per a determinats clients no eren visibles a la botiga. A la pràctica, Magento i Navision eren dos sistemes que convivien però no col·laboraven.

La nova unitat de negoci: el detonant del canvi

El catalitzador de la migració va ser la decisió estratègica de llançar Alquiler Rehabmedic: una unitat de negoci independent per al lloguer d'equipament de rehabilitació mèdica. Llits articulats, cadires de rodes elèctriques, grues de trasllat, scooters de mobilitat: un catàleg de productes que es lloga per dies, setmanes o mesos amb el seu propi cicle de facturació, gestió de dipòsits, control d'estat de l'equip i logística de lliurament i recollida.

Aquesta unitat de negoci no podia construir-se sobre l'stack existent sense multiplicar la complexitat. Muntar un tercer sistema per al lloguer sobre Navision i Magento hauria creat tres sitges desconnectades. La decisió va ser migrar a una plataforma unificada que resolgués simultàniament la gestió ERP, l'e-commerce de venda i el portal de lloguer des d'una única base de dades i una única instància d'aplicació.

L'estratègia de migració: fases, prioritats i criteris d'èxit

Principi de disseny: zero downtime operatiu

El criteri d'èxit no negociable des de l'inici va ser que la botiga no podia tancar en cap moment del procés de migració. Les comandes en línia són una font d'ingressos contínua i qualsevol interrupció té un cost directe quantificable. Aquest principi va dictar tota l'arquitectura de la migració: operació paral·lela durant la fase de transició, validació incremental per mòduls i un pla de rollback provat abans del tall definitiu.

Seqüència de migració

La migració es va organitzar en quatre fases amb les seves pròpies dates de tall independents:

  1. Fase 0 — Preparació i configuració d'Odoo: instal·lació de la instància Odoo, configuració de la localització espanyola (l10n_es), pla de comptes, configuració del mòdul Multi-web per als dos llocs (venda + lloguer), staging de dades de prova.
  2. Fase 1 — Migració de mestres: clients, proveïdors i catàleg de productes de Navision. Operar en paral·lel: les comandes continuen processant-se a Navision mentre Odoo es valida amb dades reals.
  3. Fase 2 — Migració de l'e-commerce Magento: catàleg web, categories, comandes pendents i clients registrats de Magento. Tall a Odoo eCommerce amb manteniment del lloc Magento en mode només-lectura durant 48 hores de seguretat.
  4. Fase 3 — Tall ERP i llançament de Lloguer: tall operatiu definitiu de Navision, migració de saldos comptables i cartera d'efectes, activació del portal de lloguer en producció amb Schema.org complet des del dia u.

Mapatge de dades: de Navision i Magento a Odoo

Clients i contactes: Navision → Odoo

El model de clients de Navision té particularitats que requereixen decisions de mapatge explícites abans de qualsevol extracció:

Camp Navision (taula Customer)Camp Odoo (res.partner)Observacions de transformació
No. (codi client)refConservar com a referència interna per a traçabilitat
NamenameNormalitzar: trim, eliminar dobles espais
VAT Registration No.vatAfegir prefix ES si no és present; validar format NIF/CIF
Address / Address 2street / street2Mapatge directe; verificar longituds
Post Code / Cityzip / cityNormalitzar municipis vs. catàleg INE si es requereix geocoding
Phone No. / Mobile Phone No.phone / mobileNormalitzar a format E.164 (+34...)
E-MailemailValidar sintaxi; descartar nuls i invàlids
Payment Terms Codeproperty_payment_term_idCrear taula d'equivalència prèvia; crear condicions de pagament a Odoo
Customer Price Groupproperty_product_pricelistMapejar a llistes de preus d'Odoo; recrear regles de descompte
Salesperson Codeuser_idRequerir que els comercials estiguin creats a Odoo abans de la càrrega
Balanç (saldo pendent)Apunt comptable d'oberturaNo crear com a camp de partner; migrar com a assentament d'obertura

Catàleg de productes: Navision → Odoo

El catàleg d'equipament mèdic de Rehabmedic té característiques específiques del sector: referències de fabricant obligatòries per a traçabilitat, gestió de números de sèrie per a equips d'alt valor, i unitats de mesura mixtes (unitats, kits, consumibles). El mapatge del model de producte de Navision cap a Odoo requereix decisions sobre variants i traçabilitat:

Camp Navision (taula Item)Camp Odoo (product.template / product.product)Observacions
No. (referència interna)default_codeReferència interna del producte a Odoo
DescriptionnameNom del producte; enriquir amb descripció llarga a website_description per a SEO
Item Category Codecateg_idRecrear jerarquia de categories a Odoo abans de la càrrega
Base Unit of Measureuom_id / uom_po_idVerificar existència a Odoo; crear UoMs específiques si no existeixen
Unit Pricelist_pricePreu de venda base; els preus especials van a pricelists
Unit Coststandard_priceCost estàndard; afecta a la valoració d'inventari
Item Tracking Code (serial/lot)tracking ('serial' / 'lot' / 'none')Equips d'alt valor: tracking=serial; consumibles: tracking=none
Vendor No. + Vendor Item No.product.supplierinfoCrear registre supplierinfo per a cada proveïdor; és una taula separada a Odoo
Sales VAT Bus. Posting Grouptaxes_idMapejar als impostos de la localització espanyola (IVA 21%, 10%, 4%, 0%)

Comandes Magento → Odoo eCommerce

La migració de l'historial de comandes de Magento no es va plantejar com a migració completa de totes les comandes històriques —un cost alt i valor baix— sinó selectiva: comandes dels últims 12 mesos amb estat actiu (en procés, pendent d'enviament, devolucions en gestió) més la cartera de clients registrats amb el seu historial recent. Les comandes anteriors es van mantenir a Magento en mode consulta.

L'esquema de Magento i el d'Odoo tenen diferències estructurals rellevants en el model de comanda:

Entitat MagentoEntitat OdooObservacions de transformació
sales_ordersale.orderMapatge directe de capçalera; verificar estats (pending/processing/complete → sale/done/cancel)
sales_order_itemsale.order.lineVerificar que product_id existeix a Odoo abans d'importar línies
customer_entityres.partner (customer_rank=1)Deduplicar contra els clients ja migrats de Navision per correu i VAT
customer_addressres.partner (child, type='delivery')Model pare-fill a Odoo; una adreça per registre fill
catalog_productproduct.templateVincular per SKU / default_code; no duplicar productes ja migrats de Navision
catalog_categorywebsite_page + product.categoryRecrear arbre de categories web a Odoo Website
cms_page (pàgines CMS)website.page (Odoo Website)Migrar contingut HTML; revisar URLs per mantenir slugs i evitar 404

El punt més crític de la migració de Magento va ser la preservació d'URLs: les pàgines de producte i categoria indexades per Google tenien URLs amb la seva pròpia estructura a Magento. Per no perdre l'autoritat acumulada ni generar 404 que destruïssin el posicionament, es va generar un mapa de redireccions 301 complet abans del tall, mapeant cada URL de Magento al seu equivalent a Odoo eCommerce, i es va configurar a Nginx abans d'activar el nou lloc.

El procés ETL: extracció, transformació i càrrega

Extracció des de Navision

Dynamics NAV exposa la seva base de dades a través de SQL Server. Amb accés de només lectura al servidor de base de dades, l'extracció és directa mitjançant consultes. L'esquema de taules de Navision segueix una convenció de noms que inclou el nom de l'empresa com a prefix:

-- 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_];

Per als productes i l'estoc, el mateix patró d'accés directe a les taules Item, Item Ledger Entry i Value Entry permet extreure el catàleg complet i l'inventari actual en una sola fase d'extracció consistent (punt de control en el moment de l'extracció).

Extracció des de Magento

Magento emmagatzema les seves dades a MySQL amb un esquema EAV (Entity-Attribute-Value) que fa les extraccions directes més complexes que en un esquema relacional convencional. Per als atributs de producte que s'emmagatzemen en taules EAV (catalog_product_entity_varchar, catalog_product_entity_decimal, etc.) l'extracció requereix joins amb la taula d'atributs:

-- 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ó i neteja

El pipeline de transformació en Python era el nucli del procés ETL. Les tasques principals:

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)}")

Càrrega a Odoo via XML-RPC

La càrrega es va executar mitjançant l'API XML-RPC d'Odoo, que permet crear i actualitzar registres de forma programàtica amb traçabilitat completa. El script de càrrega incorporava control d'errors, registre de cada operació i la possibilitat de tornar a llançar només els registres fallits sense reprocessar els correctes:

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 i Schema.org des del primer dia

El llançament del portal de lloguer va coincidir amb la fase final de la migració. La decisió d'implementar Schema.org complet des del primer dia —no com a optimització posterior, sinó com a part de l'arquitectura des de l'inici— va ser el que va fer possible el posicionament accelerat.

Odoo Multi-web va permetre gestionar dos llocs web independents des de la mateixa instància: la botiga de venda d'equipament (que continuava amb la seva operativa habitual) i el nou portal de lloguer, amb el seu propi domini, la seva pròpia identitat visual i la seva pròpia arquitectura de continguts. L'ERP unificat era la font de dades de tots dos: el mateix catàleg de productes, el mateix inventari, els mateixos clients, però exposat de forma diferent a cada lloc segons la seva lògica de negoci.

El marcatge Schema.org implementat a les pàgines de producte del portal de lloguer cobria els tipus que Google millor interpreta per a aquest tipus de contingut:

  • Product amb offers (preu de lloguer, disponibilitat, moneda), brand, image i aggregateRating a cada fitxa de producte.
  • Organization amb les dades completes de Rehabmedic, coordenades de contacte i àrea de servei.
  • LocalBusiness amb la cobertura geogràfica del servei de lloguer.
  • FAQPage a les pàgines de categoria responent les preguntes de major volum de cerca (procés de lloguer, terminis, dipòsits, cobertura).
  • BreadcrumbList a tota la jerarquia de navegació per facilitar la comprensió estructural per part dels rastrejadors.

El resultat és verificable i està documentat: el portal de lloguer va assolir posicions al Top 3 de Google per a les paraules clau objectiu del mercat espanyol en menys de 60 dies des del llançament, sense inversió en publicitat de pagament i sense campanya de link building.

Infraestructura: rèplica PostgreSQL en viu i observabilitat amb Telegram

Alta disponibilitat amb rèplica streaming

La infraestructura sobre la qual funciona Odoo a Rehabmedic està dissenyada perquè una fallada del node primari no signifiqui temps d'inactivitat per a l'usuari. L'element central és la rèplica PostgreSQL streaming en temps real: el servidor secundari rep el WAL (Write-Ahead Log) del primari de forma contínua, mantenint una còpia fidel de l'estat de la base de dades amb un lag mínim.

L'arquitectura cobreix a més la distribució de càrrega: les consultes de només lectura —que en un lloc d'e-commerce representen la majoria del trànsit— es poden enrutar a la rèplica, reduint la pressió sobre el node primari i millorant els temps de resposta en moments de pic.

El monitoratge de l'estat de la rèplica forma part del sistema d'observabilitat i es pot verificar directament des de 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;

Observabilitat amb bots de Telegram

El sistema d'alertes es va implementar amb un enfocament pragmàtic: no el dashboard més sofisticat, sinó l'alerta més ràpida quan alguna cosa requereix atenció. Els bots de Telegram són el canal de notificació perquè són immediats, ubics (funcionen al mòbil sense aplicació addicional) i permeten estructurar alertes per severitat en diferents canals.

Les alertes cobreixen múltiples capes de l'stack:

  • Retard de replicació PostgreSQL superior al llindar configurat (indicador primerenc de sobrecàrrega del primari o problema de xarxa).
  • Worker crashes d'Odoo detectats als registres del servidor (fallades en processos de fons, cron jobs, workers web).
  • Temps de resposta HTTP del lloc per sobre del llindar d'alerta (degradació perceptible per l'usuari abans que es converteixi en caiguda).
  • Ús de CPU i memòria del servidor aproximant-se als límits configurats.
  • Fallades en jobs de còpia de seguretat automàtica (còpia de seguretat de base de dades).

El resultat operatiu és una postura proactiva: els problemes es detecten i resolen abans que l'usuari final els experimenti. Durant el període d'operació posterior al llançament, incloent pics de trànsit per campanyes i moments d'alta demanda estacional, la plataforma va mantenir disponibilitat completa.

Validació: com vam verificar que la migració va ser correcta

La càrrega de dades a Odoo no és el final del procés de migració. La validació és el pas que determina si les dades són correctes o si hi ha errors que només apareixeran en producció dies o setmanes després. Les verificacions que es van executar abans de cada tall:

  • Recomptes de registres: el nombre de clients, productes i proveïdors a Odoo ha de coincidir amb el recompte del sistema d'origen, menys els registres explícitament exclosos (bloquejats, inactius, duplicats). Tota diferència es documenta i justifica.
  • Reconciliació de saldos comptables: el saldo total de comptes a cobrar a Odoo ha de quadrar amb el balanç de tancament de Navision a la data de tall. Qualsevol diferència superior al llindar de tolerància bloqueja el go-live.
  • Verificació d'inventari: el valor total de l'estoc a Odoo versus Navision. Mostreig de productes d'alta rotació i alt valor per a verificació individual.
  • Prova de cicle complet: crear una comanda real de principi a fi a Odoo (comanda → confirmació → preparació → albarà → factura → cobrament) amb un client i producte reals del sistema migrat abans d'activar l'accés als usuaris.
  • Validació amb usuaris clau: el responsable de vendes revisa la seva cartera de clients i tarifes; el responsable de magatzem verifica l'estoc; l'administració confirma els saldos comptables. Els usuaris operatius detecten problemes que cap script automatitzat trobarà.
  • Verificació d'URLs i redireccionament: recorregut automatitzat del sitemap de Magento verificant que cada URL redirigeix correctament al seu equivalent a Odoo amb codi HTTP 301, sense 404.

Resultats verificables

  • Zero downtime operatiu durant tot el procés de migració. La botiga de venda no va interrompre el seu servei en cap moment. Les comandes es van processar sense interrupció.
  • Top 3 a Google orgànic en menys de 60 dies per a les paraules clau objectiu del portal de lloguer, sense inversió en publicitat de pagament, gràcies a l'arquitectura Schema.org implementada des del llançament.
  • Plataforma unificada: la substitució de Navision i Magento per Odoo va eliminar el cost operatiu de mantenir dos sistemes desconnectats i la fricció de sincronització manual entre ells.
  • Alta disponibilitat real: la rèplica PostgreSQL en viu va absorbir la càrrega de lectura durant els pics de trànsit sense impacte en el rendiment del node primari. Les alertes de Telegram van permetre resoldre incidències menors de forma proactiva.
  • Nova unitat de negoci operativa: Alquiler Rehabmedic llançat sobre Odoo Multi-web amb el seu propi portal, la seva pròpia lògica de negoci i el seu propi posicionament SEO, sense infraestructura addicional.

Lliçons aplicables a la teva migració

1. El mapa de redireccions és tan important com el mapa de dades

En una migració d'e-commerce, perdre les URLs indexades és perdre posicionament acumulat. L'inventari d'URLs de Magento i el seu mapatge a les URLs equivalents a Odoo s'ha de fer abans del tall, no després. Un 301 mal configurat o absent pot costar mesos de recuperació SEO.

2. La deduplicació creuada entre sistemes és el treball més laboriós

Quan el mateix client té una fitxa a l'ERP (Navision) i una altra a la botiga (Magento), amb dades parcialment diferents, decidir quina és la font de veritat i com fusionar els registres requereix criteri de negoci, no només lògica tècnica. És el treball que no es pot automatitzar completament i el que més temps consumeix a la pràctica.

3. Schema.org no és un detall de SEO: és arquitectura de plataforma

Si les dades del producte existeixen correctament a l'ERP —preu, disponibilitat, categoria, descripció— el marcatge Schema.org és el resultat natural de fer fluir aquestes dades fins a la capa HTML amb l'estructura correcta. El cost d'implementar-lo des de l'inici és mínim. El cost de no haver-ho fet des de l'inici, quan les dades ja estan en producció, és una refactorització completa de les plantilles.

4. La rèplica PostgreSQL és assegurança de vida, no luxe d'arquitectura avançada

Una instància Odoo sense replicació de base de dades en producció és una aposta: l'aposta que el disc no fallarà, que el servidor no caurà, que la còpia de seguretat manual del divendres serà suficient quan el problema succeeixi el dimecres. La replicació streaming a PostgreSQL és madura, ben documentada i té un cost d'implementació molt inferior al d'una caiguda en producció.

5. L'observabilitat proactiva és rendible des del primer dia

Un bot de Telegram que notifica quan el lag de replicació supera els 30 segons costa unes hores de configuració. Detectar aquest mateix problema quan ja ha causat inconsistència de dades o caiguda del servei costa dies d'anàlisi i recuperació, més el cost comercial del temps d'inactivitat. La relació cost-benefici no admet discussió.

Tens una migració similar pendent?

Sol·licitar auditoria tècnica gratuïta

Web scraping massiu en Python per al CRM d'Odoo (+2M referències)
Arquitectura real d'un sistema d'extracció, normalització i ingesta massiva de dades a Odoo CRM: de les fonts externes al lead qualificat, amb més de dos milions de referències gestionades en producció