Portal de facturación y suscripciones sobre Odoo para una plataforma SaaS
Caso de estudio sobre la construcción de un back office de facturación en Odoo 19 integrado con una plataforma SaaS externa mediante REST API, SSO con JWT y webhooks asíncronos, manteniendo el sistema externo como única fuente de verdad.
Resumen
Una plataforma SaaS de suscripción necesitaba un back office de facturación, contabilidad y gestión del ciclo de vida de suscripciones que conviviera con un sistema externo donde ya residían todos los datos de cliente y producto. El trabajo consistió en construir, sobre Odoo 19, un portal modular integrado vía REST API, SSO y webhooks, manteniendo la plataforma externa como única fuente de verdad, y en escribir los scripts de migración necesarios para volcar el histórico de clientes, suscripciones y pedidos desde el sistema previo sin duplicar datos ni perder estado.
Contexto
La empresa operaba una aplicación móvil y web con miles de usuarios activos y un modelo de ingresos basado en suscripciones recurrentes, con varias modalidades de producto (servicio principal, módulos asociados, addons) y distintos métodos de pago (tarjeta, transferencia, ciclos anuales y trimestrales).
La base técnica del producto ya existía: una plataforma propia encargada de autenticación, contenidos y comunicación con el usuario final. En paralelo, varias funciones operativas (gestión de suscripciones recurrentes, facturación y contabilidad, gestión de referidos y promociones, parte de la analítica de cliente) vivían en herramientas SaaS contratadas por separado. Cada una resolvía bien lo suyo, pero la suma generaba un coste recurrente acumulado, datos repartidos entre sistemas y un trabajo manual notable para conciliarlos.
La decisión de apoyarse en Odoo 19 no respondía a preferencia de framework, sino a una lógica de consolidación: una sola plataforma podía absorber progresivamente esas piezas, aportando de serie contabilidad, facturación, gestión de productos y portales de cliente, y dejando el sistema externo como master de la identidad del usuario. El reto no estaba en Odoo en sí, sino en integrarlo correctamente con la plataforma externa sin duplicar lógicas ni romper la experiencia del usuario final.
Problema
- El sistema externo era el master de los datos de cliente, pero Odoo, por defecto, asume que él mismo es la fuente de verdad.
- El usuario tenía que poder acceder al portal de facturación desde la app sin volver a autenticarse, manteniendo sesión entre varios subdominios.
- Las suscripciones tenían reglas de negocio no triviales: períodos de prueba distintos según producto y método de pago, planes incompatibles entre sí, ciclos de facturación que no pueden mezclarse y un máximo de productos activos por tipología.
- La empresa operaba con varias entidades legales y necesitaba que ciertas facturas se reflejaran automáticamente entre compañías.
- Cualquier cambio relevante en la suscripción (alta, pago, cancelación) tenía que llegar en tiempo real a la plataforma externa, sin bloquear la operativa contable si el endpoint estaba caído.
- Existía un histórico significativo de clientes, direcciones, suscripciones (en distintos estados) y pedidos en el sistema previo, que había que volcar al nuevo entorno preservando relaciones, estados y trazabilidad.
Una integración naïve habría implicado configuraciones manuales propensas a error, doble alta de clientes, emails duplicados desde dos sistemas y trazabilidad pobre cuando algo fallara.
Objetivos
- Convertir Odoo en un back office de facturación sin que operadores ni clientes lo perciban como un sistema separado.
- Consolidar progresivamente en una sola plataforma funciones que antes vivían en herramientas SaaS contratadas por separado, reduciendo coste operativo recurrente.
- Mantener la plataforma externa como única fuente de verdad de los clientes.
- Modelar el ciclo de vida completo de la suscripción dentro de Odoo, con reglas de negocio explícitas.
- Garantizar comunicación bidireccional fiable entre Odoo y la plataforma externa.
- Migrar el histórico desde el sistema previo de forma controlada, repetible y reversible en staging.
- Dejar la configuración operativa en manos del equipo administrativo, sin necesidad de tocar código.
Enfoque técnico
El trabajo se estructuró en once módulos de Odoo con responsabilidades bien delimitadas. La separación modular fue una decisión consciente: cada bloque funcional (sincronización, autenticación, suscripciones, pagos, webhooks, referidos, portal) podía evolucionar y desplegarse de forma independiente.
Sincronización de clientes vía API. Se expuso un endpoint REST como única vía de alta y actualización de clientes. Cada registro lleva un identificador externo que actúa como clave de enlace; el email queda solo como fallback. Se desactivó el self-registration y se silenciaron los emails que Odoo emite por defecto (bienvenida, reset de contraseña) para que toda la comunicación con el usuario quedara centralizada en la plataforma externa.
SSO con JWT y sesión cross-subdominio. Se construyó un endpoint de autenticación que valida un JWT firmado de corta duración (HS256, 60 segundos), con detección de reuso mediante el JTI y registro completo de cada intento. Solo se admiten usuarios de tipo portal; los internos quedan fuera por diseño. La cookie de sesión se ancla al dominio padre, manteniendo la sesión activa al saltar entre subdominios.
Modelado de suscripciones con reglas explícitas. Las suscripciones se taxonomizaron por tipo de producto y método de pago. El período de prueba se configura a nivel de producto y método, no como una constante global. Si el método de pago no puede determinarse con certeza al confirmar el pedido, no se concede trial: la opción conservadora siempre prevalece. Sobre el carrito y los pedidos creados en back office se aplica una protección en cuatro capas (cantidades máximas por línea, reemplazo automático entre planes equivalentes, prevención de duplicados activos y consistencia de ciclo de facturación) para que ningún flujo, ni público ni interno, pueda llevar al sistema a un estado inválido.
Webhooks asíncronos hacia la plataforma externa. Cada cambio relevante (suscripción confirmada, pago confirmado, cancelación) emite un webhook saliente. La entrega se delega en queue_job cuando está disponible, con backoff exponencial y reintentos automáticos; si la librería no está instalada, el sistema cae en envío síncrono sin romper la operación. Cada intento queda registrado con URL, payload, código HTTP, respuesta y siguiente reintento, accesible desde el back office. Un fallo en el webhook nunca interrumpe la operación contable.
Multi-compañía con facturación reflejada. Cuando una factura se publica en la compañía principal, el sistema genera automáticamente los documentos espejo (pedido de venta, factura de cliente, pedido de compra y factura de proveedor) en las compañías vinculadas, según reglas configuradas a nivel de producto. Los fallos se aíslan: si una compañía secundaria falla, las demás y la factura original quedan intactas.
Cierre del portal y modo webview. El portal se modificó para que los clientes no pudieran modificar datos que residen en el sistema externo (direcciones, seguridad, perfil). Se inyecta una variable de sesión desde el JWT para ocultar header, navegación y pie cuando se renderiza dentro de la app, dando una experiencia nativa sin duplicar plantillas.
Scripts de migración del histórico. Antes del corte a producción hubo que volcar al nuevo entorno los clientes, direcciones, suscripciones (en sus distintos estados) y pedidos pasados procedentes del sistema previo. Se escribió una batería de scripts de migración independientes, uno por entidad, ejecutables en orden y de forma idempotente:
- Cada script lee desde su fuente (export estructurado o API legada) y normaliza los datos a una estructura intermedia antes de impactar Odoo.
- Se resolvieron los mapeos entre modelos de datos: estados de suscripción (activa, en trial, expirada, pausada, cancelada), métodos de pago, tipos de producto, ciclos de facturación, países y provincias por código ISO o por nombre, monedas e idiomas.
- Se preservaron las relaciones entre entidades (cliente → direcciones, cliente → suscripciones, suscripción → producto y ciclo, suscripción → pagos históricos) y se conservaron los identificadores externos para que la sincronización en caliente posterior enlazara sin duplicar.
- Cada script es idempotente: al volver a ejecutarse detecta los registros ya creados por su identificador externo y los actualiza, en lugar de duplicarlos.
- Se incluyó un modo dry-run completo que reporta qué se crearía, qué se actualizaría y qué referencias quedarían sin resolver, sin tocar la base de datos.
- El procesamiento es por lotes y el log de errores se emite por registro, no por lote, de modo que un fallo aislado nunca detiene la migración completa.
Esto permitió ensayar la migración varias veces en staging antes del corte definitivo, ajustando mapeos a medida que aparecían casos límite (clientes con varias direcciones del mismo tipo, suscripciones en grace period sin pago asociado, productos descatalogados en el sistema previo pero aún referenciados por suscripciones activas).
Decisiones y trade-offs
Se descartó construir un sistema de facturación a medida porque Odoo ya aportaba contabilidad, fiscalidad, gestión documental y portales. El coste de mantenerse dentro del framework era menor que reinventarlo.
Se priorizó la modularización aunque implicara más ficheros y más superficie de despliegue. La alternativa, un único módulo monolítico, habría hecho mucho más arriesgado cualquier cambio futuro y habría obligado a desplegar todo de golpe cada vez.
En los flujos críticos se eligió la opción conservadora frente a la permisiva: sin trial si no se identifica el método de pago, sin webhooks durante migraciones, fallos de sistemas satélite que nunca bloquean el principal. El coste es alguna fricción puntual; la ganancia es que el sistema no entra en estados inconsistentes.
Los scripts de migración se diseñaron como código mantenible y repetible, no como scripts de usar y tirar. La alternativa de un volcado one-shot habría sido más rápida de escribir, pero habría obligado a partir de cero ante cada incidencia o ante cada nueva ronda en staging. El coste extra de hacerlos idempotentes y con dry-run se amortizó en la primera repetición.
Toda la configuración relevante (secret JWT, dominio de cookie, URL de webhook, días de trial por producto, mapeos multi-compañía) vive en el panel de ajustes. Esto traslada control al equipo de operaciones sin necesidad de tocar código ni redesplegar.
Resultado
- Un back office de facturación y suscripciones plenamente operativo, integrado con la plataforma principal sin duplicar fuentes de datos.
- El cliente final transita entre app y portal sin percibir que son sistemas distintos.
- El equipo de operaciones gestiona altas, prórrogas, pagos, promociones y referidos sin intervención técnica.
- Los webhooks salientes resisten caídas temporales del receptor sin perder eventos.
- El corte desde el sistema previo se ejecutó sobre un histórico ya ensayado varias veces en staging, sin pérdida de relaciones ni de estados de suscripción.
- La separación en módulos permitió añadir funcionalidades posteriores (referidos, promociones, ajustes en el flujo de pago) sin tocar el núcleo de sincronización ni el de SSO.
Stack técnico
- Odoo 19 (Community/Enterprise) sobre Odoo.sh
- Python
- PostgreSQL
- REST API + JWT (HS256)
- OCA
queue_job(con fallback síncrono) - Webhooks salientes con backoff exponencial
- Scripts de migración idempotentes con dry-run y procesamiento por lotes
- Stripe como proveedor de pago
- XML / QWeb para vistas y portal
Qué demuestra este caso
Este caso demuestra capacidad para diseñar un backend modular sobre Odoo en un contexto donde el ERP no es la fuente de verdad, sino un satélite especializado en facturación, y para acompañar ese diseño con una migración cuidadosa del histórico previo. Combina criterio de arquitectura (separación clara de responsabilidades, integraciones resilientes, configuración fuera del código) con sensibilidad operativa (reglas de negocio defensivas, scripts de migración repetibles, trazabilidad de cada evento, experiencia coherente para el cliente final).
Versión corta para tarjeta
Title: Portal de facturación sobre Odoo integrado con plataforma SaaS
Sector: SaaS de suscripción
Problem: Una plataforma con un sistema propio como fuente de verdad necesitaba un back office de facturación y gestión de suscripciones sin duplicar datos de cliente, además de migrar al nuevo entorno el histórico de clientes, suscripciones y pedidos del sistema previo.
Technical approach: Once módulos Odoo 19 con responsabilidades aisladas, sincronización vía REST API, SSO con JWT y sesión cross-subdominio, webhooks asíncronos con reintentos, facturación multi-compañía automatizada y scripts de migración idempotentes con dry-run.
Result: Back office operativo y trazable, integración resiliente con la plataforma externa, histórico migrado sin pérdida de estado y configuración íntegramente en manos del equipo de operaciones.
Tags: Odoo, Suscripciones, Integraciones, SaaS, Migración, Backend
CTA opcional
Si tu producto tiene un sistema propio y necesitas integrarle un back office de facturación, ERP o portal de cliente sin duplicar datos ni perder control sobre las reglas de negocio, puedo ayudarte a diseñarlo y construirlo.