Odoo es una suite ERP open source (Python + PostgreSQL) extensible por módulos custom. Este estudio integra toda la documentación del OKF: qué es, cómo generar módulos (scaffold, manifest, modelo, vista, seguridad) y cómo generar programas que usan Odoo (API externa XML-RPC/JSON-RPC y Odoo MCP), con sus scripts minimalistas.
odoo-minimal (odoo-bin · scaffold · ORM · XML-RPC/JSON-RPC · MCP)
La base: ERP open source + arquitectura + entorno ERNE (odoo_demo, COP, PUC).
# Cliente web (Owl JS) → XML-RPC/JSON-RPC/HTTP → Servidor Odoo 18 (Python) → PostgreSQL
odoo-bin -d odoo_demo --addons-path=addons,custom_addons
# odoo/ framework (ORM, vistas, RPC) · addons/ ~640 módulos de negocio · custom_addons/ módulos propios
# Clonar Odoo 18 + venv + dependencias (una sola vez)
py -3.12 -m venv C:\Users\TUF\odoo\odoo18\.venv
C:\Users\TUF\odoo\odoo18\.venv\Scripts\pip install -r C:\Users\TUF\odoo\odoo18\requirements.txt
# Servidor con la base demo
cd C:\Users\TUF\odoo\odoo18
.venv\Scripts\odoo-bin -d odoo_demo --addons-path=addons,custom_addons
odoo_demo, empresa ERNE (res.company id=1), Colombia 🇨🇴 · COP · plan contable PUC. ⚠️ Informes financieros (Balance/P&L) son exclusivos de Enterprise; Community no los tiene.Crear funcionalidad propia sin tocar el core: scaffold → manifest → modelo → vista → seguridad → instalar.
cd custom_addons
../../odoo-bin scaffold mi_modulo
# Genera: __init__.py · __manifest__.py · models/ · views/ · security/
import... # no importa nada: es un dict
{
'name': 'Mi Módulo',
'version': '18.0.1.0.0',
'depends': ['base'],
'data': ['security/ir.model.access.csv', 'views/mi_modulo_views.xml'],
'installable': True,
'application': True,
}
from odoo import fields, models
class MiModelo(models.Model):
_name = 'mi.modelo'
_description = 'Mi Modelo'
name = fields.Char(string='Nombre', required=True)
descripcion = fields.Text(string='Descripción')
activo = fields.Boolean(string='Activo', default=True)
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mi_modelo,mi.modelo,model_mi_modelo,base.group_user,1,1,1,1
<odoo>
<record id="view_mi_modelo_tree" model="ir.ui.view">
<field name="model">mi.modelo</field>
<field name="arch" type="xml">
<tree string="Mi Modelo"><field name="name"/><field name="activo"/></tree>
</field>
</record>
<record id="view_mi_modelo_form" model="ir.ui.view">
<field name="model">mi.modelo</field>
<field name="arch" type="xml">
<form string="Mi Modelo"><sheet><group>
<field name="name"/><field name="descripcion"/><field name="activo"/>
</group></sheet></form>
</field>
</record>
<record id="action_mi_modelo" model="ir.actions.act_window">
<field name="name">Mi Modelo</field>
<field name="res_model">mi.modelo</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_mi_modulo_root" name="Mi Módulo"/>
<menuitem id="menu_mi_modelo" parent="menu_mi_modulo_root" action="action_mi_modelo"/>
</odoo>
# -i instala · -u actualiza
../../odoo-bin -d odoo_demo -i mi_modulo --addons-path=addons,custom_addons
../../odoo-bin -d odoo_demo -u mi_modulo --addons-path=addons,custom_addons
iot_sensor (modelos iot.sensor + iot.sensor.reading, menú "IoT", esqueleto iot_handlers/). Los módulos custom viven en custom_addons sin tocar el core.Desde fuera de la UI: API externa XML-RPC/JSON-RPC y servidor MCP (agente IA).
import xmlrpc.client
url, db, username, password = 'http://localhost:8069', 'odoo_demo', 'admin', 'admin'
common = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/common')
uid = common.authenticate(db, username, password, {})
models = xmlrpc.client.ServerProxy(f'{url}/xmlrpc/2/object')
pid = models.execute_kw(db, uid, password, 'res.partner', 'create',
[{'name': 'Cliente Demo'}])
ids = models.execute_kw(db, uid, password, 'res.partner', 'search',
[[['is_company', '=', True]]])
recs = models.execute_kw(db, uid, password, 'res.partner', 'read',
[ids[:5]], {'fields': ['name', 'country_id']})
models.execute_kw(db, uid, password, 'res.partner', 'write',
[[pid], {'name': 'Nuevo Nombre'}])
{"jsonrpc": "2.0", "method": "call",
"params": {"service": "object", "method": "execute_kw",
"args": [db, uid, password, "res.partner", "create", [{}]]}}
execute_kw.# Agente IA → MCP (stdio) → odoo_mcp/server.py (FastMCP) → xmlrpc.client → Odoo → PostgreSQL
# Datos: list_models · get_model_fields · search_records · get_record
# create_record · update_record · delete_record · call_method
# Módulos: list_modules · get_module_info · install_module · upgrade_module · uninstall_module
# Config: get_config_param · set_config_param · list_config_params
mcp 1.x (FastMCP), canal xmlrpc.client (stdlib, sin módulo extra), conexión perezosa (arranca aunque Odoo esté apagado). Registrado en .vscode/mcp.json como odoo-local.El token 🟠 recorre: generar el módulo, definirlo, verlo, instalarlo y usarlo por API/MCP. Clic reinicia.
| Comando / concepto | Qué hace |
|---|---|
odoo-bin scaffold mi_modulo | Genera el esqueleto del módulo |
__manifest__.py | Metadatos + depends + data a cargar |
models.Model + _name | Base del ORM (modelo con puntos) |
ir.ui.view · ir.actions.act_window | Vistas tree/form + acción + menú |
ir.model.access.csv | Permisos ACL del modelo |
-i / -u | Instalar / actualizar módulo |
/xmlrpc/2/common | Endpoint sin auth (authenticate) |
/xmlrpc/2/object + execute_kw | CRUD del ORM por RPC |
search_read · search_count | Atajos buscar+leer / contar |
ir.model · ir.model.fields | Inspección y modelos dinámicos (x_) |
xmlrpc.client (stdlib, sin dependencia) para la API; el MCP usa el SDK mcp 1.x (fijado mcp>=1.9,<2.0). Docs: https://www.odoo.com/documentation/18.0/.