Saltar al contenido principal
Automatización · n8n y Odoo

n8n y Odoo: qué automatizar con un flujo y qué exige un módulo

n8n vale para lo que ocurre alrededor de Odoo; lo que cambia el estado del negocio dentro pide un módulo. La frontera, con el JSON-RPC por delante y respuestas reales de un laboratorio con Odoo 19 y n8n 2.30.6.

Panel de Odoo: donde acaban los registros que crea un flujo de n8n y donde se audita si salieron bien

n8n sirve para lo que ocurre alrededor de Odoo: recoger un formulario externo, avisar a un chat, montar un informe diario. Lo que cambia el estado de negocio dentro —precios, reservas de stock, validaciones de albarán, conciliación de pagos— pide un módulo. La frontera se ve al ejecutar las dos cosas contra un Odoo de verdad: este artículo sale de un laboratorio propio con Odoo 19.0-20260630 y n8n 2.30.6.

Idea clave: un flujo que llama a la API de Odoo no ejecuta su interfaz. Puede recibir un 200 con un asistente dentro y creerse que ha validado un albarán que sigue abierto.

Cómo habla n8n con Odoo: cuatro caminos, no uno

  • XML-RPC: /xmlrpc/2/common para autenticar y /xmlrpc/2/object para execute_kw. El de la documentación.
  • JSON-RPC: un POST /jsonrpc con service, method y args. El mismo servicio en JSON.
  • Sesión web: /web/session/authenticate y /web/dataset/call_kw con la cookie. Lo que hace tu navegador.
  • La API JSON-2, solo en Odoo 19: POST /json/2/<modelo>/<método> con Authorization: bearer <clave API> y X-Odoo-Database. Sin sesión: los argumentos van como campos con nombre, context incluido.

Los tres primeros siguen funcionando en 19, pero el servidor avisa en el log: «The /xmlrpc, /xmlrpc/2 and /jsonrpc endpoints are deprecated in Odoo 19 and scheduled for removal in Odoo 22». Un flujo nuevo contra un 19 debería ir por /json/2; si sirve también a 17 y 18, por la sesión.

# Sesion + call_kw: funciona igual en 17, 18 y 19.
POST /web/session/authenticate
 {"params":{"db":"lab","login":"n8n_bot2","password":"..."}}
-> 200 {"result":{"uid":6,"server_version":"19.0-20260630"}}   # + cookie

POST /web/dataset/call_kw          # con esa cookie
 {"params":{"model":"crm.lead","method":"create",
            "args":[{"name":"Lead web","type":"lead"}],
            "kwargs":{"context":{"lang":"es_ES"}}}}
-> 200 {"result": 10}

# Clave API en la cabecera, sin sesion: solo Odoo 19.
POST /json/2/crm.lead/create
Authorization: bearer <clave>     X-Odoo-Database: lab
 {"vals_list":[{"name":"Lead via /json/2 (API key)"}]}
-> 200 [9]

# Sin cabecera -> 401 "User not authenticated, use an API Key with a
#                 Bearer Authorization header."
# Metodo privado -> 403 "Private methods (such as
#                 'crm.lead._compute_display_name') cannot be called remotely." 

El nodo Odoo de n8n 2.30.6 elige por ti según la credencial: la de clave API va por /json/2; la clásica va por /jsonrpc con execute, posicional, que no acepta kwargs y no puede pasar contexto. La credencial de clave API avisa además de que requiere «Odoo 19+ and a Custom pricing plan».

El nodo HTTP Request, tal cual quedó

Fíjate en dónde no está la clave: el cuerpo solo lleva datos y la cabecera Authorization la pone una credencial de n8n, que se cifra y se rota aparte.

// Lineas del nodo real, copiadas de `n8n export:workflow`.
"type": "n8n-nodes-base.httpRequest", "typeVersion": 4.2,
"url": "http://odoo:8069/json/2/crm.lead/create",
"authentication": "genericCredentialType",
"genericAuthType": "httpHeaderAuth",
"headerParameters": { "parameters": [
    { "name": "X-Odoo-Database", "value": "lab" } ] },
"jsonBody": "={{ JSON.stringify({ vals_list: [{ name: 'Web: ' + $json.body.empresa, contact_name: $json.body.nombre, email_from: $json.body.email, description: $json.body.mensaje, type: 'lead' }] }) }}",
"credentials": { "httpHeaderAuth": { "name": "Odoo lab - API key (header)" } }
// La clave no esta aqui: la pone esa credencial.

