Ir al contenido
  • Inicio
  • Libro
  • Blog
  • Sobre nosotros
  • Ayuda
  • Contáctanos
  •  [email protected]
  • Inicia sesión
  • English (US) Español (BO)
  • Simplify It S.R.L.
    • Contáctanos
Simplify It S.R.L.
      • Inicio
      • Libro
      • Blog
      • Sobre nosotros
      • Ayuda
      • Contáctanos
    •  [email protected]
    • English (US) Español (BO)
    • Inicia sesión
    • Contáctanos

    Capítulo 17 Parte 1: El Puente Legacy-OWL - Comprensión e Implementación Básica

  • Todos los blogs
  • Libro OWL
  • Capítulo 17 Parte 1: El Puente Legacy-OWL - Comprensión e Implementación Básica
  • 25 de agosto de 2026 por
    Capítulo 17 Parte 1: El Puente Legacy-OWL - Comprensión e Implementación Básica
    Grover Menacho

    ¿Por qué este capítulo? Porque casi ningún proyecto real de Odoo parte de cero — vas a pasar mucho más tiempo trabajando dentro de una base de código que ya tiene años de widgets legado que construyendo algo nuevo en OWL puro. ¿Qué trata de resolver Odoo con esto? Odoo necesita una forma de que el código frontend viejo y nuevo convivan durante la migración de varios años del sistema de widgets legado a OWL, sin forzar una reescritura riesgosa y de una sola vez de todas las personalizaciones de cada cliente. Aplicación en la vida real: Esta es exactamente la situación que me llevó a escribir este libro: la actualización de un cliente rompió todos los widgets personalizados que habíamos construido para ellos, y las técnicas de puente de este capítulo son las que te permiten modernizar un módulo pieza por pieza en vez de congelar un proyecto hasta terminar una reescritura completa.

    ¡Felicidades por llegar al último capítulo central del libro! Has dominado JavaScript moderno, construido componentes OWL sofisticados, implementado gestión de estado global y aprendido prácticas profesionales de pruebas. Estás completamente equipado para construir aplicaciones de vanguardia en Odoo desde cero.

    Sin embargo, la realidad del desarrollo profesional de Odoo es más matizada. La mayor parte de tu trabajo no involucrará proyectos desde cero donde puedas usar OWL puro en todas partes. En su lugar, trabajarás con bases de datos de Odoo existentes llenas de años de personalizaciones, módulos de terceros e interfaces de usuario construidas con el framework JavaScript más antiguo de Odoo: el sistema de widgets legado.

    Esto crea un desafío crítico: ¿Cómo modernizar aplicaciones de forma incremental? ¿Cómo introducir componentes OWL poderosos en interfaces legado existentes sin romper todo? ¿Cómo aprovechar widgets legado existentes en nuevas aplicaciones OWL cuando reconstruirlos tomaría meses?

    La respuesta está en el puente legacy-OWL de Odoo: una capa de compatibilidad sofisticada que permite interoperabilidad perfecta entre código viejo y nuevo. Esto no es solo una curiosidad técnica; es una herramienta esencial para cualquier desarrollador profesional de Odoo trabajando en proyectos del mundo real.


    Entendiendo el Panorama Legado

    Antes de sumergirnos en las técnicas del puente, entendamos entre qué estamos haciendo el puente.

    El Sistema de Widgets Legado

    El framework JavaScript legado de Odoo, usado desde la versión 8 hasta partes de la versión 16, fue construido alrededor de:

    Arquitectura Basada en Widgets: Los componentes eran clases que extendían Widget

    const MyWidget = Widget.extend({
        template: 'my_module.MyTemplate',
    
        start: function() {
            // Lógica de inicialización
            return this._super.apply(this, arguments);
        },
    
        destroy: function() {
            // Lógica de limpieza
            this._super.apply(this, arguments);
        }
    });
    

    Uso Intensivo de jQuery: Manipulación directa del DOM y manejo de eventos

    events: {
        'click .my-button': '_onButtonClick',
    },
    
    _onButtonClick: function(event) {
        this.$('.result').text('¡Botón clickeado!');
    }
    

    Plantillas QWeb: Compilación de plantillas del lado del servidor

    <t t-name="my_module.MyTemplate">
        <div class="my-widget">
            <button class="my-button">Haz clic</button>
            <div class="result"></div>
        </div>
    </t>
    

    ¿Por Qué Hacer un Puente en Lugar de Reescribir?

    Realidad Práctica: Las instalaciones grandes de Odoo tienen cientos de widgets personalizados que representan años de lógica de negocio y refinamientos de interfaz de usuario.

    Gestión de Riesgos: Una reescritura completa introduce riesgo significativo de romper funcionalidad existente de la que los usuarios dependen diariamente.

    Limitaciones de Recursos: Reescribir todo de una vez requeriría recursos masivos de desarrollo y cronogramas de proyecto extendidos.

    Continuidad del Negocio: Los usuarios necesitan nuevas características mientras la funcionalidad existente continúa funcionando de manera confiable.

    El enfoque del puente permite modernización gradual: introducir componentes OWL incrementalmente mientras se mantiene la estabilidad del sistema.


    Visión General de la Arquitectura del Puente

    El puente legacy-OWL funciona en ambas direcciones:

    La dirección 1 monta OWL dentro del widget; la dirección 2 envuelve el widget para poder usarlo desde OWL.{width=100%}

    Dirección 1: OWL en Legado - Montar componentes OWL modernos dentro de widgets legado existentes - Agregar nuevas características sin reescribir pantallas completas - Modernizar gradualmente las interfaces de usuario

    Dirección 2: Legado en OWL - Usar widgets legado existentes dentro de nuevas aplicaciones OWL - Aprovechar funcionalidad legado compleja sin reconstruir - Mantener compatibilidad con widgets de terceros


    Escenario 1: Incrustando Componentes OWL en Widgets Legado

    Este es el patrón de modernización más común. Tienes un formulario o dashboard legado existente, y quieres agregar una nueva característica sofisticada construida con OWL.

    Ejemplo: Agregando un Gráfico Moderno a un Dashboard Legado

    Digamos que tienes un widget de dashboard de ventas legado, y quieres agregar un gráfico de ingresos interactivo construido con OWL.

    1. El Widget Dashboard Legado

    odoo.define('sales_dashboard.LegacyDashboard', function (require) {
        "use strict";
    
        const Widget = require('web.Widget');
        const core = require('web.core');
        const { mount } = require("@odoo/owl");
    
        // Importar nuestro componente OWL moderno
        const { RevenueChart } = require('sales_dashboard.RevenueChart');
    
        const LegacyDashboard = Widget.extend({
            template: 'sales_dashboard.LegacyDashboardTemplate',
    
            events: {
                'click .refresh-data': '_onRefreshData',
                'change .date-filter': '_onDateFilterChange',
            },
    
            init: function(parent, options) {
                this._super.apply(this, arguments);
                this.salesData = options.salesData || [];
                this.owlComponents = {}; // Rastrear componentes OWL montados
            },
    
            async start() {
                await this._super(...arguments);
    
                // Inicializar funcionalidad legado
                this._setupLegacyFeatures();
    
                // Montar componentes OWL
                await this._mountOWLComponents();
    
                // Cargar datos iniciales
                await this._loadDashboardData();
            },
    
            _setupLegacyFeatures: function() {
                // Funcionalidad basada en jQuery legado
                this.$('.legacy-counter').each(function(index, element) {
                    $(element).data('value', 0);
                });
    
                // Inicializar selector de fecha legado
                this.$('.date-filter').datepicker({
                    format: 'yyyy-mm-dd',
                    onSelect: this._onDateFilterChange.bind(this)
                });
            },
    
            async _mountOWLComponents() {
                try {
                    // Montar el gráfico de ingresos en su contenedor designado
                    const chartTarget = this.el.querySelector('.revenue-chart-container');
                    if (chartTarget) {
                        this.owlComponents.revenueChart = await mount(RevenueChart, chartTarget, {
                            props: {
                                initialData: this.salesData,
                                onDataPointClick: this._onChartDataPointClick.bind(this),
                                onDateRangeChange: this._onChartDateRangeChange.bind(this),
                                // Pasar referencia del widget legado para interacciones complejas
                                legacyWidget: this,
                            }
                        });
                    }
    
                    // Montar componentes OWL adicionales según sea necesario
                    const metricsTarget = this.el.querySelector('.metrics-widget-container');
                    if (metricsTarget) {
                        const { SalesMetrics } = require('sales_dashboard.SalesMetrics');
                        this.owlComponents.salesMetrics = await mount(SalesMetrics, metricsTarget, {
                            props: {
                                data: this.salesData,
                                onMetricClick: this._onMetricClick.bind(this)
                            }
                        });
                    }
    
                } catch (error) {
                    console.error('Falló al montar componentes OWL:', error);
                    // Degradación elegante - mostrar mensaje de error o UI de respaldo
                    this._showComponentError(error);
                }
            },
    
    

    Una vez montados los componentes OWL, el widget carga sus datos y los envía tanto a los contadores legados como a los props de los componentes OWL:

            async _loadDashboardData() {
                try {
                    const data = await this._rpc({
                        model: 'sale.order',
                        method: 'get_dashboard_data',
                        args: [this._getDateRange()],
                    });
    
                    // Actualizar widgets legado
                    this._updateLegacyCounters(data.counters);
    
                    // Actualizar componentes OWL
                    await this._updateOWLComponents(data);
    
                } catch (error) {
                    console.error('Falló al cargar datos del dashboard:', error);
                    this._showError('Falló al cargar datos del dashboard');
                }
            },
    
            async _updateOWLComponents(data) {
                // Actualizar props del componente OWL
                if (this.owlComponents.revenueChart) {
                    this.owlComponents.revenueChart.props.data = data.revenue;
                    // Disparar re-renderizado actualizando props
                    await this.owlComponents.revenueChart.render();
                }
    
                if (this.owlComponents.salesMetrics) {
                    this.owlComponents.salesMetrics.props.data = data.metrics;
                    await this.owlComponents.salesMetrics.render();
                }
            },
    
            _updateLegacyCounters: function(counters) {
                // Actualizaciones basadas en jQuery legado
                Object.keys(counters).forEach(key => {
                    const $counter = this.$(`.counter[data-metric="${key}"]`);
                    this._animateCounter($counter, counters[key]);
                });
            },
    
            _animateCounter: function($element, targetValue) {
                const currentValue = $element.data('value') || 0;
                $({ value: currentValue }).animate({ value: targetValue }, {
                    duration: 1000,
                    step: function() {
                        $element.text(Math.floor(this.value));
                    },
                    complete: function() {
                        $element.data('value', targetValue);
                    }
                });
            },
    
            // Manejadores de eventos para funcionalidad legado
            _onRefreshData: function(event) {
                event.preventDefault();
                this._loadDashboardData();
            },
    
            _onDateFilterChange: function(event) {
                const newDateRange = this._getDateRange();
                this._loadDashboardData();
            },
    
            // Manejadores de eventos para interacciones de componentes OWL
            _onChartDataPointClick: function(dataPoint) {
                // Manejar clics desde gráfico OWL - tal vez mostrar diálogo de desglose legado
                this._showDrillDownDialog(dataPoint);
            },
    
            _onChartDateRangeChange: function(dateRange) {
                // Actualizar filtros de fecha legado cuando el gráfico OWL cambia el rango de fecha
                this.$('.date-filter').datepicker('setDate', dateRange.start);
                this._loadDashboardData();
            },
    
            _onMetricClick: function(metric) {
                // Navegar a vista de lista legado
                this.do_action({
                    type: 'ir.actions.act_window',
                    res_model: 'sale.order',
                    views: [[false, 'list']],
                    domain: metric.domain,
                    context: metric.context,
                });
            },
    
            // Métodos utilitarios
            _getDateRange: function() {
                return {
                    start: this.$('.date-start').val(),
                    end: this.$('.date-end').val(),
                };
            },
    
            _showDrillDownDialog: function(dataPoint) {
                // Implementación de diálogo legado
                const dialog = new Dialog(this, {
                    title: `Detalles para ${dataPoint.label}`,
                    size: 'medium',
                    $content: $(`<div>Ingresos: ${dataPoint.value}</div>`),
                });
                dialog.open();
            },
    
            _showComponentError: function(error) {
                this.$('.owl-error-container').show().find('.error-message').text(
                    'Falló al cargar componentes interactivos. Por favor, recarga la página.'
                );
            },
    
            _showError: function(message) {
                // Visualización de error legado
                this.$('.error-container').show().find('.error-text').text(message);
            },
    

    La última pieza es la más importante de este capítulo: limpiar los componentes OWL montados. Como la clase legada Widget no tiene ningún hook de ciclo de vida automático para esto, hay que hacerlo a mano dentro de destroy, o cada componente OWL montado generará una fuga de memoria cada vez que el widget legado se cierre y se vuelva a abrir.

            // Crítico: Limpiar componentes OWL para prevenir fugas de memoria
            destroy: function() {
                // Desmontar todos los componentes OWL
                Object.values(this.owlComponents).forEach(component => {
                    if (component && component.destroy) {
                        component.destroy();
                    }
                });
                this.owlComponents = {};
    
                // Llamar al destroy padre
                this._super.apply(this, arguments);
            },
        });
    
        return LegacyDashboard;
    });
    

    2. La Plantilla Legado

    <t t-name="sales_dashboard.LegacyDashboardTemplate">
        <div class="legacy-sales-dashboard">
            <!-- Encabezado legado con controles -->
            <div class="dashboard-header">
                <h2>Dashboard de Ventas (Legado)</h2>
                <div class="dashboard-controls">
                    <input type="text" class="date-filter date-start" placeholder="Fecha Inicio"/>
                    <input type="text" class="date-filter date-end" placeholder="Fecha Fin"/>
                    <button class="btn btn-primary refresh-data">Actualizar</button>
                </div>
            </div>
    
            <!-- Contadores legado -->
            <div class="legacy-counters row">
                <div class="col-md-3">
                    <div class="counter-widget">
                        <h4>Ventas Totales</h4>
                        <div class="counter" data-metric="total_sales">0</div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="counter-widget">
                        <h4>Clientes Nuevos</h4>
                        <div class="counter" data-metric="new_customers">0</div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="counter-widget">
                        <h4>Negocios Activos</h4>
                        <div class="counter" data-metric="active_deals">0</div>
                    </div>
                </div>
                <div class="col-md-3">
                    <div class="counter-widget">
                        <h4>Tasa de Conversión</h4>
                        <div class="counter" data-metric="conversion_rate">0%</div>
                    </div>
                </div>
            </div>
    
            <!-- Componentes OWL modernos montados aquí -->
            <div class="modern-components">
                <div class="row">
                    <div class="col-md-8">
                        <div class="card">
                            <div class="card-header">
                                <h5>Tendencias de Ingresos (Componente OWL Moderno)</h5>
                            </div>
                            <div class="card-body">
                                <!-- El componente OWL será montado aquí -->
                                <div class="revenue-chart-container"></div>
                                <!-- Respaldo cuando el componente OWL falla -->
                                <div class="owl-error-container" style="display: none;">
                                    <div class="alert alert-warning">
                                        <span class="error-message"></span>
                                    </div>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="col-md-4">
                        <div class="card">
                            <div class="card-header">
                                <h5>Métricas Clave (Componente OWL Moderno)</h5>
                            </div>
                            <div class="card-body">
                                <!-- Otro componente OWL montado aquí -->
                                <div class="metrics-widget-container"></div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
    
            <!-- Tabla legado -->
            <div class="legacy-table-section">
                <h4>Órdenes Recientes (Tabla Legado)</h4>
                <table class="table table-striped">
                    <thead>
                        <tr>
                            <th>Orden #</th>
                            <th>Cliente</th>
                            <th>Monto</th>
                            <th>Fecha</th>
                        </tr>
                    </thead>
                    <tbody class="recent-orders-tbody">
                        <!-- Poblado por JavaScript legado -->
                    </tbody>
                </table>
            </div>
    
            <!-- Contenedor de errores -->
            <div class="error-container" style="display: none;">
                <div class="alert alert-danger">
                    <span class="error-text"></span>
                </div>
            </div>
        </div>
    </t>
    

    3. El Componente OWL Moderno RevenueChart

    /** @odoo-module **/
    
    import { Component, useState, onMounted, onWillUpdateProps } from "@odoo/owl";
    import { useService } from "@web/core/utils/hooks";
    
    export class RevenueChart extends Component {
        static template = xml`
            <div class="revenue-chart-owl">
                <div class="chart-controls">
                    <select t-model="state.chartType" t-on-change="onChartTypeChange">
                        <option value="line">Gráfico de Línea</option>
                        <option value="bar">Gráfico de Barras</option>
                        <option value="area">Gráfico de Área</option>
                    </select>
                    <button class="btn btn-sm btn-secondary" t-on-click="exportChart">
                        Exportar
                    </button>
                </div>
    
                <div class="chart-container" t-ref="chartContainer">
                    <canvas t-ref="chartCanvas"></canvas>
                </div>
    
                <div class="chart-summary" t-if="chartSummary">
                    <small class="text-muted">
                        Mostrando <t t-esc="chartSummary.totalDataPoints"/> puntos de datos.
                        Mayor: <t t-esc="chartSummary.highest"/>
                        Promedio: <t t-esc="chartSummary.average"/>
                    </small>
                </div>
            </div>
        `;
    
        static props = {
            initialData: { type: Array, optional: true },
            onDataPointClick: { type: Function, optional: true },
            onDateRangeChange: { type: Function, optional: true },
            legacyWidget: { type: Object, optional: true }, // Referencia al widget legado
        };
    
        setup() {
            this.notification = useService("notification");
    
            this.state = useState({
                chartType: 'line',
                data: this.props.initialData || [],
                loading: false,
            });
    
            this.chart = null;
            this.chartCanvasRef = useRef("chartCanvas");
    
            onMounted(() => {
                this.initializeChart();
            });
    
            onWillUpdateProps((nextProps) => {
                if (JSON.stringify(nextProps.initialData) !== JSON.stringify(this.props.initialData)) {
                    this.updateChartData(nextProps.initialData);
                }
            });
        }
    
        async initializeChart() {
            // Inicializar Chart.js o librería de gráficos similar
            const ctx = this.chartCanvas.getContext('2d');
    
            this.chart = new Chart(ctx, {
                type: this.state.chartType,
                data: this.getChartData(),
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    interaction: {
                        intersect: false,
                        mode: 'index',
                    },
                    onClick: (event, elements) => {
                        if (elements.length > 0 && this.props.onDataPointClick) {
                            const dataPoint = this.state.data[elements[0].index];
                            this.props.onDataPointClick(dataPoint);
                        }
                    },
                    plugins: {
                        legend: {
                            display: true,
                            position: 'top',
                        },
                        tooltip: {
                            callbacks: {
                                label: (context) => {
                                    return `Ingresos: $${context.parsed.y.toLocaleString()}`;
                                }
                            }
                        }
                    },
                    scales: {
                        x: {
                            type: 'time',
                            time: {
                                unit: 'day'
                            }
                        },
                        y: {
                            beginAtZero: true,
                            ticks: {
                                callback: function(value) {
                                    return '$' + value.toLocaleString();
                                }
                            }
                        }
                    }
                }
            });
        }
    
        getChartData() {
            return {
                labels: this.state.data.map(point => point.date),
                datasets: [{
                    label: 'Ingresos',
                    data: this.state.data.map(point => ({
                        x: point.date,
                        y: point.revenue
                    })),
                    borderColor: 'rgb(75, 192, 192)',
                    backgroundColor: 'rgba(75, 192, 192, 0.2)',
                    tension: 0.1
                }]
            };
        }
    
        updateChartData(newData) {
            if (this.chart && newData) {
                this.state.data = newData;
                this.chart.data = this.getChartData();
                this.chart.update();
            }
        }
    
        onChartTypeChange() {
            if (this.chart) {
                this.chart.config.type = this.state.chartType;
                this.chart.update();
            }
        }
    
        exportChart() {
            if (this.chart) {
                const url = this.chart.toBase64Image();
                const link = document.createElement('a');
                link.download = 'revenue-chart.png';
                link.href = url;
                link.click();
    
                this.notification.add("Gráfico exportado exitosamente", { type: "success" });
            }
        }
    
        get chartSummary() {
            if (!this.state.data.length) return null;
    
            const revenues = this.state.data.map(d => d.revenue);
            return {
                totalDataPoints: this.state.data.length,
                highest: Math.max(...revenues).toLocaleString(),
                average: (revenues.reduce((a, b) => a + b, 0) / revenues.length).toFixed(0),
            };
        }
    
        get chartCanvas() {
            return this.chartCanvasRef.el;
        }
    }
    

    Este ejemplo demuestra varios principios clave:

    1. Separación Clara: El código legado y OWL permanece separado pero se comunica a través de interfaces bien definidas
    2. Degradación Elegante: Si los componentes OWL fallan al cargar, la funcionalidad legado continúa funcionando
    3. Comunicación Bidireccional: Los componentes OWL pueden notificar a widgets legado de eventos, y viceversa
    4. Gestión de Memoria: Limpieza apropiada previene fugas de memoria
    5. Manejo de Errores: Manejo robusto de errores asegura estabilidad del sistema

    Beneficios Clave de Este Enfoque

    1. Modernización Incremental: Reemplazamos el gráfico simple con un componente OWL sofisticado mientras mantenemos características legado complejas

    2. Gestión de Riesgos: La funcionalidad de exportación y diálogos legado continúa funcionando

    3. Experiencia de Usuario Mejorada: Gráficos y tablas modernos e interactivos

    4. Mantenibilidad: Las nuevas características pueden construirse en OWL mientras las características legado permanecen estables

    5. Rendimiento: Solo cargar componentes modernos cuando sea necesario


    Mejores Prácticas para el Escenario 1

    1. Aislamiento de Componentes

    Mantén los componentes OWL autocontenidos:

    // Bueno: El componente OWL es independiente
    const owlComponent = await mount(ChartComponent, target, {
        props: {
            data: this.salesData,
            onEvent: this.handleEvent.bind(this)
        }
    });
    
    // Malo: El componente OWL depende de las partes internas del widget legado
    const owlComponent = await mount(ChartComponent, target, {
        props: {
            legacyWidget: this, // Demasiado acoplamiento
            domElement: this.$('.some-element') // Dependencia directa del DOM
        }
    });
    

    2. Límites de Error

    Siempre implementa respaldos elegantes:

    async _mountOWLComponents() {
        const targets = [
            { selector: '.chart-container', component: ChartComponent },
            { selector: '.metrics-container', component: MetricsComponent }
        ];
    
        for (const { selector, component } of targets) {
            try {
                const target = this.el.querySelector(selector);
                if (target) {
                    this.owlComponents[selector] = await mount(component, target, {
                        props: this.getComponentProps(component)
                    });
                }
            } catch (error) {
                console.error(`Falló al montar ${component.name}:`, error);
                this._showFallbackUI(selector, error);
            }
        }
    }
    
    _showFallbackUI(selector, error) {
        const target = this.el.querySelector(selector);
        if (target) {
            target.innerHTML = `
                <div class="alert alert-warning">
                    <h6>Componente No Disponible</h6>
                    <p>Esta característica está temporalmente no disponible. Por favor, recarga la página.</p>
                    <button class="btn btn-sm btn-secondary" onclick="location.reload()">
                        Recargar Página
                    </button>
                </div>
            `;
        }
    }
    

    3. Gestión de Memoria

    Crítico para prevenir fugas:

    destroy: function() {
        // Limpiar componentes OWL primero
        this._destroyOWLComponents();
    
        // Luego llamar al destroy padre
        this._super.apply(this, arguments);
    },
    
    _destroyOWLComponents: function() {
        Object.entries(this.owlComponents).forEach(([key, component]) => {
            try {
                if (component && typeof component.destroy === 'function') {
                    component.destroy();
                }
            } catch (error) {
                console.error(`Error destruyendo componente ${key}:`, error);
            }
        });
        this.owlComponents = {};
    }
    

    TL;DR: Envuelve widgets legado desde dentro de un componente OWL con useRef + onMounted/onWillUnmount, o monta componentes OWL dentro del start()/destroy() de un widget legado — cualquiera de las dos direcciones funciona mientras mantengas explícita la interfaz entre ambos.

    Pruébalo tú mismo: El ejemplo de este capítulo está disponible como addon instalable de Odoo 19: simplifyit_owl_book_ch17_1_ex1. Las instrucciones de instalación están en el README del repositorio.

    Errores Comunes

    Olvidar destruir los componentes OWL montados. Si montas un componente OWL dentro de _mountOWLComponents pero no lo desmontas en destroy, el componente (y todo lo que retiene: listeners de eventos, timers, suscripciones a servicios) genera una fuga cada vez que el widget legado se cierra y se vuelve a abrir.

    Interactuar con el DOM del widget legado antes de que esté listo. El this.el del widget legado solo existe una vez que start() se ha ejecutado. Montar un componente OWL o hacer this.el.querySelector(...) desde init() fallará silenciosamente o lanzará un error — hazlo siempre desde dentro (o después) de start().

    Mezclar los dos sistemas de eventos sin un límite claro. Es tentador hacer que los componentes OWL llamen directamente a this.trigger_up(...) del widget legado, o que el widget legado acceda a los internos de OWL. Mantén la interfaz entre ambos explícita — callback props en una dirección, métodos públicos del widget legado en la otra — para no terminar con dos objetos que ambos creen ser dueños del mismo estado.

    Asumir que los props del componente OWL son "reactivos" automáticamente. Los widgets legados mutan objetos planos; OWL solo vuelve a renderizar cuando detecta un cambio a través de su sistema de reactividad. Modificar directamente un valor en this.owlComponents.revenueChart.props.data (como hace el ejemplo anterior) NO dispara automáticamente un re-render — por eso el ejemplo llama explícitamente a .render() después.

    Ejercicios

    1. Toma el widget LegacyDashboard y añade un tercer componente OWL (por ejemplo, un TopCustomersList simple) montado en un nuevo contenedor. Asegúrate de que se destruya correctamente junto con los otros dos.
    2. Escribe a mano el método _destroyOWLComponents (sin mirar el ejemplo de "Gestión de Memoria") para un widget que rastrea dos componentes OWL montados en this.owlComponents.
    3. Modifica _onChartDataPointClick para que, en vez de abrir un diálogo legado, llame a un callback prop pasado desde un padre OWL — explica en una frase por qué esto no es posible en esta dirección (los widgets legados no son componentes OWL y no reciben props).

    En la Parte 2, exploraremos el Escenario 2 (usar widgets legado en componentes OWL), patrones de puente avanzados, estrategias de pruebas y planificación de migración. Esta comprensión fundamental del Escenario 1 te preparará para los desafíos de integración más complejos que vienen.

    ¿Qué Sigue?

    La Parte 2 invierte la dirección que acabas de aprender — en vez de montar OWL dentro de un widget legado, vas a envolver un widget legado dentro de un componente OWL, además de cubrir patrones de puente avanzados y un cronograma práctico de migración.

    en Libro OWL
    Capítulo 17 Parte 2: Patrones Avanzados de Puente y Estrategias de Migración
    Reading the whole book? Take it with you — free, no sign-up.
    PDF EPUB All chapters
    Enlaces útiles
    • Inicio
    • Sobre nosotros
    • Blog
    • El libro de OWL 2.0
    • Ayuda
    • Contáctanos
    Sobre nosotros

    Simplify It S.R.L. es una firma de implementación y desarrollo Odoo con base en La Paz, Bolivia, que trabaja con empresas de Latinoamérica y Norteamérica. Desarrollamos sobre Odoo desde la versión 6.1: módulos a medida, migraciones de versión y localización boliviana.

    También somos autores del libro de OWL 2.0, que publicamos gratis capítulo a capítulo en nuestro blog.

    Conecta con nosotros
    • Contáctanos
    • [email protected]
    • +591 65144144
    • La Paz, Bolivia
    Síguenos
    Copyright © Simplify It S.R.L.
    English (US) | Español (BO)
    Con la tecnología de Odoo - Cree un sitio web gratuito