En la empresa en la que trabajo llevamos años utilizando Odoo 12. Durante ese tiempo he diseñado y desarrollado más de ciento veinte módulos propios. Algunos son aplicaciones completas, con sus modelos, pantallas y flujos de trabajo. Otros son ampliaciones mucho más pequeñas sobre ventas, compras, almacén, fabricación, contabilidad o cualquier otra parte del sistema estándar.

Más pronto que tarde tendremos que plantear una migración a una versión actual.

Antes de migrar una sola línea hay que responder una pregunta bastante más importante:

¿De verdad necesitamos trasladar todo lo que existe ahora mismo?

La primera decisión no es técnica

Cuando una instalación lleva muchos años funcionando, resulta fácil asumir que migrar consiste en reproducirla en una versión nueva.

Tenemos estos módulos, estas vistas, estas automatizaciones y estos procesos. Por tanto, adaptamos todo el código hasta que vuelva a funcionar.

Pero que una funcionalidad exista no significa que siga siendo necesaria. Puede que se desarrollara para cubrir una carencia de la versión antigua que hoy ya resuelve Odoo de serie. Puede que respondiera a un proceso que la empresa ha abandonado. Puede incluso que se solicitara, se implantara y apenas se haya utilizado desde entonces.

También puede ocurrir que la necesidad siga existiendo, pero que la solución que construimos hace años ya no sea la mejor manera de resolverla.

Por eso empezaría inventariando necesidades y funcionalidades. Para cada una querría saber:

  • Qué problema pretendía resolver.
  • Quién la utiliza actualmente.
  • Con qué frecuencia se utiliza.
  • De qué otras partes del sistema depende.
  • Si la versión de destino ofrece ya una alternativa estándar.
  • Qué ocurriría si decidiéramos no trasladarla.

La unidad de decisión no debería ser el módulo. Debería ser la necesidad que ese módulo resolvía.

Las decisiones de software también envejecen

Cuando implantamos Odoo 12 veníamos de otro ERP. Los usuarios estaban acostumbrados a trabajar de una forma muy concreta, especialmente en operaciones con muchas líneas y mucha información comercial.

El sistema anterior era una aplicación nativa de escritorio y ofrecía una agilidad que, en algunos puntos, Odoo no tenía. Para que el cambio resultara viable hubo que desarrollar bastante funcionalidad, especialmente en la interfaz, hasta conseguir que determinados trabajos fueran rápidos y cómodos.

Muchas de aquellas decisiones fueron acertadas. Respondían a problemas reales y permitieron que la implantación funcionara.

Con el tiempo he llegado a la conclusión de que el software envejece mejor cuando la decisión que lo originó era arquitectónicamente sensata: resolvía el problema con el mecanismo mínimo necesario, sin acoplarse más de lo imprescindible a lo que tenía alrededor. Envejece peor cuando resuelve el problema a base de capas, condiciones y casos particulares, hasta que la solución termina dependiendo de todo lo que la rodea.

Casi nunca se llega a la versión más simple de algo añadiendo. Se llega quitando, hasta que no queda nada que sobre. Un desarrollo se acerca a una solución refinada no cuando ya no puedes añadirle nada más, sino cuando ya no puedes quitarle nada sin que deje de resolver el problema.

Pero una decisión acertada hace años no tiene por qué seguir siendo la decisión correcta hoy.

Odoo ha cambiado. La empresa también. Los procesos han evolucionado, los usuarios trabajan de otra manera y algunas limitaciones del sistema antiguo pueden haber desaparecido.

Migrar sin revisar esas decisiones supondría asumir que todas han envejecido bien. Y eso rara vez es cierto en una instalación que ha evolucionado durante tanto tiempo.

Una migración debería servir para trasladar lo que aporta valor, pero también para retirar lo que se ha quedado por inercia.

Es parecido a una mudanza. Si llevas diez años sin abrir una caja, probablemente no tenga sentido pasar por el esfuerzo de trasladarla, encontrarle otro sitio y seguir guardándola durante otros diez.

