20 KiB
Análisis del Módulo Base de Odoo
Módulo: base Versión Odoo: 18.0 Community Prioridad: P0 (Crítico) Mapeo MGN: MGN-001 (Fundamentos), MGN-002 (Empresas), MGN-003 (Catálogos)
Descripción del Módulo
El módulo base de Odoo es el módulo fundacional que todos los demás módulos requieren. Proporciona:
- Sistema de usuarios y autenticación
- Gestión de empresas (multi-company)
- Catálogos maestros (países, monedas, UdM)
- Sistema de partners (clientes, proveedores, empleados, etc.)
- Gestión de permisos (grupos, reglas de acceso)
- Infraestructura base del ORM
Es el equivalente al "core" de un ERP.
Modelos de Datos Principales
1. res.users (Usuarios)
Tabla: res_users
Campos clave:
login = fields.Char(required=True) # Email/username
password = fields.Char() # Hash bcrypt
partner_id = fields.Many2one('res.partner') # Vinculado a partner
company_id = fields.Many2one('res.company') # Empresa principal
company_ids = fields.Many2many('res.company') # Empresas permitidas
groups_id = fields.Many2many('res.groups') # Grupos de permisos
active = fields.Boolean(default=True) # Usuario activo/inactivo
share = fields.Boolean() # Usuario portal (limitado)
Relaciones:
1:1conres.partner(cada usuario es un partner)N:Mconres.groups(un usuario tiene múltiples grupos)N:1conres.company(empresa principal)N:Mconres.company(empresas permitidas)
Funciones destacables:
authenticate()- Autenticación de usuariocheck_credentials()- Validación de passwordchange_password()- Cambio de contraseñahas_group()- Verificar si usuario pertenece a grupo
Aplicabilidad MGN-001: ⭐⭐⭐⭐⭐
- Replicar estructura en
auth.users - Añadir
tenant_idpara multi-tenancy - JWT en lugar de sesiones Odoo
2. res.groups (Grupos de Permisos)
Tabla: res_groups
Campos clave:
name = fields.Char(required=True, translate=True)
category_id = fields.Many2one('ir.module.category') # Categoría del grupo
implied_ids = fields.Many2many('res.groups') # Grupos heredados
users = fields.Many2many('res.users') # Usuarios del grupo
model_access = fields.One2many('ir.model.access') # Permisos de modelos
rule_groups = fields.Many2many('ir.rule') # Record rules
Patrón de herencia de grupos:
Grupo "User: All Documents"
↓ implica
Grupo "User: Own Documents Only"
Aplicabilidad MGN-001: ⭐⭐⭐⭐⭐
- Sistema RBAC basado en grupos
- Grupos jerárquicos con herencia
- Implementar en
auth.roles+auth.role_hierarchy
3. ir.model.access (Permisos de Modelo)
Tabla: ir_model_access
Campos clave:
name = fields.Char(required=True)
model_id = fields.Many2one('ir.model', required=True) # Modelo protegido
group_id = fields.Many2one('res.groups') # Grupo (null = todos)
perm_read = fields.Boolean(default=False) # Permiso READ
perm_write = fields.Boolean(default=False) # Permiso WRITE
perm_create = fields.Boolean(default=False) # Permiso CREATE
perm_unlink = fields.Boolean(default=False) # Permiso DELETE
Ejemplo:
# ir.model.access.csv
"access_sale_order_salesman","sale.order salesman","model_sale_order","sales_team.group_sale_salesman",1,1,1,0
# Grupo 'salesman' puede READ, WRITE, CREATE pero NO DELETE sale.order
Aplicabilidad MGN-001: ⭐⭐⭐⭐⭐
- Implementar en
auth.model_permissions - Permisos CRUD por modelo y rol
- Tabla:
role_id,resource,read,write,create,delete
4. ir.rule (Record Rules - RLS)
Tabla: ir_rule
Campos clave:
name = fields.Char(required=True)
model_id = fields.Many2one('ir.model', required=True)
groups = fields.Many2many('res.groups') # Grupos aplicables
domain_force = fields.Text(required=True) # Dominio (filtro)
perm_read = fields.Boolean(default=True)
perm_write = fields.Boolean(default=True)
perm_create = fields.Boolean(default=True)
perm_unlink = fields.Boolean(default=True)
Ejemplo:
# Record rule: "Sale Order: See own orders"
domain_force = [('user_id', '=', user.id)]
# Un vendedor solo ve sus propias órdenes de venta
Aplicabilidad MGN-001: ⭐⭐⭐⭐⭐
- Implementar con PostgreSQL RLS (Row Level Security)
- Políticas por tenant, por usuario, por rol
- Ejemplo:
CREATE POLICY tenant_isolation ON sales.orders USING (tenant_id = current_setting('app.tenant_id')::int);
5. res.company (Empresas)
Tabla: res_company
Campos clave:
name = fields.Char(required=True)
partner_id = fields.Many2one('res.partner') # Partner vinculado
currency_id = fields.Many2one('res.currency') # Moneda principal
logo = fields.Binary()
email = fields.Char()
phone = fields.Char()
parent_id = fields.Many2one('res.company') # Empresa padre (holdings)
child_ids = fields.One2many('res.company') # Subsidiarias
Aplicabilidad MGN-002: ⭐⭐⭐⭐⭐
- Implementar en
core.companies - Soporte para holdings (parent_id)
- Vinculado a
tenant_iden multi-tenancy
6. res.partner (Partners - Contactos Universales)
Tabla: res_partner
Campos clave:
name = fields.Char(required=True)
email = fields.Char()
phone = fields.Char()
mobile = fields.Char()
street = fields.Char()
city = fields.Char()
state_id = fields.Many2one('res.country.state')
country_id = fields.Many2one('res.country')
zip = fields.Char()
vat = fields.Char() # Tax ID / RFC
is_company = fields.Boolean() # ¿Es empresa o persona?
customer_rank = fields.Integer() # ¿Es cliente?
supplier_rank = fields.Integer() # ¿Es proveedor?
employee = fields.Boolean() # ¿Es empleado?
user_id = fields.Many2one('res.users') # Usuario vinculado
parent_id = fields.Many2one('res.partner') # Empresa padre
child_ids = fields.One2many('res.partner') # Contactos hijos
Patrón Universal de Partner:
Odoo usa res.partner para:
- Clientes
- Proveedores
- Empleados
- Empresas
- Contactos de empresas
- Direcciones de entrega
Aplicabilidad MGN-003: ⭐⭐⭐⭐
- Considerar tabla universal
core.partnerso separar encustomers,vendors,contacts - Ventaja Odoo: un solo modelo reduce duplicación
- Desventaja: tabla grande, queries complejas
- Recomendación: Adoptar patrón Odoo pero con vistas SQL por tipo
7. res.currency (Monedas)
Tabla: res_currency
Campos clave:
name = fields.Char(required=True) # Código ISO (USD, MXN, EUR)
symbol = fields.Char(required=True) # $, €, £
rate = fields.Float(digits=(12, 6)) # Tasa de cambio
rounding = fields.Float(digits=(12, 6)) # Redondeo (0.01)
active = fields.Boolean(default=True)
Aplicabilidad MGN-003: ⭐⭐⭐⭐⭐
- Catálogo maestro esencial
- Implementar en
core.currencies - Tabla de tasas de cambio históricas:
core.currency_rates
8. res.country (Países)
Tabla: res_country
Campos clave:
name = fields.Char(required=True, translate=True)
code = fields.Char(required=True, size=2) # ISO 3166-1 alpha-2
phone_code = fields.Integer() # Código telefónico
currency_id = fields.Many2one('res.currency')
state_ids = fields.One2many('res.country.state') # Estados/provincias
Aplicabilidad MGN-003: ⭐⭐⭐⭐⭐
- Catálogo maestro
- Incluir en
core.countries - Seed data con países ISO
9. uom.uom (Unidades de Medida)
Tabla: uom_uom
Campos clave:
name = fields.Char(required=True)
category_id = fields.Many2one('uom.category') # Categoría (Length, Weight, Time)
factor = fields.Float(required=True) # Factor de conversión
uom_type = fields.Selection([
('bigger', 'Bigger than reference'),
('reference', 'Reference Unit'),
('smaller', 'Smaller than reference')
])
rounding = fields.Float(default=0.01)
Ejemplo de conversión:
Categoría: Weight
- kg (reference, factor=1.0)
- ton (bigger, factor=1000.0) # 1 ton = 1000 kg
- g (smaller, factor=0.001) # 1 g = 0.001 kg
Aplicabilidad MGN-003: ⭐⭐⭐⭐⭐
- Esencial para inventario y compras
- Implementar en
core.units_of_measure+core.uom_categories
Lógica de Negocio Destacable
1. Sistema de Autenticación
Archivo: odoo/addons/base/models/res_users.py
def _check_credentials(self, password, user_agent_env):
""" Check password and raise if invalid """
assert password
self.env.cr.execute(
"SELECT COALESCE(password, '') FROM res_users WHERE id=%s",
[self.env.uid]
)
[hashed] = self.env.cr.fetchone()
valid, replacement = self._crypt_context().verify_and_update(password, hashed)
if replacement is not None:
self._set_encrypted_password(self.env.uid, replacement)
if not valid:
raise AccessDenied()
Patrones observados:
- Hash de password con bcrypt
- Verificación segura con
verify_and_update - Actualización automática de hash si algoritmo cambió
Aplicabilidad MGN-001: ⭐⭐⭐⭐⭐
- Usar bcrypt en Node.js
- JWT para tokens en lugar de sesiones
- Refresh tokens
2. Multi-Company Access Control
Archivo: odoo/addons/base/models/res_users.py
@api.depends('groups_id')
def _compute_share(self):
for user in self:
user.share = not any(group.category_id.xml_id == 'base.module_category_user_type'
for group in user.groups_id)
def _get_company(self):
return self.env.company
company_id = fields.Many2one('res.company', default=_get_company, required=True)
company_ids = fields.Many2many('res.company', compute='_compute_company_ids', store=True)
Patrones observados:
- Usuario tiene una empresa principal (
company_id) - Usuario puede acceder a múltiples empresas (
company_ids) - Record rules filtran registros por
company_id
Aplicabilidad MGN-002: ⭐⭐⭐⭐
- En multi-tenancy schema-based: cada tenant = 1 schema
- Dentro de un tenant, soporte multi-company con
company_id - RLS policies filtran por tenant y company
3. Sistema de Permisos en Cascada
Lógica:
- Se evalúan
ir.model.access(permisos de modelo por grupo) - Si el usuario tiene permiso de modelo, se evalúan
ir.rule(record rules) - Las record rules se combinan con OR entre grupos del usuario
- Si no hay record rules, el usuario ve todos los registros (si tiene permiso de modelo)
Ejemplo:
# Usuario pertenece a grupos: 'Sales User', 'Project Manager'
# ir.model.access
# 'Sales User' → puede READ sale.order
# 'Project Manager' → puede READ sale.order
# ir.rule (Sales User)
domain = [('user_id', '=', user.id)] # Solo sus propias órdenes
# ir.rule (Project Manager)
domain = [('project_id.user_id', '=', user.id)] # Órdenes de sus proyectos
# Resultado: El usuario ve registros que cumplen:
# (user_id = X) OR (project_id.user_id = X)
Aplicabilidad MGN-001: ⭐⭐⭐⭐⭐
- Implementar con PostgreSQL RLS
- Múltiples policies se combinan con OR
- Granularidad: modelo → record → field
Patrones Arquitectónicos Observados
1. Partner como Entidad Universal
Patrón:
res.partner (Tabla universal para contactos)
↓
Usado por:
- res.users (usuario es un partner)
- sale.order (customer_id → res.partner)
- purchase.order (vendor_id → res.partner)
- hr.employee (employee_id → res.partner)
- res.company (company es un partner)
Ventajas:
- Un solo lugar para datos de contacto
- Evita duplicación de direcciones, emails, teléfonos
- Fácil vincular cualquier módulo a partners
Desventajas:
- Tabla muy grande
- Queries complejas (filtrar por
customer_rank,supplier_rank, etc.)
Recomendación MGN-003: Adoptar con vistas SQL
CREATE VIEW customers AS SELECT * FROM core.partners WHERE customer_rank > 0;
CREATE VIEW vendors AS SELECT * FROM core.partners WHERE supplier_rank > 0;
2. Herencia de Grupos
Patrón:
groups_id = fields.Many2many('res.groups')
implied_ids = fields.Many2many('res.groups') # Grupos heredados
# Grupo "Sales Manager" implica "Sales User"
# Si asignas "Sales Manager" a un usuario, automáticamente tiene "Sales User"
Aplicabilidad MGN-001: ⭐⭐⭐⭐
- Implementar tabla
auth.role_hierarchy - Consultas recursivas para obtener roles heredados
3. Soft Delete (archive)
Patrón:
active = fields.Boolean(default=True)
def toggle_active(self):
for record in self:
record.active = not record.active
- En lugar de DELETE, se hace
UPDATE active = false - Permite recuperar registros borrados
- Queries por defecto filtran
active = true
Aplicabilidad MGN (todos): ⭐⭐⭐⭐⭐
- Implementar campo
activeen todas las tablas - Soft delete en lugar de hard delete
APIs y Endpoints Estándar
Odoo usa XML-RPC y JSON-RPC para APIs. No es RESTful puro.
Endpoints típicos:
/web/session/authenticate # Login
/web/dataset/call_kw # Llamar métodos de modelos
/web/dataset/search_read # Búsqueda + lectura
Métodos estándar de modelo:
create(vals)- Crear registrowrite(vals)- Actualizar registrounlink()- Eliminar registroread(fields)- Leer campossearch(domain)- Buscar registrossearch_read(domain, fields)- Buscar + leer
Aplicabilidad MGN: ❌ No copiar
- ERP Genérico usará REST APIs (ADR-008)
- Endpoints estándar:
GET /api/v1/users,POST /api/v1/users, etc.
Integraciones con Otros Módulos
base es requerido por TODOS los módulos Odoo.
Dependencias inversas (quién usa base):
saleusares.partner(clientes)purchaseusares.partner(proveedores)accountusares.currency,res.companyhrusares.partner(empleados)stockusauom.uom(unidades de medida)projectusares.users(asignación de tareas)
Funcionalidades Relevantes para ERP Genérico
Funcionalidades P0 (Críticas)
-
Autenticación de usuarios
- Login con email/password
- Hash bcrypt
- Sesiones/tokens
-
Gestión de usuarios
- Crear, editar, desactivar usuarios
- Reseteo de contraseña
- Perfil de usuario vinculado a partner
-
Sistema RBAC (Roles & Permissions)
- Grupos de permisos
- Herencia de grupos
- Permisos CRUD por modelo
- Record rules (RLS)
-
Multi-company
- Gestión de múltiples empresas
- Usuarios con acceso a múltiples empresas
- Cambio de empresa en contexto
-
Catálogos maestros
- Países y estados
- Monedas y tasas de cambio
- Unidades de medida
- Partners (clientes, proveedores, contactos)
Funcionalidades P1 (Importantes)
-
Portal de usuarios externos
- Usuarios tipo "portal" (share=True)
- Acceso limitado
- Vistas específicas
-
Gestión de direcciones
- Direcciones de facturación/entrega
- Vinculadas a partners
-
Configuración de empresa
- Logo, datos fiscales
- Moneda principal
- Parámetros del sistema
Funcionalidades P2 (Opcionales)
-
Multi-idioma
- Traducciones de catálogos
- UI multiidioma
-
Gestión de bancos
- Cuentas bancarias de empresa
- Cuentas bancarias de partners
Mapeo a Módulos MGN
MGN-001: Fundamentos
De base:
res.users→auth.usersres.groups→auth.rolesir.model.access→auth.model_permissionsir.rule→ PostgreSQL RLS policies
Funcionalidades:
- RF-AUTH-001: Autenticación JWT
- RF-AUTH-002: Gestión de usuarios
- RF-AUTH-003: Sistema RBAC
- RF-AUTH-004: Multi-tenancy
MGN-002: Empresas y Organizaciones
De base:
res.company→core.companies
Funcionalidades:
- RF-COMP-001: Gestión de empresas
- RF-COMP-002: Multi-company
- RF-COMP-003: Configuración de empresa
MGN-003: Catálogos Maestros
De base:
res.partner→core.partnersres.currency→core.currenciesres.country→core.countriesres.country.state→core.statesuom.uom→core.units_of_measureuom.category→core.uom_categories
Funcionalidades:
- RF-CAT-001: Gestión de partners (clientes, proveedores, contactos)
- RF-CAT-002: Catálogo de monedas y tasas
- RF-CAT-003: Catálogo de países y estados
- RF-CAT-004: Catálogo de unidades de medida
Recomendaciones de Implementación
1. Sistema de Autenticación
Odoo usa: Sesiones con cookies
ERP Genérico debe usar: JWT (ADR-001)
// Login endpoint
POST /api/v1/auth/login
Request: { email, password }
Response: {
accessToken: "jwt...",
refreshToken: "jwt...",
user: { id, email, name, roles }
}
2. Sistema RBAC
Implementación sugerida:
Tablas:
-- auth.roles (equivalente a res.groups)
CREATE TABLE auth.roles (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
category VARCHAR(50),
parent_role_id INT REFERENCES auth.roles(id),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- auth.user_roles (N:M)
CREATE TABLE auth.user_roles (
user_id INT REFERENCES auth.users(id),
role_id INT REFERENCES auth.roles(id),
PRIMARY KEY (user_id, role_id)
);
-- auth.model_permissions (equivalente a ir.model.access)
CREATE TABLE auth.model_permissions (
id SERIAL PRIMARY KEY,
role_id INT REFERENCES auth.roles(id),
resource VARCHAR(100) NOT NULL, -- 'users', 'products', 'orders', etc.
can_read BOOLEAN DEFAULT FALSE,
can_write BOOLEAN DEFAULT FALSE,
can_create BOOLEAN DEFAULT FALSE,
can_delete BOOLEAN DEFAULT FALSE,
UNIQUE (role_id, resource)
);
RLS (Row Level Security):
-- Política de aislamiento por tenant
CREATE POLICY tenant_isolation ON auth.users
USING (tenant_id = current_setting('app.tenant_id')::int);
-- Política: usuarios solo ven su propio registro
CREATE POLICY own_user_only ON auth.users
FOR SELECT
USING (id = current_setting('app.user_id')::int);
3. Catálogos Maestros
Implementación sugerida:
// Seed data de países (ISO 3166-1)
// Importar desde https://restcountries.com/
const countries = [
{ code: 'MX', name: 'México', phone_code: 52, currency_code: 'MXN' },
{ code: 'US', name: 'United States', phone_code: 1, currency_code: 'USD' },
// ...
];
// Seed data de monedas (ISO 4217)
const currencies = [
{ code: 'MXN', name: 'Peso Mexicano', symbol: '$', decimals: 2 },
{ code: 'USD', name: 'US Dollar', symbol: '$', decimals: 2 },
{ code: 'EUR', name: 'Euro', symbol: '€', decimals: 2 },
// ...
];
4. Partners (Patrón Universal)
Opción 1: Tabla única (patrón Odoo)
CREATE TABLE core.partners (
id SERIAL PRIMARY KEY,
tenant_id INT NOT NULL,
name VARCHAR(200) NOT NULL,
email VARCHAR(100),
phone VARCHAR(20),
is_customer BOOLEAN DEFAULT FALSE,
is_vendor BOOLEAN DEFAULT FALSE,
is_employee BOOLEAN DEFAULT FALSE,
is_company BOOLEAN DEFAULT FALSE,
parent_id INT REFERENCES core.partners(id),
-- ... más campos
);
CREATE VIEW customers AS
SELECT * FROM core.partners WHERE is_customer = TRUE;
CREATE VIEW vendors AS
SELECT * FROM core.partners WHERE is_vendor = TRUE;
Opción 2: Tablas separadas
CREATE TABLE core.partners (
id SERIAL PRIMARY KEY,
tenant_id INT NOT NULL,
name VARCHAR(200) NOT NULL,
-- ... campos comunes
);
CREATE TABLE sales.customers (
id INT PRIMARY KEY REFERENCES core.partners(id),
-- campos específicos de clientes
);
CREATE TABLE purchase.vendors (
id INT PRIMARY KEY REFERENCES core.partners(id),
-- campos específicos de proveedores
);
Recomendación: Opción 1 (patrón Odoo) por simplicidad
Conclusión
El módulo base de Odoo es una referencia excelente para:
- Sistema de autenticación robusto
- RBAC granular con herencia de roles
- Multi-company bien implementado
- Catálogos maestros completos
- Patrón de partner universal
Prioridad de implementación en MGN:
- MGN-001 (Fundamentos): Sistema auth, usuarios, RBAC
- MGN-003 (Catálogos): Monedas, países, UoM, partners
- MGN-002 (Empresas): Multi-company, configuración
Nivel de reutilización: 90% de los patrones son aplicables
Fecha: 2025-11-23 Analista: Architecture-Analyst Estado: ✅ Análisis completo