Odoo
Cómo construimos nuestro propio blog headless sobre Odoo
Por qué no elegimos Ghost, dejamos el contenido junto a nuestros leads, y las dos cosas que estuvieron a punto de salir mal
Toda empresa llega tarde o temprano a la misma decisión pequeña y aburrida: ¿dónde vive el blog? A nosotros nos llegó hace unas semanas, y las dos respuestas obvias —una plataforma de blogging externa, o una carpeta de archivos en el mismo repositorio de la web— nos parecieron equivocadas por razones que no tenían nada que ver con el software de blogs.
Así que lo construimos sobre el sistema que ya lleva el resto del negocio: Odoo. No porque sea la opción de moda —casi seguro que no lo es— sino porque era la honesta, y porque somos partner de Odoo, y poner en producción una integración propia sobre nuestro propio CRM es de esas cosas que o demuestran el punto, o nos dejan mal delante de todos. Esta es la versión donde demostró el punto, y los dos sitios donde estuvo cerca de no hacerlo.
La decisión aburrida que todo el mundo se salta
Una plataforma de blogging externa es un segundo servicio que hay que mantener vivo: un segundo acceso, una segunda factura, un segundo sitio donde un excolaborador quizá todavía tenga permisos. Una carpeta de artículos en Markdown dentro del propio repositorio de la web es el problema contrario —barata de operar, pero publicar un artículo pasa a ser tarea de quien sepa abrir un pull request, que en la práctica significa que casi nunca se hace.
Odoo ya estaba en pie. Ya tenía los accesos de nuestro equipo, nuestros grupos de permisos, nuestro CRM —y, no por casualidad, los mismos leads que un artículo de blog se supone que tiene que ayudar a generar—. Que un artículo y el lead que produce vivan en la misma base de datos no es una coincidencia que diseñamos después; es la razón de fondo para hacer esto.
Un artículo, dos idiomas — y por qué un campo traducible era la herramienta equivocada
Odoo trae una forma nativa de traducir un campo: marcarlo <code>translate=True</code> y rellenar el otro idioma en un diálogo lateral. Funciona bien para el nombre de un producto. No funciona para un artículo, y el motivo no tiene que ver con Odoo: tiene que ver con lo que de verdad es un artículo traducido.
Un artículo bilingüe de verdad casi nunca es el mismo texto dos veces. El ejemplo que conecta con un lector hispanohablante no es el que conecta con uno angloparlante; el título que funciona como titular en un idioma suena plano en el otro. Tratar eso como «un texto original más su traducción» no es simplificar el modelo: es describir mal las dos piezas de escritura independientes que en realidad existen.
Así que cada idioma es su propio registro completo, y una clave estable e invisible une a los dos —la misma clave, que nunca aparece en ninguna URL, que no se traduce nunca y que no se vuelve a tocar una vez puesta—. Es la misma idea que nuestro sitio ya usa para todo lo que existe en dos idiomas: un identificador estable para la relación, y un slug localizado para la dirección que el lector ve de verdad.
Una API que falla alto y claro
La web lee los artículos mediante una API HTTP pequeña y deliberadamente aburrida —REST plano, no el protocolo RPC que Odoo usa internamente— porque quien está al otro lado es un proceso de compilación, no un navegador, y un proceso de compilación tiene que poder depurarse con un solo <code>curl</code>.
La regla que más importó al construirla fue simple: fallar alto y claro, o no fallar. Si falta la clave de contenido, la API rechaza toda petición con un error explícito, a propósito —un blog que se sirve a sí mismo en silencio sin ninguna clave configurada es un desenlace peor que un blog que por un momento no compila—. Y si un mismo slug existe a la vez en los dos idiomas, la API se niega a adivinar cuál quiso decir el lector, porque adivinar mal significa publicar un artículo en el idioma equivocado sin que nadie se entere hasta que un lector lo comenta.
El único campo que podía fallar en silencio
El cuerpo del artículo se guarda como HTML crudo, sin que el saneador habitual de Odoo actúe al escribirlo —el filtro estándar es lo bastante agresivo como para borrar el formato que un artículo de verdad necesita—. Es una concesión razonable de hacer una vez. También es, si se deja ahí sin pensarlo dos veces, una forma de dejar que cualquier cosa escrita en ese campo llegue sin filtrar a un sitio público.
Antes de que esto se acercara a un lector, revisamos el conjunto buscando específicamente ese tipo de hueco —y lo encontramos—. La corrección mueve el filtro al otro extremo de la tubería: nada se sanea al entrar, todo se sanea al salir, en el único punto por el que tiene que pasar de todas formas, sin importar quién lo escribió ni cómo. La única excepción son los vídeos incrustados, que ese mismo filtro borraría por completo; esos sobreviven sólo si vienen de una lista corta de dominios autorizados, y sólo después de reconstruirse desde cero a partir de su URL de origen —así que de lo que escribió quien edita no sobrevive nada en un embed salvo la dirección a la que apunta—.
De pulsar «Publicar» a una página real en internet
La web en sí es estática —el mismo archivo servido a cada visitante, generado una vez y guardado en caché en el borde de la red, que es casi todo el motivo de que sea rápida—. Eso crea una única contrapartida real que vale la pena decir con honestidad: el sitio no le pregunta nada a Odoo mientras un visitante lo está mirando. Le pregunta una vez, mientras se compila.
Lo que significa que publicar un artículo no lo pone en línea por sí solo: dispara una recompilación, y es esa recompilación la que de verdad lo publica, normalmente en uno o dos minutos. Editar algo ya publicado se comporta distinto a propósito: en vez de recompilar en cada pulsación, las correcciones se agrupan y se sueltan juntas cada diez minutos, para que una tarde de retoques cueste una sola recompilación y no veinte.
El error que nunca llegó a un lector
El error más útil que encontramos aquí nunca tocó a un solo visitante, porque se descubrió mientras la tubería todavía se estaba construyendo y probando contra datos falsos, antes de apuntarla a algo real.
El proceso de compilación le pide a Odoo todos los artículos en los dos idiomas en el menor número de peticiones razonable. La primera versión de esa petición pedía todo de una vez, sin decir qué idioma quería —y el nombre de las etiquetas y la biografía del autor de un artículo son justo el tipo de contenido que Odoo sí traduce de forma nativa, resuelto contra el idioma que tenga esa petición en ese momento—. Pedir los dos idiomas de un tirón hace que las etiquetas de todos los artículos vuelvan en un único idioma fijo —el que le tocara por defecto a esa petición—, sin relación alguna con el idioma real de cada artículo. Un artículo en español habría mostrado su etiqueta de tema en inglés, en silencio, en cada página, sin un solo error en ningún sitio que lo señalara.
La corrección fue pedir una llamada por idioma en vez de una sola para todo, que es un cambio más pequeño de lo que el error merecía. Descubrirlo antes de que existiera un solo artículo real es exactamente para lo que sirve probar una tubería contra datos falsos antes de apuntarla a contenido real: el tipo de fallo que de verdad estábamos buscando era éste —una salida que parece correcta y está mal en silencio, de una forma que nada señala por sí sola.
Lo que esto es en realidad
Nada de esto es una historia sobre software de blogs. Es lo que parece una semana normal de trabajo de integración cuando el cliente es honesto sobre lo que necesita, las restricciones son reales y no supuestas, y alguien de verdad revisa las partes peligrosas antes de darlo por terminado —y no después de que un lector las encuentre—.
Que esta vez sea Odoo es porque Odoo es lo que ya llevamos nosotros mismos —y porque construimos exactamente este tipo de cosas, sobre exactamente este tipo de sistema, para clientes que no somos nosotros—. Si tu equipo tiene un caso parecido —contenido, o leads, o un proceso operativo— que un sistema que ya tienes podría resolver, nos gustaría saber cuál es.
¿Tienes un desafío tecnológico en mente?
Cuéntanos qué estás intentando resolver. Te ayudamos a explorar el camino adecuado — y si no lo hay, también te lo decimos.
Hablemos