Ir al contenido

SOLID en módulos Odoo: principios de diseño para desarrolladores

Single Responsibility, herencia, mixins y arquitectura mantenible en Odoo 19
16 de junio de 2026 por
SOLID en módulos Odoo: principios de diseño para desarrolladores
Aitor Atencia

Odoo · Arquitectura · SOLID

Los principios SOLID no son teoría de laboratorio: son la brújula para construir módulos Odoo mantenibles, extensibles y libres de deuda técnica.

Logo Odoo Diagrama SOLID
SOLID aplicado al ecosistema de herencia y extensión de Odoo.

Contexto

¿Por qué SOLID en Odoo?

Odoo ya trae un framework de extensión potente: _inherit, mixins (mail.thread, mail.activity.mixin), wizards transitorios y un ORM declarativo. Pero sin criterio arquitectónico, es fácil acabar con modelos de 800 líneas, lógica duplicada en tres sitios y módulos imposibles de actualizar entre versiones.

«Extender sin romper» es exactamente lo que SOLID propone — y Odoo lo hace posible si lo aplicas con intención.

S

S Single Responsibility — Una razón de cambio

Cada modelo, servicio o wizard debe tener una sola responsabilidad. Si un método calcula precios, envía emails y actualiza inventario, tienes tres razones para modificarlo.

❌ Mal

class SaleOrder(models.Model):
    _inherit = 'sale.order'

    def action_confirm(self):
        res = super().action_confirm()
        self._send_whatsapp()
        self._update_crm_score()
        self._create_project_tasks()
        return res

✅ Bien

# sale_whatsapp/models/sale_order.py
class SaleOrder(models.Model):
    _inherit = 'sale.order'
    def action_confirm(self):
        res = super().action_confirm()
        self._send_whatsapp()
        return res

# project_sale/models/sale_order.py
class SaleOrder(models.Model):
    _inherit = 'sale.order'
    def action_confirm(self):
        res = super().action_confirm()
        self._create_project_tasks()
        return res

Un módulo por integración. Cada uno sobrescribe action_confirm llamando a super(). Odoo encadena las herencias automáticamente.

O

O Open/Closed — Abierto a extensión, cerrado a modificación

Herencia UML
En Odoo, _inherit es la herramienta natural del principio Open/Closed.

Nunca modifiques código del core de Odoo. Extiende con _inherit o crea módulos puente:

class ResPartner(models.Model):
    _inherit = 'res.partner'

    birth_date = fields.Date(string='Birth Date')
    age = fields.Integer(compute='_compute_age', store=True)

    @api.depends('birth_date')
    def _compute_age(self):
        today = fields.Date.today()
        for partner in self:
            if partner.birth_date:
                partner.age = today.year - partner.birth_date.year
            else:
                partner.age = 0

Si mañana necesitas otro campo, añades otro módulo que herede res.partner. El módulo original no se toca.

L

L Liskov Substitution — Las subclases no rompen el contrato

Si heredas un modelo y sobrescribes un método, el comportamiento debe ser compatible con lo que el padre promete. No cambies la semántica de retorno ni relajes validaciones que el core espera.

Regla práctica en Odoo

  • super().action_confirm() siempre al inicio o al final — nunca lo omitas
  • No lances excepciones en estados donde el padre no las lanza
  • Si añades validaciones (@api.constrains), documenta por qué
  • Los métodos create/write deben aceptar los mismos vals que el padre

I

I Interface Segregation — No obligues a implementar lo que no usas

En Odoo esto se traduce en mixins selectivos y modelos abstractos pequeños:

# ❌ Heredar mail.thread en un modelo que no necesita chatter
class ProductLabel(models.Model):
    _name = 'product.label'
    _inherit = ['mail.thread', 'mail.activity.mixin']
    name = fields.Char()

# ✅ Solo los mixins que aportan valor
class DiagnosticTest(models.Model):
    _name = 'diagnostic.test'
    _inherit = ['mail.thread']  # Sí necesita historial
    _description = 'Diagnostic Test'

Tampoco crees métodos «comodín» de 15 parámetros opcionales. Prefiere métodos pequeños con responsabilidad clara o wizards para flujos complejos.

D

D Dependency Inversion — Depende de abstracciones, no de implementaciones

En Odoo el equivalente es usar self.env['modelo'] en lugar de importar clases directamente, y delegar integraciones externas a servicios inyectables:

class PaymentGateway(models.AbstractModel):
    _name = 'payment.gateway'
    _description = 'Payment Gateway Interface'

    def charge(self, amount, token):
        raise NotImplementedError

class PaymentGatewayStripe(models.Model):
    _name = 'payment.gateway.stripe'
    _inherit = 'payment.gateway'

    def charge(self, amount, token):
        # Lógica Stripe
        ...

El modelo de negocio llama a self.env['payment.gateway'] sin saber si detrás hay Stripe, Redsys o un mock en tests.

Patrones

SOLID en la práctica diaria

SituaciónPatrón SOLID + Odoo
Lógica de negocio complejaExtraer a métodos privados o modelos abstract
Integración externa (API, SMS)Modelo dedicado + inyección vía env
Flujo multi-pasoWizard (TransientModel) con SRP
Extender funcionalidad estándar_inherit en módulo separado (OCP)
Evitar lógica en controladoresControlador delgado → método del modelo
Evitar lógica en QWebDatos preparados en Python, plantilla tonta
Odoo backend
Un backend limpio se nota: menos bugs, migraciones más fáciles.

Antipatrones

Señales de alarma en tu código

  • Modelo con más de 300–400 líneas → probable violación de SRP
  • sudo() esparcido sin justificación → seguridad y DIP rotos
  • Copiar-pegar bloques entre módulos → falta de abstracción
  • XML con if/else complejos → lógica que debería estar en Python
  • Módulo «misc» o «utils» que crece sin control → cajón de sastre

Resumen

SOLID no contradice el estilo Odoo: lo refuerza. Un módulo por responsabilidad, herencia con super(), mixins solo cuando aportan, y dependencias resueltas vía env. El resultado son addons que sobreviven a actualizaciones de versión y equipos que no tienen miedo de tocar el código.

en Odoo
Cómo desarrollar un módulo Odoo 19: convenciones de la comunidad
Estructura, manifest, seguridad, ORM y buenas prácticas OCA