# 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:** ```python 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:1` con `res.partner` (cada usuario es un partner) - `N:M` con `res.groups` (un usuario tiene múltiples grupos) - `N:1` con `res.company` (empresa principal) - `N:M` con `res.company` (empresas permitidas) **Funciones destacables:** - `authenticate()` - Autenticación de usuario - `check_credentials()` - Validación de password - `change_password()` - Cambio de contraseña - `has_group()` - Verificar si usuario pertenece a grupo **Aplicabilidad MGN-001:** ⭐⭐⭐⭐⭐ - Replicar estructura en `auth.users` - Añadir `tenant_id` para multi-tenancy - JWT en lugar de sesiones Odoo --- ### 2. res.groups (Grupos de Permisos) **Tabla:** `res_groups` **Campos clave:** ```python 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:** ```python 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:** ```csv # 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:** ```python 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:** ```python # 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:** ```python 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_id` en multi-tenancy --- ### 6. res.partner (Partners - Contactos Universales) **Tabla:** `res_partner` **Campos clave:** ```python 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.partners` o separar en `customers`, `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:** ```python 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:** ```python 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:** ```python 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` ```python 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` ```python @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:** 1. Se evalúan `ir.model.access` (permisos de modelo por grupo) 2. Si el usuario tiene permiso de modelo, se evalúan `ir.rule` (record rules) 3. Las record rules se combinan con OR entre grupos del usuario 4. Si no hay record rules, el usuario ve todos los registros (si tiene permiso de modelo) **Ejemplo:** ```python # 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 ```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:** ```python 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:** ```python 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 `active` en 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 registro - `write(vals)` - Actualizar registro - `unlink()` - Eliminar registro - `read(fields)` - Leer campos - `search(domain)` - Buscar registros - `search_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):** - `sale` usa `res.partner` (clientes) - `purchase` usa `res.partner` (proveedores) - `account` usa `res.currency`, `res.company` - `hr` usa `res.partner` (empleados) - `stock` usa `uom.uom` (unidades de medida) - `project` usa `res.users` (asignación de tareas) --- ## Funcionalidades Relevantes para ERP Genérico ### Funcionalidades P0 (Críticas) 1. **Autenticación de usuarios** - Login con email/password - Hash bcrypt - Sesiones/tokens 2. **Gestión de usuarios** - Crear, editar, desactivar usuarios - Reseteo de contraseña - Perfil de usuario vinculado a partner 3. **Sistema RBAC (Roles & Permissions)** - Grupos de permisos - Herencia de grupos - Permisos CRUD por modelo - Record rules (RLS) 4. **Multi-company** - Gestión de múltiples empresas - Usuarios con acceso a múltiples empresas - Cambio de empresa en contexto 5. **Catálogos maestros** - Países y estados - Monedas y tasas de cambio - Unidades de medida - Partners (clientes, proveedores, contactos) ### Funcionalidades P1 (Importantes) 6. **Portal de usuarios externos** - Usuarios tipo "portal" (share=True) - Acceso limitado - Vistas específicas 7. **Gestión de direcciones** - Direcciones de facturación/entrega - Vinculadas a partners 8. **Configuración de empresa** - Logo, datos fiscales - Moneda principal - Parámetros del sistema ### Funcionalidades P2 (Opcionales) 9. **Multi-idioma** - Traducciones de catálogos - UI multiidioma 10. **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.users` - `res.groups` → `auth.roles` - `ir.model.access` → `auth.model_permissions` - `ir.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.partners` - `res.currency` → `core.currencies` - `res.country` → `core.countries` - `res.country.state` → `core.states` - `uom.uom` → `core.units_of_measure` - `uom.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) ```typescript // 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:** ```sql -- 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):** ```sql -- 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:** ```typescript // 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) ```sql 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 ```sql 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:** 1. MGN-001 (Fundamentos): Sistema auth, usuarios, RBAC 2. MGN-003 (Catálogos): Monedas, países, UoM, partners 3. 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