Al validar un albarán con transportista, Odoo automatiza cuatro cosas: la llamada a la API, la etiqueta PDF adjunta al albarán, el número de seguimiento en carrier_tracking_ref y el correo o SMS al cliente con ese número. No automatiza la recogida física ni la verdad del peso y los bultos. Aquí va el flujo con el modelo de Odoo de cada paso y el código de nuestros conectores de Correos Express y MRW, que usamos en nuestro propio almacén. Sin Odoo detrás, lo mismo está en integrar la API de un transportista en una web propia.
Idea clave: el fallo caro no es la etiqueta que no sale, sino la que no sale y nadie se entera. Un conector serio distingue el error de datos (lo enseña y no reintenta) del error del transportista (lo encola, reintenta con espera creciente y lo escribe en el chatter).
Qué hace falta antes
- Contrato y credenciales del entorno correcto. Correos Express separa test (
www.test.cexpr.es) y producción (www.cexpr.es); MRW usa el servicio web Sagec con franquicia, abonado, departamento, usuario y contraseña. Los dos traen un botón de prueba de conexión (action_cex_test_connectionyaction_mrw_test_connection) para verificarlo antes del primer envío real. - Dirección normalizada. Nombre, calle, población, CP de cinco dígitos en España y teléfono de al menos nueve dígitos, en remitente y destinatario: la lista literal de
_cex_validate_shipment_inputs. - Peso y bultos en el albarán. Los módulos leen los paquetes (
package_level_ids/package_ids) o, si no hay, un bulto conshipping_weightoweight. Peso cero no sale. - Tipo de operación con «Imprimir etiqueta». En Odoo 19 la llamada automática al validar solo ocurre con
print_labelactivado. Es la causa más común de «he validado y no ha pasado nada».
El flujo paso a paso, con el modelo de Odoo en cada paso
- Validar (
stock.picking._action_done). Odoo 19 llama a_send_confirmation_email, questock_deliverysobrescribe: con transportistarate_and_ship, sin tracking aún y tipo de operación que imprime etiqueta, ejecutasend_to_shipper()antes de componer el correo. - Llamar a la API (
delivery.carrier.send_shipping). Odoo despacha acex_send_shippingomrw_send_shipping: JSON contraapiRestGrabacionEnviok8s/jsonen Correos Express, SOAPTransmEnviocontrasagec.mrw.esen MRW. Si ya había número de envío, se devuelve el existente. - Guardar la etiqueta (
ir.attachment). El PDF se adjunta al albarán comoEtiqueta_CEX_<número>.pdfoEtiqueta_MRW_<número>.pdfy se publica en el chatter. Con varios bultos, Correos Express devuelve varias con sufijo_01,_02… - Escribir el tracking (
carrier_tracking_ref).send_to_shippertomatracking_numberde la respuesta, lo escribe en el campo estándar y publica «Paquete enviado al transportista … con número de rastreo …». Los módulos guardan además su número propio. - Avisar al cliente (
mail.template/sms.template).stockenvía «Shipping: Send by Email» si la compañía tiene activada la confirmación por correo, ystock_smsel SMS. Ambas incluyen el tracking solo sicarrier_tracking_refya está relleno.

