#API REST de Integración — Deltav → Odoo 17
Versión: 1.0 — Julio 2026
Base URL:https://api-integracion.mi-empresa.com/v1
Protocolo: REST/JSON sobre HTTPS
Autenticación: Bearer Token (JWT) obtenido con API Key de Odoo
#1. Autenticación
El sistema externo debe autenticarse con la API Key de Odoo para obtener un token JWT de corta duración.
POST /v1/auth/token
Request:
{
"api_key": "a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
Response 200 OK:
{
"access_token": "eyJhbGciOiJIUzI1NiIs...",
"token_type": "bearer",
"expires_in": 3600,
"odoo_uid": 7
}
Uso en llamadas subsiguientes:
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Errores de autenticación:
| Status | Code | Descripción |
|---|---|---|
| 401 | AUTHENTICATION_ERROR | API Key inválida o expirada |
| 401 | TOKEN_EXPIRED | El JWT expiró, re-autenticar |
#2. Convenciones
| Regla | Detalle |
|---|---|
| Content-Type | Todas las requests y responses usan application/json |
| Fechas | ISO 8601: "2026-07-02T15:30:00Z" (datetime) o "2026-07-02" (date) |
| IDs externos | external_ref es un string alfanumérico (máx. 255 chars) que identifica de forma única y estable a cada entidad en el sistema externo |
| Monedas | Se usa el ID de res.currency en Odoo (ej. 2 = USD, 3 = EUR, 21 = ARS) |
| Países | Código ISO 3166-1 alpha-2: "US", "AR", "ES", etc. |
| POST es upsert | Si el external_ref ya existe → se actualiza el registro. Si no → se crea |
| Campos no enviados | No se modifican en Odoo (merge parcial) |
| Orden de carga | 1º Contactos → 2º Productos → 3º BoMs → 4º Pagos |
#3. Endpoints
#3.1 Health Check
GET /v1/health
Response 200 OK:
{
"status": "healthy",
"odoo_version": "17.0",
"odoo_connected": true,
"timestamp": "2026-07-02T17:00:00Z"
}
#3.2 Contactos
Modelo Odoo: res.partner
#Crear o Actualizar
POST /v1/contacts
Request Body:
{
"external_ref": "EXT-CUST-001",
"name": "Acme Corporation",
"is_company": true,
"vat": "US123456789",
"email": "info@acme.com",
"phone": "+1-555-0100",
"mobile": "+1-555-0101",
"website": "https://acme.com",
"street": "123 Main St",
"street2": "Suite 400",
"city": "San Francisco",
"state": "California",
"zip": "94105",
"country_code": "US",
"is_customer": true,
"is_supplier": false,
"payment_term": "immediate",
"contacts": [
{
"name": "John Doe",
"email": "john@acme.com",
"phone": "+1-555-0200",
"position": "CFO"
}
],
"tags": ["enterprise", "premium"]
}
Parámetros:
| Campo | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| external_ref | string (max 255) | ✅ | — | ID único del sistema externo |
| name | string | ✅ | — | Nombre o razón social |
| is_company | boolean | ❌ | false | true si es empresa |
| vat | string | ❌ | — | Nº de identificación fiscal |
| string | ❌ | — | Email principal | |
| phone | string | ❌ | — | Teléfono fijo |
| mobile | string | ❌ | — | Teléfono móvil |
| website | string | ❌ | — | URL del sitio web |
| street | string | ❌ | — | Dirección (calle y número) |
| street2 | string | ❌ | — | Complemento de dirección |
| city | string | ❌ | — | Ciudad |
| state | string | ❌ | — | Provincia / Estado |
| zip | string | ❌ | — | Código postal |
| country_code | string (ISO 2) | ❌ | — | Código de país ("US", "AR") |
| is_customer | boolean | ❌ | false | Marca como cliente |
| is_supplier | boolean | ❌ | false | Marca como proveedor |
| payment_term | string | ❌ | — | "immediate", "net_15", "net_30", "net_60" |
| contacts | array | ❌ | — | Contactos hijos (personas asociadas) |
| contacts[].name | string | ✅ (si se envía) | — | Nombre del contacto |
| contacts[].email | string | ❌ | — | Email del contacto |
| contacts[].phone | string | ❌ | — | Teléfono |
| contacts[].position | string | ❌ | — | Cargo / función |
| tags | array of string | ❌ | — | Etiquetas / categorías |
Response 201 Created:
{
"status": "created",
"external_ref": "EXT-CUST-001",
"odoo_id": 42,
"name": "Acme Corporation",
"created_at": "2026-07-02T17:00:00Z"
}
Response 200 OK (actualización):
{
"status": "updated",
"external_ref": "EXT-CUST-001",
"odoo_id": 42,
"name": "Acme Corporation",
"updated_at": "2026-07-02T17:05:00Z"
}
#Consultar Contacto
GET /v1/contacts/{external_ref}
Parámetros de búsqueda (query string):
| Parámetro | Tipo | Descripción |
|---|---|---|
| search | string | Búsqueda por nombre (ilike) |
| is_customer | boolean | Filtrar clientes |
| is_supplier | boolean | Filtrar proveedores |
| country_code | string | Filtrar por país |
| limit | integer | Máx. resultados (default: 20) |
| offset | integer | Paginación (default: 0) |
Ejemplo: GET /v1/contacts?search=Acme&is_customer=true&limit=20
Response 200 OK:
{
"external_ref": "EXT-CUST-001",
"odoo_id": 42,
"name": "Acme Corporation",
"is_company": true,
"vat": "US123456789",
"email": "info@acme.com",
"phone": "+1-555-0100",
"mobile": "+1-555-0101",
"website": "https://acme.com",
"street": "123 Main St",
"street2": "Suite 400",
"city": "San Francisco",
"state": "California",
"zip": "94105",
"country": "United States",
"country_code": "US",
"is_customer": true,
"is_supplier": false,
"payment_term": "immediate",
"contacts": [
{
"odoo_id": 43,
"name": "John Doe",
"email": "john@acme.com",
"phone": "+1-555-0200",
"position": "CFO"
}
],
"tags": ["enterprise", "premium"]
}
Errores específicos:
| Status | Code | Caso |
|---|---|---|
| 404 | NOT_FOUND | external_ref no existe |
| 400 | VALIDATION_ERROR | Falta name o external_ref |
| 400 | INVALID_COUNTRY | country_code no es un código ISO-2 válido |
#3.3 Productos
Modelos Odoo: product.template + product.product
#Crear o Actualizar
POST /v1/products
Request Body (producto simple):
{
"external_ref": "EXT-PROD-001",
"name": "Laptop Pro 15\"",
"default_code": "LP15-2026",
"barcode": "1234567890123",
"type": "product",
"list_price": 1299.99,
"standard_price": 850.00,
"currency_id": 2,
"uom_id": 1,
"uom_po_id": 1,
"description": "Laptop profesional 15 pulgadas, 16GB RAM, 512GB SSD",
"description_purchase": "Laptop Pro 15\" — pedido mayorista",
"sale_ok": true,
"purchase_ok": true,
"active": true,
"categ_id": "Laptops",
"taxes_id": ["15% VAT"],
"supplier_taxes_id": ["15% VAT"],
"weight": 1.8,
"volume": 0.005
}
Request Body (producto con variantes):
{
"external_ref": "EXT-PROD-002",
"name": "Camiseta Algodón Premium",
"default_code": "TSH-PRM",
"type": "product",
"list_price": 29.99,
"attributes": [
{ "name": "Talle", "values": ["S", "M", "L", "XL"] },
{ "name": "Color", "values": ["Negro", "Blanco", "Azul"] }
]
}
Parámetros:
| Campo | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| external_ref | string (max 255) | ✅ | — | ID único del sistema externo |
| name | string | ✅ | — | Nombre del producto |
| default_code | string | ❌ | — | SKU / referencia interna |
| barcode | string | ❌ | — | Código de barras (EAN13/UPC) |
| type | string | ❌ | "product" | "product" (stockable), "consu" (consumible), "service" |
| list_price | number | ❌ | 0.00 | Precio de venta |
| standard_price | number | ❌ | 0.00 | Costo estándar |
| currency_id | integer | ❌ | moneda cia. | ID de moneda en Odoo |
| uom_id | integer | ❌ | 1 (Unidades) | Unidad de medida de venta |
| uom_po_id | integer | ❌ | 1 (Unidades) | Unidad de medida de compra |
| description | string | ❌ | — | Descripción para ventas |
| description_purchase | string | ❌ | — | Descripción para compras |
| sale_ok | boolean | ❌ | true | Habilitado para venta |
| purchase_ok | boolean | ❌ | true | Habilitado para compra |
| active | boolean | ❌ | true | Producto activo |
| categ_id | string | ❌ | — | Categoría (se busca o crea por nombre) |
| taxes_id | array of string | ❌ | — | Impuestos de venta (por nombre) |
| supplier_taxes_id | array of string | ❌ | — | Impuestos de compra (por nombre) |
| weight | number | ❌ | — | Peso en kg |
| volume | number | ❌ | — | Volumen en m³ |
| attributes | array | ❌ | — | Atributos para generar variantes |
| attributes[].name | string | ✅ (si se envía) | — | Nombre del atributo (ej. "Color") |
| attributes[].values | array of string | ✅ (si se envía) | — | Valores del atributo |
| suppliers | array | ❌ | — | Información de proveedores |
| suppliers[].partner_external_ref | string | ✅ (si se envía) | — | external_ref del proveedor |
| suppliers[].price | number | ✅ (si se envía) | — | Precio de compra |
| suppliers[].min_qty | number | ❌ | 0 | Cantidad mínima |
| suppliers[].delay | integer | ❌ | 0 | Días de entrega |
Response 201 Created (con variantes):
{
"status": "created",
"external_ref": "EXT-PROD-002",
"odoo_template_id": 16,
"default_code": "TSH-PRM",
"name": "Camiseta Algodón Premium",
"variants": [
{ "odoo_id": 40, "name": "Camiseta Algodón Premium (S, Negro)", "default_code": "TSH-PRM-S-NEG" },
{ "odoo_id": 41, "name": "Camiseta Algodón Premium (S, Blanco)", "default_code": "TSH-PRM-S-BCO" }
],
"variants_count": 12,
"created_at": "2026-07-02T17:00:00Z"
}
Response 200 OK (actualización):
{
"status": "updated",
"external_ref": "EXT-PROD-001",
"odoo_template_id": 15,
"updated_at": "2026-07-02T17:05:00Z"
}
#Consultar Producto
GET /v1/products/{external_ref}
Parámetros de búsqueda:
| Parámetro | Tipo | Descripción |
|---|---|---|
| search | string | Búsqueda por nombre o SKU |
| type | string | "product", "consu", "service" |
| categ_id | string | Filtrar por categoría |
| active | boolean | Solo activos/inactivos |
| limit | integer | Default: 20 |
| offset | integer | Default: 0 |
Response 200 OK:
{
"external_ref": "EXT-PROD-001",
"odoo_template_id": 15,
"name": "Laptop Pro 15\"",
"default_code": "LP15-2026",
"barcode": "1234567890123",
"type": "product",
"list_price": 1299.99,
"standard_price": 850.00,
"currency": { "id": 2, "name": "USD" },
"uom": { "id": 1, "name": "Units" },
"categ": { "id": 5, "name": "Laptops" },
"description": "Laptop profesional 15 pulgadas, 16GB RAM, 512GB SSD",
"sale_ok": true,
"purchase_ok": true,
"active": true,
"taxes": [{ "id": 1, "name": "15% VAT" }],
"weight": 1.8,
"volume": 0.005,
"qty_available": 45,
"virtual_available": 30,
"variants": [
{
"odoo_id": 30,
"default_code": "LP15-SLV-16",
"name": "Laptop Pro 15\" (Silver, 16GB)",
"attributes": { "Color": "Silver", "RAM": "16GB" },
"qty_available": 12
}
],
"suppliers": [
{
"partner_external_ref": "EXT-SUP-001",
"partner_name": "TechDistributors Inc",
"price": 780.00,
"min_qty": 10,
"delay": 5
}
]
}
Errores específicos:
| Status | Code | Caso |
|---|---|---|
| 404 | NOT_FOUND | external_ref no existe |
| 400 | VALIDATION_ERROR | Falta name o external_ref |
| 400 | INVALID_TYPE | type no es válido |
| 400 | DUPLICATE_SKU | default_code ya existe en otro producto |
#3.4 Lista de Materiales (BoM)
Modelos Odoo: mrp.bom + mrp.bom.line
#Crear o Actualizar
POST /v1/boms
Request Body:
{
"external_ref": "EXT-BOM-001",
"code": "BOM-LP15-001",
"product_external_ref": "EXT-PROD-001",
"product_qty": 1.0,
"product_uom_id": 1,
"type": "normal",
"active": true,
"lines": [
{
"product_external_ref": "EXT-PROD-002",
"product_qty": 1.0,
"product_uom_id": 1
},
{
"product_external_ref": "EXT-PROD-003",
"product_qty": 2.0,
"product_uom_id": 1
},
{
"product_external_ref": "EXT-PROD-004",
"product_qty": 0.05,
"product_uom_id": 5
}
]
}
Parámetros:
| Campo | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| external_ref | string (max 255) | ✅ | — | ID único del sistema externo |
| code | string | ❌ | — | Código de referencia de la BoM |
| product_external_ref | string | ✅ | — | external_ref del producto terminado |
| product_qty | number | ✅ | — | Cantidad producida por esta BoM |
| product_uom_id | integer | ❌ | 1 (Unidades) | Unidad de medida del producto terminado |
| type | string | ❌ | "normal" | "normal", "phantom" (kit virtual), "subcontracting" |
| active | boolean | ❌ | true | BoM activa |
| lines | array | ✅ | — | Componentes de la BoM |
| lines[].product_external_ref | string | ✅ | — | external_ref del componente |
| lines[].product_qty | number | ✅ | — | Cantidad necesaria |
| lines[].product_uom_id | integer | ❌ | 1 (Unidades) | Unidad de medida del componente |
Nota: Las líneas de BoM en Odoo 17 referencian product.product (variante), no product.template. El middleware resuelve automáticamente a la variante por defecto del componente.
Response 201 Created:
{
"status": "created",
"external_ref": "EXT-BOM-001",
"odoo_id": 8,
"code": "BOM-LP15-001",
"product": {
"external_ref": "EXT-PROD-001",
"name": "Laptop Pro 15\""
},
"product_qty": 1.0,
"type": "normal",
"lines_count": 3,
"created_at": "2026-07-02T17:00:00Z"
}
#Consultar BoM
GET /v1/boms/{external_ref}
Parámetros de búsqueda:
| Parámetro | Tipo | Descripción |
|---|---|---|
| product_external_ref | string | Filtrar por producto terminado |
| type | string | "normal", "phantom", "subcontracting" |
| active | boolean | Solo activas/inactivas |
Response 200 OK:
{
"external_ref": "EXT-BOM-001",
"odoo_id": 8,
"code": "BOM-LP15-001",
"product": {
"external_ref": "EXT-PROD-001",
"odoo_id": 15,
"name": "Laptop Pro 15\""
},
"product_qty": 1.0,
"product_uom": { "id": 1, "name": "Units" },
"type": "normal",
"active": true,
"lines": [
{
"odoo_id": 51,
"product": { "external_ref": "EXT-PROD-002", "name": "Pantalla 15\" IPS" },
"product_qty": 1.0,
"product_uom": { "id": 1, "name": "Units" }
},
{
"odoo_id": 52,
"product": { "external_ref": "EXT-PROD-003", "name": "Módulo RAM 16GB DDR5" },
"product_qty": 2.0,
"product_uom": { "id": 1, "name": "Units" }
},
{
"odoo_id": 53,
"product": { "external_ref": "EXT-PROD-004", "name": "Pasta Térmica Arctic MX-6" },
"product_qty": 0.05,
"product_uom": { "id": 5, "name": "kg" }
}
]
}
Errores específicos:
| Status | Code | Caso |
|---|---|---|
| 404 | NOT_FOUND | external_ref no existe |
| 400 | DEPENDENCY_NOT_FOUND | El product_external_ref o un componente no existe |
| 400 | VALIDATION_ERROR | product_qty ≤ 0 o lines vacío |
| 400 | DUPLICATE_BOM | Ya existe una BoM para ese producto con ese code |
#3.5 Pagos
Modelos Odoo: account.payment + account.move
#Registrar Pago
POST /v1/payments
Request Body — Cobro de cliente (inbound):
{
"external_ref": "EXT-PAY-001",
"payment_type": "inbound",
"partner_type": "customer",
"partner_external_ref": "EXT-CUST-001",
"amount": 2599.98,
"currency_id": 2,
"payment_date": "2026-07-02",
"journal_external_ref": "BANK-US-01",
"payment_method": "manual",
"memo": "Pago facturas INV-001 e INV-002",
"matched_invoices": [
{ "invoice_external_ref": "INV-001", "amount": 1299.99 },
{ "invoice_external_ref": "INV-002", "amount": 1299.99 }
]
}
Request Body — Pago a proveedor (outbound):
{
"external_ref": "EXT-PAY-002",
"payment_type": "outbound",
"partner_type": "supplier",
"partner_external_ref": "EXT-SUP-001",
"amount": 8500.00,
"currency_id": 2,
"payment_date": "2026-07-01",
"journal_external_ref": "BANK-US-01",
"payment_method": "bank_transfer",
"memo": "Pago lote componentes Q2 2026",
"matched_invoices": [
{ "invoice_external_ref": "VENDOR-BILL-005", "amount": 8500.00 }
]
}
Parámetros:
| Campo | Tipo | Requerido | Default | Descripción |
|---|---|---|---|---|
| external_ref | string (max 255) | ✅ | — | ID único del sistema externo para este pago |
| payment_type | string | ✅ | — | "inbound" (cobro) o "outbound" (pago) |
| partner_type | string | ✅ | — | "customer" o "supplier" |
| partner_external_ref | string | ✅ | — | external_ref del cliente o proveedor |
| amount | number (> 0) | ✅ | — | Monto total del pago |
| currency_id | integer | ❌ | moneda cia. | ID de moneda en Odoo |
| payment_date | string (date) | ✅ | — | Fecha "YYYY-MM-DD" |
| journal_external_ref | string | ✅ | — | Referencia del diario contable (mapeado en middleware) |
| payment_method | string | ❌ | "manual" | "manual", "check", "bank_transfer", "credit_card" |
| memo | string | ❌ | — | Nota / referencia del pago |
| matched_invoices | array | ❌ | — | Facturas a conciliar (si se omite → pago a cuenta) |
| matched_invoices[].invoice_external_ref | string | ✅ (si se envía) | — | external_ref de la factura |
| matched_invoices[].amount | number | ❌ | saldo pendiente | Monto a conciliar |
Nota: Las facturas (account.move) deben estar precargadas en Odoo. Este endpoint no las crea.
Response 201 Created:
{
"status": "created",
"external_ref": "EXT-PAY-001",
"odoo_id": 156,
"payment_type": "inbound",
"partner_type": "customer",
"partner": {
"external_ref": "EXT-CUST-001",
"name": "Acme Corporation"
},
"amount": 2599.98,
"currency": { "id": 2, "name": "USD" },
"payment_date": "2026-07-02",
"state": "posted",
"memo": "Pago facturas INV-001 e INV-002",
"matched_invoices": [
{ "invoice_external_ref": "INV-001", "odoo_id": 89, "invoice_number": "INV/2026/0001", "amount_matched": 1299.99 },
{ "invoice_external_ref": "INV-002", "odoo_id": 90, "invoice_number": "INV/2026/0002", "amount_matched": 1299.99 }
],
"created_at": "2026-07-02T17:00:00Z"
}
#Consultar Pago
GET /v1/payments/{external_ref}
Parámetros de búsqueda:
| Parámetro | Tipo | Descripción |
|---|---|---|
| partner_external_ref | string | Filtrar por cliente/proveedor |
| payment_type | string | "inbound" o "outbound" |
| date_from | string (date) | Pagos desde fecha |
| date_to | string (date) | Pagos hasta fecha |
| state | string | "draft", "posted", "cancelled" |
| limit | integer | Default: 20 |
| offset | integer | Default: 0 |
Response 200 OK:
{
"external_ref": "EXT-PAY-001",
"odoo_id": 156,
"payment_type": "inbound",
"partner_type": "customer",
"partner": { "external_ref": "EXT-CUST-001", "odoo_id": 42, "name": "Acme Corporation" },
"amount": 2599.98,
"currency": { "id": 2, "name": "USD" },
"payment_date": "2026-07-02",
"state": "posted",
"journal": { "id": 5, "name": "Bank US" },
"payment_method": "manual",
"memo": "Pago facturas INV-001 e INV-002",
"matched_invoices": [
{ "invoice_external_ref": "INV-001", "odoo_id": 89, "invoice_number": "INV/2026/0001", "amount_matched": 1299.99, "amount_residual": 0.00 }
]
}
Errores específicos:
| Status | Code | Caso |
|---|---|---|
| 404 | NOT_FOUND | external_ref no existe |
| 400 | DEPENDENCY_NOT_FOUND | partner_ext_ref, journal_ext_ref o invoice_ext_ref no existe |
| 400 | VALIDATION_ERROR | amount ≤ 0, payment_type inválido |
| 409 | BUSINESS_RULE_ERROR | Factura ya está totalmente pagada |
| 409 | CONFLICT | external_ref del pago duplicado |
| 422 | JOURNAL_MISMATCH | El diario no está configurado para ese payment_type |
#4. Manejo de Errores
Todos los errores retornan esta estructura consistente:
{
"error": {
"code": "ERROR_CODE",
"message": "Descripción legible en español",
"details": { "field": "información específica" },
"request_id": "req_a1b2c3d4e5"
}
}
#Catálogo completo de errores
| HTTP | Error Code | Descripción |
|---|---|---|
| 400 | VALIDATION_ERROR | Campos requeridos faltantes o con formato inválido |
| 400 | INVALID_COUNTRY | Código de país no válido |
| 400 | INVALID_TYPE | Tipo de entidad no reconocido |
| 400 | DUPLICATE_SKU | SKU/default_code duplicado |
| 400 | DUPLICATE_BOM | BoM duplicada para el producto |
| 400 | DEPENDENCY_NOT_FOUND | Referencia externa dependiente no existe |
| 401 | AUTHENTICATION_ERROR | API Key inválida o falta autenticación |
| 401 | TOKEN_EXPIRED | JWT expirado |
| 403 | PERMISSION_DENIED | Usuario sin permisos para la operación |
| 404 | NOT_FOUND | Recurso no encontrado |
| 409 | CONFLICT | Conflicto de estado (ej. pago duplicado) |
| 409 | BUSINESS_RULE_ERROR | Regla de negocio de Odoo violada |
| 422 | JOURNAL_MISMATCH | Diario no configurado para el tipo de pago |
| 429 | RATE_LIMIT | Demasiadas peticiones |
| 500 | ODOO_ERROR | Error interno de Odoo |
| 502 | UPSTREAM_UNAVAILABLE | Odoo no disponible |
| 504 | UPSTREAM_TIMEOUT | Timeout esperando respuesta de Odoo |
#5. Idempotencia y Mapeo
Toda entidad del sistema externo se identifica por su external_ref, que es único, estable e inmutable. El middleware mantiene una tabla interna de mapeo que resuelve external_ref → odoo_id.
#Tabla de mapeo (external_mapping)
| Columna | Tipo | Descripción |
|---|---|---|
| id | SERIAL PK | Identificador interno |
| entity_type | VARCHAR(50) | 'contact', 'product', 'bom', 'payment' |
| external_ref | VARCHAR(255) | ID único del sistema externo |
| odoo_id | INTEGER | ID correspondiente en Odoo |
| odoo_model | VARCHAR(100) | 'res.partner', 'product.template', etc. |
| created_at | TIMESTAMPTZ | Fecha de creación del mapeo |
| updated_at | TIMESTAMPTZ | Fecha de última actualización |
#Comportamiento del upsert
| Escenario | Acción del Middleware |
|---|---|
| external_ref no existe en mapping | CREATE en Odoo, guardar mapping nuevo |
| external_ref existe en mapping | WRITE (actualizar) en Odoo usando el ID mapeado |
| Misma request repetida | Idempotente: mismo resultado, sin duplicados |
#Unicidad
- CONSTRAINT UNIQUE(entity_type, external_ref)
#6. Mapeo de Campos Odoo ↔ API
#Contactos (res.partner)
| API Field | Odoo Field | Notas |
|---|---|---|
| external_ref | — | Solo en mapping |
| name | name | |
| is_company | is_company | |
| vat | vat | |
| phone | phone | |
| mobile | mobile | |
| website | website | |
| street | street | |
| street2 | street2 | |
| city | city | |
| state | state_id | Resuelto por nombre |
| zip | zip | |
| country_code | country_id | Resuelto por código ISO-2 |
| is_customer | customer_rank | > 0 |
| is_supplier | supplier_rank | > 0 |
| payment_term | property_payment_term_id | Resuelto por nombre |
| contacts | child_ids | Contactos hijo (res.partner tipo contact) |
| tags | category_id | Resuelto por nombre |
#Productos (product.template)
| API Field | Odoo Field | Notas |
|---|---|---|
| name | name | |
| default_code | default_code | |
| barcode | barcode | |
| type | detailed_type | Mapeo: product→product, consu→consu, service→service |
| list_price | list_price | |
| standard_price | standard_price | |
| currency_id | currency_id | |
| uom_id | uom_id | |
| uom_po_id | uom_po_id | |
| description | description_sale | |
| description_purchase | description_purchase | |
| sale_ok | sale_ok | |
| purchase_ok | purchase_ok | |
| active | active | |
| categ_id | categ_id | Busca o crea por nombre |
| taxes_id | taxes_id | Busca por nombre |
| supplier_taxes_id | supplier_taxes_id | Busca por nombre |
| weight | weight | |
| volume | volume |
#Lista de Materiales (mrp.bom)
| API Field | Odoo Field | Notas |
|---|---|---|
| code | code | |
| product_uom_id | product_uom_id | |
| type | type | "normal", "phantom", "subcontracting" |
| active | active |
#Pagos (account.payment)
| API Field | Odoo Field | Notas |
|---|---|---|
| payment_type | payment_type | "inbound" o "outbound" |
| partner_type | partner_type | "customer" o "supplier" |
| partner_external_ref | partner_id | Resuelto vía mapping |
| amount | amount | |
| currency_id | currency_id | |
| payment_date | date | |
| journal_external_ref | journal_id | Mapeado en middleware |
| payment_method | payment_method_line_id | Mapeado por código |
| memo | ref | |
| matched_invoices | — | Conciliación vía account.move.line |
#7. Ejemplos de Integración (curl)
#Flujo completo
#1. Autenticación
TOKEN=$(curl -s -X POST https://api-integracion.mi-empresa.com/v1/auth/token \
-H "Content-Type: application/json" \
-d '{"api_key":"a1b2c3d4-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}' | jq -r '.access_token')
AUTH="Authorization: Bearer $TOKEN"
#2. Crear Contacto
curl -s -X POST https://api-integracion.mi-empresa.com/v1/contacts \
-H "Content-Type: application/json" \
-H "$AUTH" \
-d '{
"external_ref": "EXT-CUST-001",
"name": "Acme Corporation",
"is_company": true,
"email": "info@acme.com",
"is_customer": true,
"country_code": "US"
}'
#3. Crear Producto
curl -s -X POST https://api-integracion.mi-empresa.com/v1/products \
-H "Content-Type: application/json" \
-H "$AUTH" \
-d '{
"external_ref": "EXT-PROD-001",
"name": "Laptop Pro 15\"",
"type": "product",
"list_price": 1299.99
}'
#4. Crear Producto con variantes
curl -s -X POST https://api-integracion.mi-empresa.com/v1/products \
-H "Content-Type: application/json" \
-H "$AUTH" \
-d '{
"external_ref": "EXT-PROD-002",
"name": "Camiseta Algodón Premium",
"type": "product",
"list_price": 29.99,
"attributes": [
{"name": "Talle", "values": ["S", "M", "L", "XL"]},
{"name": "Color", "values": ["Negro", "Blanco", "Azul"]}
]
}'
#5. Crear BoM
curl -s -X POST https://api-integracion.mi-empresa.com/v1/boms \
-H "Content-Type: application/json" \
-H "$AUTH" \
-d '{
"external_ref": "EXT-BOM-001",
"product_external_ref": "EXT-PROD-001",
"product_qty": 1.0,
"lines": [
{"product_external_ref": "EXT-PROD-002", "product_qty": 1.0},
{"product_external_ref": "EXT-PROD-003", "product_qty": 2.0}
]
}'
#6. Registrar Pago
curl -s -X POST https://api-integracion.mi-empresa.com/v1/payments \
-H "Content-Type: application/json" \
-H "$AUTH" \
-d '{
"external_ref": "EXT-PAY-001",
"payment_type": "inbound",
"partner_type": "customer",
"partner_external_ref": "EXT-CUST-001",
"amount": 2599.98,
"currency_id": 2,
"payment_date": "2026-07-02",
"journal_external_ref": "BANK-US-01",
"matched_invoices": [
{"invoice_external_ref": "INV-001", "amount": 1299.99},
{"invoice_external_ref": "INV-002", "amount": 1299.99}
]
}'
#8. Contrato de Notificaciones Salientes
Como parte del módulo integrador_odoo2delta, Odoo 17 notifica al sistema externo (DeltaV) cuando se crean o confirman documentos de venta y compra. Esta sección define el contrato que el sistema externo debe implementar para recibir dichas notificaciones.
#8.1 Endpoints que el Sistema Externo debe exponer
| Método | Endpoint | Evento Odoo | Descripción |
|---|---|---|---|
POST | /notificaciones/ventas | sale.order → create / action_confirm | Notificación de nueva venta o confirmación |
POST | /notificaciones/compras | purchase.order → create / button_confirm | Notificación de nueva orden de compra o confirmación |
Autenticación: Bearer Token (JWT) en header Authorization.
Comportamiento esperado del sistema externo:
- Responder
2xx(200/201) para confirmar recepción exitosa - Responder
4xxpara errores de negocio (no se reintenta) - Timeout máximo: 15 segundos
- Si no responde en 15s o devuelve
5xx, Odoo reintenta hasta 4 veces con backoff exponencial (1s, 2s, 4s, 8s)
#8.2 Payload de Notificación — Venta
POST /notificaciones/ventas desde Odoo → Sistema Externo
{
"tipo": "venta",
"evento": "confirmacion",
"id_odoo": 42,
"numero": "S00042",
"fecha_creacion": "2026-07-15T10:30:00Z",
"fecha_confirmacion": "2026-07-15T10:35:00Z",
"estado": "sale",
"cliente": {
"id_odoo": 15,
"nombre": "Acme Corporation",
"email": "info@acme.com",
"vat": "US123456789"
},
"lineas": [
{
"producto": {
"id_odoo": 30,
"nombre": "Laptop Pro 15\"",
"sku": "LP15-2026",
"cantidad": 2.0,
"precio_unitario": 1299.99,
"subtotal": 2599.98
}
}
],
"total_neto": 2599.98,
"total_impuestos": 389.99,
"total": 2989.97,
"moneda": {"id": 2, "codigo": "USD"},
"notas": "Entrega urgente — prioridad alta"
}
Campos del payload de venta:
| Campo | Tipo | Descripción |
|---|---|---|
| tipo | string | Siempre "venta" |
| evento | string | "creacion" o "confirmacion" según el hook disparado |
| id_odoo | integer | ID del sale.order en Odoo |
| numero | string | Número de orden de venta (secuencia) |
| fecha_creacion | string (ISO 8601) | Fecha de creación de la orden |
| fecha_confirmacion | string (ISO 8601) | Fecha de confirmación (null si solo se creó) |
| estado | string | Estado del sale.order: "draft", "sent", "sale", "done", "cancel" |
| cliente | object | Datos del cliente (res.partner) |
| cliente.id_odoo | integer | ID en Odoo |
| cliente.nombre | string | Razón social |
| cliente.email | string | Email principal |
| cliente.vat | string | Nº de identificación fiscal |
| lineas | array | Líneas de la orden (sale.order.line) |
| lineas[].producto | object | Producto de la línea |
| lineas[].producto.id_odoo | integer | ID del producto en Odoo |
| lineas[].producto.nombre | string | Nombre del producto |
| lineas[].producto.sku | string | SKU / referencia interna |
| lineas[].cantidad | number | Cantidad pedida |
| lineas[].precio_unitario | number | Precio unitario |
| lineas[].subtotal | number | Cantidad × precio unitario |
| total_neto | number | Suma de subtotales sin impuestos |
| total_impuestos | number | Suma de impuestos |
| total | number | Total de la orden (neto + impuestos) |
| moneda | object | Moneda de la orden |
| moneda.id | integer | ID de moneda en Odoo |
| moneda.codigo | string | Código ISO 4217: "USD", "EUR", "ARS" |
| notas | string | Notas internas de la orden |
#8.3 Payload de Notificación — Compra
POST /notificaciones/compras desde Odoo → Sistema Externo
{
"tipo": "compra",
"evento": "confirmacion",
"id_odoo": 15,
"numero": "P00015",
"fecha_creacion": "2026-07-15T09:00:00Z",
"fecha_confirmacion": "2026-07-15T09:05:00Z",
"fecha_prevista": "2026-07-30",
"estado": "purchase",
"proveedor": {
"id_odoo": 28,
"nombre": "TechDistributors Inc",
"email": "orders@techdist.com",
"vat": "US987654321"
},
"lineas": [
{
"producto": {
"id_odoo": 30,
"nombre": "Laptop Pro 15\"",
"sku": "LP15-2026"
},
"cantidad": 10.0,
"precio_unitario": 850.00,
"subtotal": 8500.00
}
],
"total_neto": 8500.00,
"total_impuestos": 1275.00,
"total": 9775.00,
"moneda": {"id": 2, "codigo": "USD"},
"notas": "Pedido trimestral Q3 2026"
}
Campos adicionales del payload de compra:
| Campo | Tipo | Descripción |
|---|---|---|
| tipo | string | Siempre "compra" |
| evento | string | "creacion" o "confirmacion" |
| id_odoo | integer | ID del purchase.order en Odoo |
| numero | string | Número de orden de compra |
| fecha_prevista | string (date) | Fecha prevista de recepción YYYY-MM-DD |
| proveedor | object | Datos del proveedor (res.partner) |
| estado | string | Estado: "draft", "sent", "to approve", "purchase", "done", "cancel" |
#8.4 Configuración del Módulo
El módulo integrador_odoo2delta expone las siguientes opciones de configuración en Settings → Integraciones → DeltaV:
| Parámetro | Descripción | Default |
|---|---|---|
| URL API DeltaV | URL base de la API del sistema externo para notificaciones | (vacío — requerido) |
| API Key | Token de autenticación para las llamadas salientes | (vacío — requerido) |
| Timeout HTTP | Timeout máximo para cada intento de notificación | 15 segundos |
| Máx. reintentos | Número máximo de reintentos ante errores de red/5xx | 4 |
| Backoff inicial | Espera inicial entre reintentos (se duplica cada vez) | 1 segundo |
| Notificar al crear | Enviar notificación en evento create además de confirm | false |
| Log nivel | Nivel de logging para notificaciones | INFO |
#8.5 Errores y reintentos
| Escenario | Comportamiento |
|---|---|
Sistema externo responde 2xx | ✅ Notificación exitosa. Se registra en log. |
Sistema externo responde 4xx | ❌ Error de negocio. No se reintenta. Se registra en log + mensaje en chatter del documento. |
Sistema externo responde 5xx | 🔄 Reintento con backoff exponencial (1s, 2s, 4s, 8s). Máximo 4 intentos. |
| Timeout (> 15s) | 🔄 Igual que 5xx. |
| Error de red (DNS, conexión) | 🔄 Igual que 5xx. |
| Se agotan los 4 reintentos | 🚨 Se registra error crítico en log + se notifica al administrador vía chatter. |
Nota: Las notificaciones son fire-and-forget desde la perspectiva del usuario de Odoo. La confirmación de la orden no se bloquea si la notificación falla — los reintentos ocurren en segundo plano.