Con el código ocurre lo mismo.

Hay que inventariar lo visible y lo invisible

Para saber qué merece la pena conservar no basta con preguntar a los usuarios qué pantallas utilizan.

Una parte importante del comportamiento de Odoo sucede sin que nadie la vea.

Puede haber acciones planificadas que envían correos, actualizan datos o mantienen la coherencia de determinados procesos. Puede haber automatismos que se ejecutan cuando se confirma un pedido o se completa un movimiento de almacén. Y puede haber pequeños arreglos que se crearon para corregir periódicamente una situación que el sistema dejaba en un estado incoherente.

Desde el punto de vista del usuario, todo eso simplemente “funciona”.

Pero al preparar una migración hay que hacerlo visible.

Una acción planificada puede representar una funcionalidad imprescindible: por ejemplo, enviar una confirmación a determinada hora o sincronizar información con otro sistema.

También puede ser un parche histórico. Quizá en algún momento aparecía ocasionalmente un movimiento de stock en un estado incorrecto y, en vez de corregir la causa, se creó una tarea automática que localizaba esos movimientos y aplicaba la misma reparación que habría hecho una persona.

Antes de trasladar esa tarea habría que preguntarse:

  • ¿Sigue produciéndose el problema?
  • ¿Sabemos cuál era su causa?
  • ¿La nueva versión ya evita esa situación?
  • ¿Tiene sentido conservar el parche o deberíamos corregir el origen?

El inventario debe cubrir, por tanto, dos capas:

  • Lo que hacen activamente los usuarios.
  • Lo que hace el sistema por debajo para que los procesos sigan funcionando.

Si solo revisamos menús y pantallas, dejaremos fuera una parte importante del sistema real.

No todos los módulos se migran de la misma manera

También distinguiría claramente entre una aplicación propia y una extensión de un módulo estándar.

Una aplicación autocontenida suele representar una parte concreta del negocio mediante modelos y flujos que hemos definido nosotros. Sus datos, estados y reglas responden principalmente a nuestra lógica.

Es posible que haya que actualizar APIs, adaptar vistas o cambiar alguna convención del framework, pero el concepto central de la aplicación sigue siendo el mismo. Si las dependencias son limitadas, suele ser relativamente fácil entender qué hace y qué hay que adaptar.

Una extensión del core es diferente.

Si un módulo modifica ventas, almacén, contabilidad o fabricación, depende del funcionamiento interno que tenía Odoo en aquella versión. Y ese funcionamiento puede haber cambiado sustancialmente.

Un ejemplo conocido es la evolución del modelo de facturas y asientos contables. En versiones antiguas existían modelos diferenciados que posteriormente se unificaron alrededor de account.move. Una personalización construida sobre el modelo anterior no puede trasladarse simplemente cambiando cuatro nombres.

Antes de tocar nada, hay que entender por qué modificamos aquel comportamiento en su momento, si esa necesidad sigue existiendo hoy, cómo resuelve Odoo ahora ese mismo proceso y, ya con eso claro, decidir sobre qué modelo o método merece la pena intervenir, o si merece la pena intervenir siquiera.

Tengo un ejemplo propio que ilustra bien este problema. Hace años construimos un módulo de gestión de proyectos apoyado por completo en el CRM de Odoo. Todo lo que gestiona ese módulo se apoya, en el fondo, sobre registros de crm.lead: oportunidades a las que hemos ido añadiendo campos hasta que ya no se parecen en nada a una oportunidad comercial, más un flujo de estados propio construido encima. Funciona y, de hecho, se usa a diario. Pero cualquier cambio en ese módulo obliga a pensar primero qué le puede pasar al CRM real, porque comparten modelo y comparten lógica.