El flujo tampoco entra como administrador, sino como usuario técnico con los grupos justos y el tope que Odoo 19 pone a su clave:

# Usuario tecnico nuevo (solo grupo de usuario interno):
POST /json/2/crm.lead/create
-> 403 "No puede crear informes 'Lead' (crm.lead) ...
            - Sales/Administrator
            - Sales/User: Own Documents Only"
# Tras anadirle sales_team.group_sale_salesman:  -> 200

# Su clave API tampoco dura lo que uno quiera:
_generate(..., +200 dias) -> "No puede exceder 90.0 dias."
_generate(..., sin fecha) -> "La clave API debe tener una
                              fecha de vencimiento"

Ese tope de 90 días es la fecha en la que tu flujo dejará de funcionar sin que nadie toque nada. Apúntala el día que creas la clave.

Cinco automatizaciones que sí caben en un flujo

Todas comparten lo mismo: el hecho ya ocurrió y el flujo solo lo mueve de sitio.

CasoDisparadorLlamada a Odoo
Lead desde un formulario externoWebhook de n8ncrm.lead / create
Aviso al chat al validar un albaránAutomatización de Odoo con acción de webhookNinguna: Odoo empuja
Contactos a una herramienta de marketingProgramado, cada nocheres.partner / search_read por write_date
Alerta de stock bajoProgramado, cada mañanaproduct.product / search_read de qty_available
Informe diario a direcciónProgramadoread_group sobre sale.order

El segundo caso tiene letra pequeña. La acción de webhook envía _model, _id y el nombre de la acción, y es «lanzar y olvidar»: un POST de un segundo, sin reintento. En 19 sale después del commit; en 17 y 18 va dentro de la transacción, así que un n8n lento retrasa al usuario.

# Accion de servidor state='webhook' al crear un lead.
INFO    Webhook call to http://n8n:5678/webhook/odoo-lead
INFO    Webhook call to http://n8n:5678/webhook/odoo-lead - succeeded

# Apuntando a un flujo borrado (el lead se crea igual):
WARNING Webhook call failed: 404 Client Error: Not Found for url:
        http://n8n:5678/webhook/flujo-borrado
WARNING Webhook call timed out after 1s - it may or may not have failed.

Cinco que no, y el motivo exacto

  1. Calcular precios o descuentos. Odoo los calcula desde la tarifa; si el flujo los calcula fuera, al cambiar la tarifa hay dos verdades.
  2. Reservar stock para varios canales. Odoo resuelve la carrera dentro de una transacción; un flujo que lee, decide y escribe por separado sobrevende.
  3. Conciliar pagos. Emparejar y asentar van juntos: si uno se hace fuera y el otro dentro, un corte de red deja media conciliación.
  4. Lo que alguien vaya a auditar. El historial de n8n no es el chatter: «¿quién cambió esto y por qué?» se responde en el documento.
  5. Lo que dependa de un campo que cambia entre versiones. Lo comprobamos escribiendo en Odoo 19 lo que funcionaba en 18.

El caso que mejor lo resume: validar un albarán desde un flujo. Preparamos uno de 10 unidades con 4 reservadas y llamamos a button_validate, lo mismo que el botón.

# WH/OUT/00003: 10 pedidas, 4 reservadas.
POST /web/dataset/call_kw
 {"model":"stock.picking","method":"button_validate","args":[[2]]}
-> 200 {"result": {"name": "¿Crear entrega parcial?",
                   "type": "ir.actions.act_window",
                   "res_model": "stock.backorder.confirmation", ...}}

read -> [{"name":"WH/OUT/00003","state":"assigned"}]   # sigue abierto

HTTP 200, sin error, y un albarán en assigned. Para n8n el paso salió verde; para el almacén, el pedido no ha salido. Un módulo reconoce el asistente y lo deja escrito en el documento: la misma frontera que en Odoo 19 frente a los conectores de middleware.

Y los campos que cambian de nombre entre versiones, lo que rompe un flujo estable tras una migración:

# Odoo 19, escribiendo lo que funcionaba en 17 y 18:
res.users write {"groups_id": [[4, 1]]}
-> "Invalid field 'groups_id' in 'res.users'"     # en 19 se llama group_ids

