Saltar al contenido
Development

¿Agregar una función o mover una responsabilidad?

Por Victor Da Luz
railsrubypostizdev-logblog-manager

Esta semana agregué republicación cruzada a redes sociales en mi blog manager. Ya funciona, pero la versión que publiqué primero tenía un bug que no detecté hasta que alguien me hizo una pregunta directa: “no recuerdo haber pedido esto. ¿Por qué lo implementaste?” La respuesta honesta es que había confundido dos tareas distintas, y la diferencia vale la pena escribirla.

El punto de partida

Mi blog manager ya republica artículos en Medium, Dev.to y Hashnode, y anunciaba publicaciones nuevas en LinkedIn a través de la propia API de LinkedIn. Corro una instancia autoalojada de Postiz para programar publicaciones sociales, y el plan era enrutar los canales sociales, Bluesky, Mastodon, X, LinkedIn, a través de ella. Mantener las plataformas de blog nativas, mover lo social a Postiz. Una separación limpia.

La construcción en sí fue simple. Un cliente pequeño sobre la API pública de Postiz:

def create_post(payload:)
  request(Net::HTTP::Post, "/posts", payload)
end

La autenticación resultó ser la clave de API cruda en el encabezado Authorization, sin prefijo Bearer. El cuerpo del post quiere un settings: {} por canal, y el servidor completa el tipo de proveedor por su cuenta, así que un solo payload genérico funciona para cada canal. Armé un servicio para construir el payload, un job para correrlo, un campo de configuración para la clave, y un selector “Compartir en redes” en cada post. Pruebas en verde, rubocop limpio. Lo documenté y lo entregué.

El bug que publiqué

Acá está la parte que hice mal. La ruta vieja de anuncio a LinkedIn seguía ahí, corriendo en un cron diario. Mi nueva ruta de Postiz también podía publicar en LinkedIn. Así que un solo post podía salir hacia LinkedIn dos veces, una por la vía vieja, otra por la nueva. Incluso había notado la superposición mientras lo construía, y en vez de resolverla dejé una nota llamándola “decisión futura” y seguí adelante.

Ese fue el momento en que aterrizó la pregunta. El issue era mover la publicación social a Postiz. Yo había agregado Postiz junto a lo que ya existía. Suenan parecido pero no son el mismo verbo. Agregar es puramente constructivo: escribir lo nuevo, publicarlo. Mover tiene una segunda mitad fácil de saltarse: hay que ir a retirar lo viejo. Yo había hecho la mitad divertida y le hice un gesto a la otra mitad.

Auditando el resto

En vez de simplemente borrar la única ruta de LinkedIn que conocía, salí a buscar todo lo que publica en un servicio externo en cualquier parte de la app: cada job, cada cron, cada acción de controlador, cada callback de modelo. Quería el mapa completo, porque si se me había pasado una superposición probablemente se me habían pasado otras.

Así fue. Surgieron tres cosas:

  • El anuncio nativo a LinkedIn se disparaba automáticamente desde tres jobs de sincronización diaria distintos, no uno solo. Retirarlo significaba tocar los tres, además de las columnas del modelo, la interfaz, y un montón de pruebas.
  • Mi selector de canal nuevo listaba todos los canales conectados a Postiz. Había conectado Dev.to a Postiz para hacer pruebas, así que el selector me habría dejado tranquilamente publicar en Dev.to a través de Postiz mientras ya se publicaba de forma nativa. Otro doble post, uno que yo mismo había construido.
  • Un post programado a través de Postiz mostraba “programado” para siempre, porque la pieza que confirma que en verdad se publicó vive en un issue aparte que todavía no construí.

El del selector dolió un poco. Había agregado exactamente el riesgo de duplicación que se suponía que estaba eliminando, solo que en otra plataforma.

El arreglo, y la lección

Así que hice el movimiento como corresponde. La ruta nativa de LinkedIn desapareció: el servicio, las columnas, la interfaz, las pruebas, todo, y una migración elimina las columnas. LinkedIn ahora solo es alcanzable a través de Postiz. El selector filtra las plataformas de blog para que no pueda duplicar el flujo nativo. Y la responsabilidad de “publicar en redes sociales” vive en exactamente un solo lugar.

Lo que sigo reaprendiendo: cuando una tarea dice “mover X a Y”, el trabajo está sobre todo en encontrar y eliminar la X vieja, no en construir Y. Construir Y es visible, satisfactorio, y da algo para mostrar. Eliminar X es invisible, es la ausencia de un doble post que nadie te va a agradecer nunca. Pero es el verdadero punto de la tarea. Me lo salté porque no se sentía como progreso, y “no se siente como progreso” es una razón terrible para dejar una trampa en el código.

También me hizo apreciar el valor de mapear todo el sistema antes de dar por terminado un “mover”. No podría haber listado esas tres superposiciones de memoria. Tuve que ir a leer cada ruta de publicación en la app, y solo entonces pude ver que “el cron de LinkedIn” en realidad era tres crons y un bug de selector. Un movimiento no está terminado cuando la ruta nueva funciona. Está terminado cuando las rutas viejas desaparecieron, y solo sabés que desaparecieron si fuiste a mirar.

Qué sigue

La versión actual publica de inmediato con un título y un enlace. Quiero programar publicaciones para después, traer la descripción, y adjuntar una miniatura, ese es el próximo issue. Y la confirmación de “¿en verdad se publicó?” todavía necesita su webhook. Pero la base ya es honesta: una responsabilidad, un solo lugar, sin duplicados silenciosos.

Lecturas relacionadas

También te podría ser útil

Proton

Proton Drive

Almacenamiento en la nube cifrado, del equipo detrás de Proton Mail.

Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).

Más información
Proton

Proton Mail

Correo electrónico cifrado de extremo a extremo, con arquitectura de acceso cero.

Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).

Más información
Proton

Proton Pass

Gestor de contraseñas centrado en la privacidad, del equipo detrás de Proton Mail.

Como socio de Proton, obtengo ingresos por las compras que califican de los servicios de privacidad y seguridad de Proton (Pass, Mail, VPN, Drive).

Más información