Si tuviera que rediseñarlo hoy, no lo apoyaría en absoluto sobre el CRM. Crearía un módulo propio, con los modelos, campos y el flujo que ese proceso necesita, sin tocar crm.lead para nada. Es un caso claro de Rediseñar: la necesidad de negocio sigue siendo real, pero la solución que construimos en su momento generó una dependencia que hoy solo complica las cosas.

Y aquí viene la mejor parte: en paralelo existe otro módulo, apenas utilizado, pensado para prospección. Acabamos usando el módulo CRM para gestionar proyectos e inventamos nuestra propia solución para CRM.

Tenemos dos soluciones a medida: una que podría haber vivido en un módulo core y otra que se escribió sobre este módulo cuando debería haber sido un desarrollo independiente. Es el tipo de detalle que solo detectas si conoces el sistema o si haces inventario con calma.

Por eso la dificultad de una migración no depende únicamente del tamaño del módulo. Depende mucho de cuánto se apoya en el núcleo de Odoo y de cuánto ha cambiado ese núcleo.

Cuatro posibles decisiones

Después de completar el inventario, clasificaría cada funcionalidad en una de estas cuatro decisiones:

Decisión Cuándo tendría sentido
Conservar Sigue siendo necesaria, aporta valor y representa una lógica propia del negocio.
Volver al estándar La versión nueva de Odoo ya resuelve adecuadamente la necesidad.
Rediseñar La necesidad continúa, pero la solución actual ha envejecido mal o genera demasiadas dependencias.
Retirar Ya no se utiliza, responde a un proceso desaparecido o cuesta más mantenerla que el valor que aporta.

Un ejemplo real de Retirar: durante la pandemia desarrollamos un módulo para reservar franjas horarias y controlar el aforo de una instalación de uso interno. Tuvo sentido en su momento, pero ahora lleva años sin utilizarse.

Lo interesante no es tanto que haya que retirarlo, sino cómo se diseñó. Si en vez de construir una solución para aquel caso concreto hubiéramos construido un sistema genérico de reserva de recursos (salas, vehículos, material o cualquier otro elemento compartido), probablemente seguiría vivo hoy, aplicado a alguna otra cosa.

Se diseñó para resolver el problema tal y como se planteó, no la necesidad genérica que había detrás. Ese es justo el tipo de decisión que envejece mal: se construyó demasiado pegada al síntoma concreto.

La decisión no debería tomarla únicamente quien conoce el código.

Para hacerlo bien hay que conocer tres cosas:

  • El sistema actual.
  • La versión a la que se quiere migrar.
  • La forma real de trabajar de la empresa.

Si no conocemos bien la nueva versión, no podremos detectar que una personalización antigua ya tiene una alternativa estándar. Si no conocemos la empresa, tampoco podremos distinguir una rareza histórica de una capacidad realmente diferencial.

Dónde puede ayudar la IA

Una instalación con muchos módulos puede exigir muchas horas solo para construir una primera fotografía técnica.

Ahí, bien utilizada, la IA puede resultar muy útil.

Lo primero que le pediría sería trabajo mecánico:

  • Obtener el listado de módulos instalados.
  • Leer sus manifests.
  • Resumir qué declara hacer cada uno.
  • Construir un mapa inicial de dependencias.
  • Agrupar módulos relacionados.
  • Detectar APIs o patrones antiguos.
  • Localizar módulos especialmente acoplados al core.
  • Ayudar a generar documentación inicial.

Después se puede profundizar por grupos: analizar diez módulos relacionados, revisar el resultado y continuar con el siguiente bloque.

Y aquí entra la revisión humana. Un modelo puede decirnos de qué depende un módulo y resumir su código, pero no sabe por sí solo cuánto valor tiene el proceso para la empresa ni qué consecuencias tendría retirarlo.

La decisión final sigue necesitando conocimiento técnico, conocimiento del negocio y conversación con los usuarios.

Si el análisis se realiza con un servicio externo, también hay que decidir qué código puede salir de la infraestructura de la empresa, con qué proveedor y bajo qué condiciones. No todo repositorio puede subirse alegremente a cualquier modelo.