stock.move create {"name": "Latiguillo", "product_id": 1, ...}
-> "Invalid field 'name' in 'stock.move'"         # en 17 y 18 era obligatorio

Mantenemos el mismo código en Odoo 19, 18 y 17 en 117 módulos publicados: estas diferencias las paga el módulo, no el cliente. Es también el límite de Odoo Studio cuando el proceso deja de ser un campo y pasa a ser una regla.

Los cuatro errores que vemos siempre

  1. El bucle. Una automatización avisa a n8n al cambiar un campo, el flujo escribe en Odoo y el escrito vuelve a dispararla. Se corta con una condición en el disparador.
  2. La credencial en texto plano. Ni en el cuerpo del nodo ni en un nodo de código: en una credencial de n8n, con un usuario técnico de grupos mínimos.
  3. Sin idempotencia. Un webhook reintentado desde el otro lado crea el registro otra vez: tres POST idénticos a nuestro flujo dieron tres leads.
  4. Sin reintento de verdad. Un nodo no reintenta salvo que actives Retry On Fail, y el motor limita los intentos a cinco y la espera a cinco segundos. Sobra para un chat; no para un transportista: nuestro módulo de Correos Express reintenta a los 5, 15, 30, 60, 120 y 240 minutos.

La solución no es de n8n, es de diseño: guarda en Odoo la referencia externa y compruébala antes de crear. En un módulo es una restricción única en base de datos; en un flujo, una lectura previa con ventana de carrera.

Cuándo un flujo se convierte en módulo

El flujo es una forma barata de descubrir si el proceso merece existir. La señal de que se ha quedado corto es una de estas seis:

  • Escribe un campo que Odoo calcula por su cuenta.
  • Dos escrituras tienen que ocurrir juntas o no ocurrir.
  • Alguien auditará dentro de Odoo qué pasó y cuándo.
  • El proceso tiene que sobrevivir a la próxima migración.
  • Hay que poder repetir la llamada sin duplicar nada.
  • El usuario necesita reintentar desde el propio documento.

Llegado ese punto no se tira el flujo: se lleva al módulo la parte que toca el estado de negocio y n8n se queda con el borde. Pasa igual en automatizar tareas administrativas y en la integración con Signaturit.

Resumen: si el flujo se cae y lo único que pasa es que alguien no recibe un aviso, n8n vale. Si se cae y el almacén o el libro de facturas quedan en un estado que nadie sabe leer, eso era un módulo.

Preguntas frecuentes

¿Puedo pasar el idioma o la compañía en la llamada?

Por los tres caminos modernos sí: kwargs.context en call_kw y en execute_kw, y un campo context en /json/2 (un país sale «Spain» con en_US y «España» con es_ES). No puedes con la credencial clásica, que llama a execute, posicional.

¿Dónde guardo la clave API y cuánto dura?

En una credencial de n8n, nunca en el cuerpo del nodo. En Odoo 19 la clave de un usuario no administrador necesita caducidad, y el tope lo fija el grupo: 90 días para un usuario interno.

¿Se rompe mi flujo al migrar de Odoo 18 a 19?

Depende de los campos que toque, y no avisa: res.users.groups_id pasó a group_ids y stock.move.name, obligatorio en 17 y 18, no existe en 19. Prueba tus flujos contra una copia.

Enlaces útiles dentro de FlexigoTech

Servicios de ingeniería OdooImplantación y desarrollo sobre OdooDesarrollo a medidaCuando hace falta móduloUn conector de Odoo para tu softwarePara partners tecnológicosSoluciones por necesidadOrdenado por problema, no por móduloOdoo 19 frente a los conectores de middlewareLa misma frontera, un nivel arribaQué es Odoo Studio y cómo funcionaEl otro «sin código» y sus límitesAutomatizar tareas administrativasAutomatización dentro del ERPFirma electrónica en Odoo con SignaturitOtro caso donde el flujo se queda corto

Lo que hacemos sobre esto

Automatización con OperariaQué hace, capturas, versiones y precio.

¿Tienes flujos de n8n contra Odoo y ya no sabes cuáles aguantan?

Los revisamos, te decimos cuáles se quedan como están y cuáles hay que llevar a un módulo, y hacemos el módulo si toca. Escribe a comercial@flexigobe.com o llama al +34 616 809 504.

Hablar con un ingeniero