Validar antes de llamar: qué se comprueba y qué devuelve el transportista si no
El transportista valida, pero tarde y con mensajes para su equipo. Los módulos comprueban primero en Odoo y solo llaman si no hay errores; si los hay, un único UserError con todos. Si aun así rechaza, la respuesta se clasifica por palabras clave y se traduce a una frase para el operario; el administrador ve el detalle técnico.
| Comprobación en Odoo | Si el transportista lo rechaza igualmente | Lo que ve el operario |
|---|---|---|
| Dirección, población, CP de 5 dígitos (ES), teléfono ≥ 9 dígitos | Respuesta con «dirección», «código postal», «teléfono», «destinatario», «población» o «país» | «CEX detectó errores de dirección/CP/teléfono en remitente o destinatario» |
| Peso mayor que cero, al menos un bulto, máximo 99 (CEX) | Respuesta con «peso», «kilos», «bulto», «volumen», «alto», «largo», «ancho» | «CEX rechazó peso o datos de bultos» / «MRW rechaza el peso del envío para el servicio seleccionado» |
| Servicio coherente con destino: internacional (90/91) no para España, PAQ Empresa (92) solo a empresas | Código de servicio no contratado o no válido | MRW reintenta con los códigos alternativos del preset y del catálogo; si ninguno entra, el administrador ve «Servicios probados» en el error |
| Códigos de cliente y solicitante presentes | «unauthorized», «forbidden», «credenciales» | «CEX rechazó las credenciales del transportista». No se reintenta: no es transitorio |
Reintentos: un fallo silencioso es peor que uno visible
Un 503 en plena preparación de pedidos no es raro. Dejar el albarán a medias es la peor respuesta; tragarse el error, la segunda. Los módulos hacen una tercera: decidir si el fallo es transitorio y reintentar con espera creciente.
- Transitorio en Correos Express es HTTP 408, 409, 423, 425, 429, 500, 502, 503 o 504, o un mensaje con «timeout», «vuelva a intentarlo», «service unavailable» o «error interno»; MRW usa una lista más corta: 408, 429, 500, 502, 503 y 504. El resto es error de datos: se marca y se muestra.
- En el albarán quedan
cex_retry_pending,cex_retry_count,cex_retry_next_attempt,cex_last_error_messageycex_last_attempt_at(en MRW, prefijomrw_), y en el chatter «CEX temporalmente no disponible. Reintento automático #1 programado para …». - Un cron cada 5 minutos recoge los pendientes sin número de envío con la hora cumplida, de 50 en 50, y los pasa por el mismo código del primer intento.
- Espera: 5, 15, 30, 60, 120 y 240 minutos en Correos Express; 5, 15, 30, 60, 180 y 360 en MRW. Seis intentos por defecto, configurable. Al agotarlos, «Se alcanzó el máximo de reintentos automáticos»: ahí hace falta una persona.
- Quién se entera: el operario, en el error y en el chatter. Odoo 19 añade su red: si en un lote ya se procesó un albarán en el transportista, el siguiente error no revierte el lote; se convierte en una actividad de aviso.
El fragmento que fija la espera, en el módulo de Correos Express:
# flexigobe_correos_express/models/delivery_carrier.py (v19.0.3.0.19)
CEX_RETRY_BACKOFF_MINUTES = [5, 15, 30, 60, 120, 240]
def _cex_retry_delay_minutes(self, attempt_no):
self.ensure_one()
idx = max(int(attempt_no or 1) - 1, 0)
if idx >= len(CEX_RETRY_BACKOFF_MINUTES):
idx = len(CEX_RETRY_BACKOFF_MINUTES) - 1
return CEX_RETRY_BACKOFF_MINUTES[idx]El correo al cliente: cuándo se dispara y qué lleva
Se dispara al validar, no al crear el albarán. La plantilla estándar («Shipping: Send by Email», en Inventario → Ajustes → Confirmación de correo electrónico) dice «Nos alegra informarle de que su pedido ha sido enviado» y, si hay carrier_tracking_ref, añade la referencia con enlace a carrier_tracking_url; el SMS de stock_sms añade el número de pedido (object.origin) y la referencia en texto plano, sin enlace. Como stock_delivery llama a send_to_shipper antes de super()._send_confirmation_email(), el tracking ya existe al componer el correo. Si el envío quedó en cola de reintento, el correo sale sin tracking.
El enlace lo construye cada módulo: https://s.correosexpress.com/SeguimientoSinCP/search?n=<tracking> en Correos Express y https://www.mrw.es/seguimiento_envios/MRW_historico_nacional.asp?enviament=<número> en MRW. Tracking sin enlace significa que aún no hay referencia.
Caso: los dos módulos propios y en qué se diferencian
Comparten arquitectura y se mantienen para Odoo 19, 18 y 17. Las API difieren, y se nota en la etiqueta, la anulación y lo que no hacen.
| Aspecto | Correos Express (v19.0.3.0.19) | MRW (v19 · 1.0.1) |
|---|---|---|
| Protocolo | REST JSON: grabación, etiqueta, seguimiento, recogidas, PUDO, parar entrega | SOAP Sagec: TransmEnvio, TransmEnvioInternacional, GetEtiquetaEnvio, CancelarEnvio |
| Etiqueta | PDF, ZPL o EPL; un adjunto por bulto | PDF o ZPL por SOAP; si no llega el binario, se descarga de la URL que devuelve MRW |
| Multibulto | Lista de bultos con peso y medidas, máximo 99 | Un BultoRequest por bulto y NumeroBultos |
| Tracking | Cron cada 30 minutos que escribe estado, descripción y fecha en el albarán | Botón que abre el seguimiento de MRW; sin sincronización periódica |
| Anulación | «Parar entrega» por API; si el contrato no lo permite, queda como manual y avisa en el chatter | CancelarEnvio; si MRW dice que ya no existe, sincroniza el estado local en vez de fallar |
| Recogidas | Grabar, modificar, anular y consultar; búsqueda de puntos PUDO | Solo la fecha de recogida, dentro de la petición de envío |
| Contra reembolso | No: reembolso se envía vacío | No: Reembolso va fijo a «N» |
Ninguno gestiona hoy el contra reembolso, y nos lo preguntan mucho. Más en Correos Express en Odoo y MRW en Odoo; si aún eliges, qué transportista encaja con un ecommerce en Odoo.
Cómo comprobar que funciona
No con el sandbox: con un albarán real validado y estas cuatro evidencias, las de las capturas.
- Un adjunto
Etiqueta_CEX_…pdfoEtiqueta_MRW_…pdfcon código de barras legible. El de MRW mira la cabecera del binario (%PDF-,^XA,<?xml,<html) y, si no es etiqueta imprimible, no la guarda: lo dice en el chatter. carrier_tracking_refrelleno y «Etiqueta OK» con el número de envío.- En el chatter: «Paquete enviado al transportista…», «Etiqueta … generada correctamente» con el PDF, y el SMS o correo al cliente si están activados.
- Campos de reintento a cero. Si no, el envío no ha llegado al transportista aunque el albarán esté validado.
Con un transportista sin módulo publicado, mismo flujo y otro adaptador: dentro del servicio de logística en Odoo y, para consultoras, como ingeniería para consultorías. La regla de nuestros servicios es la misma: la etiqueta sale del albarán, el tracking vuelve al albarán y nadie copia nada.
Preguntas frecuentes
¿La etiqueta se genera al crear el pedido o al validar el albarán?
Al validar: _action_done dispara send_to_shipper si hay transportista con integración completa, no hay tracking y el tipo de operación tiene «Imprimir etiqueta». Los módulos añaden un botón para lanzarlo a mano.
¿Por qué el correo al cliente ha salido sin número de seguimiento?
Porque al componerlo carrier_tracking_ref estaba vacío: el transportista falló y el envío quedó en cola, o el tipo de operación no llama al transportista al validar. El chatter dice cuál.
¿Se puede anular un envío desde Odoo?
Con matices: MRW llama a CancelarEnvio; Correos Express usa «Parar entrega» y, si el contrato no lo tiene, registra la anulación como manual. Queda en el chatter.
Enlaces útiles dentro de FlexigoTech
Lo que hacemos sobre esto
¿Tu almacén sigue imprimiendo etiquetas desde el portal del transportista?
Dinos qué transportista usas, qué versión de Odoo tienes y cuántos albaranes salen al día. Te decimos qué parte ya existe, qué hay que configurar y qué habría que programar. Escribe a comercial@flexigobe.com o llama al +34 616 809 504.