La IA también puede ayudar más adelante a adaptar patrones de código, mejorar documentación, proponer tests o identificar oportunidades para desacoplar módulos. Pero antes de llegar ahí hay que decidir qué código merece realmente ese esfuerzo.

No tiene sentido utilizar IA para migrar más rápido algo que no deberíamos migrar.

Saber de dónde partimos y a dónde queremos llegar

Antes de empezar también hay que definir por qué se hace la migración.

“Migrar porque la versión es antigua” es cierto, pero insuficiente.

En nuestro caso hay varias razones posibles:

  • Dejar de depender de runtimes obsoletos: nuestro Odoo 12 y su ecosistema de dependencias nos mantienen en Python 3.8, y cada año que pasa aumenta la distancia respecto a cualquier proyecto nuevo.
  • Reducir riesgos de seguridad.
  • Beneficiarnos de años de mejoras en rendimiento y usabilidad.
  • Acceder a funcionalidades más maduras en áreas que ahora tienen más importancia para la empresa.
  • Limpiar módulos que ya no se utilizan.
  • Reducir dependencias innecesarias.
  • Recuperar capacidad para seguir evolucionando el sistema.

Para mí, esa última idea es especialmente importante.

El objetivo, más allá de limpiar código antiguo, pulir lo que se queda y mejorar tests y documentación, es dejar de estar secuestrados por decisiones técnicas de hace más de una década.

Seguir atados a Python 3.8 por nuestra instalación actual implica convivir con dependencias que han dejado de mantenerse, una superficie de riesgo cada vez más difícil de gestionar y la imposibilidad de utilizar con normalidad herramientas actuales.

El objetivo es, por tanto, recuperar el control de un sistema que debe seguir siendo mantenible durante muchos años.

Si no sabemos qué queremos conseguir, no podremos valorar si la migración ha salido bien.

En algún momento habrá que congelar el sistema actual

Hay una dificultad adicional: mientras se prepara una migración, la instalación antigua sigue viva.

Los usuarios continúan trabajando y siguen apareciendo peticiones: un botón nuevo, una pequeña automatización, un campo adicional o un cambio que ahorra unos minutos.

Si seguimos desarrollando sin límite sobre la versión de origen, el punto de partida de la migración cambia constantemente.

Por eso creo que llegará un momento en que habrá que poner el sistema actual en una especie de modo de mantenimiento, abordando principalmente incidencias críticas y corrigiendo errores importantes.

Significa dejar de incorporar mejoras menores cuya vida útil será muy corta y que, además, obligarán a actualizar continuamente el inventario y el alcance de la migración.

Necesitamos una fotografía suficientemente estable del sistema que queremos transformar.

De lo contrario, estaremos intentando alcanzar un objetivo que se mueve mientras avanzamos hacia él.

Antes de escribir código

Una migración de este tamaño no se resuelve de una sentada. Es un proyecto del que depende toda la empresa y que puede prolongarse durante meses.

Precisamente por eso conviene resistir la tentación de empezar por la parte aparentemente productiva: modificar módulos y conseguir que se instalen.

Antes de escribir una sola línea querría tener:

  • Un inventario de módulos y funcionalidades.
  • Un mapa inicial de dependencias.
  • Una explicación comprensible de qué resuelve cada pieza.
  • Evidencia de quién utiliza cada funcionalidad y con qué frecuencia.
  • Una decisión preliminar: conservar, volver al estándar, rediseñar o retirar.
  • Una visión clara de por qué estamos migrando y qué queremos mejorar.
  • Un punto de partida lo bastante estable como para poder planificar.

Después vendrán el código, los datos, las pruebas y el cambio de sistema.

Pero antes de todo eso hay que decidir qué sistema queremos llevarnos al futuro.

Porque migrar bien no consiste en trasladar todo lo que hemos acumulado. Consiste en conservar lo que sigue teniendo sentido y aprovechar el cambio para dejar atrás lo que ya no lo tiene.