<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: David De Los Santos Cuy Sánchez</title>
    <description>The latest articles on Forem by David De Los Santos Cuy Sánchez (@davidcuy).</description>
    <link>https://forem.com/davidcuy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F197188%2F0543ff84-1baa-4f48-82c1-68f03ccb8400.jpg</url>
      <title>Forem: David De Los Santos Cuy Sánchez</title>
      <link>https://forem.com/davidcuy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/davidcuy"/>
    <language>en</language>
    <item>
      <title>Cómo uso Mermaid para comunicarme con producto, arquitecturar con devs y documentar todo al mismo tiempo</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Tue, 07 Apr 2026 05:04:09 +0000</pubDate>
      <link>https://forem.com/davidcuy/como-uso-mermaid-para-comunicarme-con-producto-arquitecturar-con-devs-y-documentar-todo-al-mismo-3ih4</link>
      <guid>https://forem.com/davidcuy/como-uso-mermaid-para-comunicarme-con-producto-arquitecturar-con-devs-y-documentar-todo-al-mismo-3ih4</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhprxbs6rbmyvys5mc1o1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhprxbs6rbmyvys5mc1o1.png" alt="cover" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hay un momento en la carrera de casi todo desarrollador en el que te das cuenta de que el código ya no es el problema más difícil. El problema más difícil es explicarle a alguien de producto por qué el sistema funciona como funciona, o por qué un cambio que parece sencillo en realidad tiene diez implicaciones que no se ven a simple vista.&lt;/p&gt;

&lt;p&gt;Durante mucho tiempo, mis herramientas para eso eran las mismas de siempre: una pizarra, una presentación armada de prisa, o en el mejor caso, un diagrama en draw.io que tardaba más en hacerse que en volverse obsoleto. El problema con esas herramientas no es que sean malas — es que viven desconectadas del código, del repositorio y de la realidad del sistema. En cuanto algo cambia, el diagrama miente.&lt;/p&gt;

&lt;p&gt;Hasta que vi algo que cambió mi forma de trabajar.&lt;/p&gt;

&lt;h2&gt;
  
  
  La revelación: código que genera diagramas
&lt;/h2&gt;

&lt;p&gt;Fue mi jefe quien me mostró por primera vez lo que Mermaid podía hacer. Había construido un script en Python que leía el esquema de la base de datos y generaba automáticamente un Entity Relationship Diagram en Mermaid. Ese diagrama vivía en el repositorio y se actualizaba solo con cada cambio mediante un flujo de GitHub Actions.&lt;/p&gt;

&lt;p&gt;La primera vez que lo vi me quedé parado. No porque fuera magia — era código muy simple — sino porque conectaba dos mundos que yo siempre había tratado por separado: la documentación y el sistema real. Un diagrama que se genera desde la fuente de verdad no puede mentir. Es documentación viva.&lt;/p&gt;

&lt;p&gt;Eso me abrió los ojos a algo más grande: si puedo generar un ERD automáticamente, ¿qué más puedo hacer con una herramienta que entiende texto y produce diagramas? ¿Qué pasa si la uso no solo para documentar para devs, sino para comunicar con producto?&lt;/p&gt;

&lt;h2&gt;
  
  
  El caso real: de 10 escenarios a 4 eventos
&lt;/h2&gt;

&lt;p&gt;La respuesta llegó en un proyecto concreto. Estaba rediseñando un sistema de facturación para un equipo en otro país — lo cuento con más detalle en el post anterior sobre event-driven y facturación — y el punto de partida era una lista de más de 10 escenarios de negocio que el equipo de producto manejaba como casos independientes.&lt;/p&gt;

&lt;p&gt;El equipo técnico los había modelado tal como producto los describía. El resultado era un sistema frágil, con lógica duplicada y casos borde que aparecían constantemente. Cuando empecé a analizar el dominio de verdad, encontré que todos esos escenarios eran variantes de solo 4 eventos de negocio reales.&lt;/p&gt;

&lt;p&gt;El reto era convencer al equipo de producto de eso. No bastaba con decirlo — tenía que mostrarlo.&lt;/p&gt;

&lt;p&gt;Usé dos tipos de diagramas Mermaid para hacerlo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline: para mostrar el patrón en el tiempo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El primer diagrama fue un timeline que mostraba cómo, a lo largo del ciclo de vida de una deuda, los 10+ escenarios que el equipo manejaba en realidad se repetían como instancias de los mismos 4 momentos. Ver los patrones distribuidos en el tiempo hizo inmediatamente visible algo que en texto era invisible: la complejidad era accidental, no esencial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sequence: para explicar cómo funcionarán los 4 eventos&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;El segundo diagrama fue un diagrama de secuencia que mostraba, de forma funcional y sin código, cómo cada uno de los 4 eventos fluiría a través del sistema: quién lo dispara, qué componentes se involucran, qué resultado produce.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sequenceDiagram
    actor Usuario
    participant Sistema
    participant EventBridge
    participant Facturacion as "Servicio de Facturación"

    Usuario-&amp;gt;&amp;gt;Sistema: Realiza pago parcial
    Sistema-&amp;gt;&amp;gt;Sistema: Valida monto y deuda
    Sistema--&amp;gt;&amp;gt;Usuario: Confirma recepción del pago
    Sistema-&amp;gt;&amp;gt;EventBridge: Publica evento PagoParcialRegistrado
    EventBridge-&amp;gt;&amp;gt;Facturacion: Entrega evento PagoParcialRegistrado
    Facturacion-&amp;gt;&amp;gt;Facturacion: Actualiza saldo de la deuda
    Facturacion-&amp;gt;&amp;gt;Facturacion: Genera registros contables
    Facturacion--&amp;gt;&amp;gt;EventBridge: Publica evento DeudaActualizada
    EventBridge--&amp;gt;&amp;gt;Sistema: Notifica actualización de deuda
    Sistema--&amp;gt;&amp;gt;Usuario: Muestra nuevo estado de la deuda
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El equipo de producto entendió el modelo en esa misma reunión. No porque el diagrama fuera bonito — sino porque habló en su idioma: flujo, tiempo, actores, resultados. Sin terminología técnica innecesaria.&lt;/p&gt;

&lt;h2&gt;
  
  
  Los dos diagramas que más uso y cuándo
&lt;/h2&gt;

&lt;p&gt;Después de ese proyecto, Mermaid se volvió una herramienta permanente en mi forma de trabajar. Estos son los dos tipos que más uso:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sequence Diagram — mi favorito para comunicar con no técnicos&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Es el diagrama que más valor me ha dado con áreas de negocio. Permite mostrar, a un nivel alto y sin código, cómo se comunican diferentes componentes entre sí: un proveedor externo con nuestros servicios, un usuario con el sistema, un evento disparando una cadena de reacciones.&lt;/p&gt;

&lt;p&gt;Lo que hace especialmente útil al sequence es que funciona en dos niveles al mismo tiempo: para alguien de producto, muestra el flujo funcional de extremo a extremo. Para un desarrollador, es una guía de qué componentes debe tocar y en qué orden. Un solo diagrama, dos audiencias.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flowchart — para flujos de proceso y decisiones&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Cuando necesito mostrar un proceso con bifurcaciones, condiciones y caminos alternativos, el flowchart es la herramienta natural. Lo uso tanto para explicar procesos de negocio a producto como para documentar algoritmos y flujos de decisión para el equipo técnico.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    A[Evento recibido] --&amp;gt; B{Tipo de evento}
    B --&amp;gt;|Adquisición| C[Procesar adquisición]
    B --&amp;gt;|Pago parcial| D[Aplicar pago parcial]
    B --&amp;gt;|Cierre de mes| E[Generar cierre de mes]
    B --&amp;gt;|Liquidación| F[Calcular liquidación]

    C --&amp;gt; G[Registrar en auditoría]
    D --&amp;gt; G[Registrar en auditoría]
    E --&amp;gt; G[Registrar en auditoría]
    F --&amp;gt; G[Registrar en auditoría]

    G --&amp;gt; H[Publicar eventos derivados]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dónde viven los diagramas: la clave de la documentación viva
&lt;/h2&gt;

&lt;p&gt;Una de las mayores ventajas de Mermaid es que no está atado a una sola herramienta. Dependiendo del contexto, uso:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Wikis&lt;/strong&gt;: el diagrama vive junto al código, se versiona con git, y cualquier developer del equipo puede actualizarlo en el mismo PR donde modifica el sistema.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Confluence y Microsoft Loop&lt;/strong&gt;: ambas tienen visualizadores nativos de Mermaid, útil cuando la documentación vive en herramientas corporativas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;draw.io&lt;/strong&gt;: tiene un generador que toma código Mermaid y produce el diagrama visual, práctico para presentaciones o documentos formales.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Este mismo blog&lt;/strong&gt;: integré un visor de Mermaid directamente, así que los diagramas que estás viendo en este post son código Mermaid renderizado en tiempo real. Hashnode también lo soporta de forma nativa. dev.to, por ahora, no tiene ese visor — ahí los bloques aparecen como código plano.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La versatilidad es real: el mismo bloque de texto produce el diagrama en la mayoría de esos contextos. No hay que rehacerlo, exportarlo ni mantener dos versiones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué Mermaid y no otra cosa
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Es código, entonces es versionable.&lt;/strong&gt; El diagrama vive en el repositorio, cambia con un commit, y su historial de cambios es el historial del sistema. Cuando alguien pregunta "¿cómo funcionaba esto hace seis meses?", el diagrama tiene la respuesta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Es automatizable.&lt;/strong&gt; Como el ERD que me mostró mi jefe: puedes generar diagramas desde una fuente de verdad y actualizarlos automáticamente con GitHub Actions o cualquier pipeline de CI. Documentación que se mantiene sola.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Es compatible con IA.&lt;/strong&gt; Hoy en día, generar o modificar un diagrama Mermaid con un LLM es trivial. Describes el flujo en lenguaje natural y obtienes el código del diagrama. La barrera de entrada desapareció.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo empezar mañana
&lt;/h2&gt;

&lt;p&gt;Si nunca has usado Mermaid, abre &lt;a href="https://mermaid.live" rel="noopener noreferrer"&gt;mermaid.live&lt;/a&gt; y escribe tu primer diagrama de secuencia. La sintaxis es legible — en 15 minutos tienes algo funcional.&lt;/p&gt;

&lt;p&gt;Si ya lo conoces pero no lo estás usando con negocio, el próximo proyecto donde tengas que explicar un flujo a alguien no técnico es tu oportunidad. Antes de armar la presentación, prueba hacer el sequence diagram primero. Te vas a sorprender de cuánto más rápido llega el entendimiento.&lt;/p&gt;

&lt;p&gt;Y si quieres llevar la documentación al siguiente nivel, agrega un paso a tu pipeline de CI que genere o valide tus diagramas desde el código. El mismo principio del ERD automático aplica a cualquier artefacto del sistema que puedas describir en texto estructurado.&lt;/p&gt;

&lt;p&gt;Mermaid no es solo una herramienta de diagramas. Es la forma en que los diagramas dejan de ser documentación que envejece y se convierten en parte viva del sistema.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;¿Usas Mermaid en tu día a día? ¿Tienes algún caso de uso que no mencioné aquí? Me encantaría leerlo en los comentarios.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>liderazgotcnico</category>
      <category>architecture</category>
      <category>systemdesign</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Cuando el negocio no es el tuyo: cómo entender el dominio para rediseñar un sistema de facturación con event-driven</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Fri, 03 Apr 2026 02:54:37 +0000</pubDate>
      <link>https://forem.com/davidcuy/cuando-el-negocio-no-es-el-tuyo-como-entender-el-dominio-para-redisenar-un-sistema-de-facturacion-d14</link>
      <guid>https://forem.com/davidcuy/cuando-el-negocio-no-es-el-tuyo-como-entender-el-dominio-para-redisenar-un-sistema-de-facturacion-d14</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscbwxsrhp0fm3b0iyazn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fscbwxsrhp0fm3b0iyazn.png" alt="cover" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Llegué a ese proyecto sin saber exactamente en qué me estaba metiendo.&lt;/p&gt;

&lt;p&gt;El sistema tenía problemas de facturación: cargos que no se aplicaban, cargos duplicados, y nadie con certeza absoluta de cuándo debía procesarse uno. El equipo de producto tenía documentados más de 10 escenarios. Y yo, que venía de otro país, tenía que entender primero cómo funcionaba ese negocio con sus propias reglas, sus propios términos y su propia lógica.&lt;/p&gt;

&lt;p&gt;Esa combinación — un sistema roto, un dominio que no era el mío y presión por resolverlo — es exactamente el tipo de situación donde es tentador ir directo al código. Por suerte, no lo hice.&lt;/p&gt;

&lt;h2&gt;
  
  
  El síntoma: una lista interminable de “cuándo facturar”
&lt;/h2&gt;

&lt;p&gt;Cuando llegué al sistema, lo primero que vi fue la lista de escenarios que el equipo manejaba:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;El cliente tiene una deuda activa sin pago&lt;/li&gt;
&lt;li&gt;El cliente tiene una deuda activa con pago&lt;/li&gt;
&lt;li&gt;El cliente tiene una deuda activa con pago y es fin de mes&lt;/li&gt;
&lt;li&gt;El cliente tiene una deuda activa sin pago y es fin de mes&lt;/li&gt;
&lt;li&gt;... y múltiples combinaciones de los anteriores&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Antes — lógica basada en combinaciones de estados:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD
    A[Sistema detecta cambio] --&amp;gt; B{¿Deuda activa?}
    B --&amp;gt;|Sí, sin pago| C{¿Es fin de mes?}
    B --&amp;gt;|Sí, con pago| D{¿Es fin de mes?}
    B --&amp;gt;|No| E[No facturar]
    C --&amp;gt;|Sí| F[Facturar: sin pago + fin de mes]
    C --&amp;gt;|No| G[Facturar: sin pago]
    D --&amp;gt;|Sí| H[Facturar: con pago + fin de mes]
    D --&amp;gt;|No| I[Facturar: con pago]
    F --&amp;gt; J[...y más combinaciones]
    G --&amp;gt; J
    H --&amp;gt; J
    I --&amp;gt; J
    J --&amp;gt; K[❌ Duplicados / cargos perdidos]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El equipo técnico los había modelado tal como el equipo de producto los había descrito. Y el equipo de producto los había descrito tal como los había visto ocurrir en el sistema. El problema es que eso no es el negocio — son los síntomas del negocio.&lt;/p&gt;

&lt;p&gt;Hay una diferencia importante entre “esto es lo que pasa en el sistema” y “esto es lo que significa para el cliente”. Cuando modelas los primeros, terminas con lógica frágil que se rompe ante cualquier caso borde. Cuando modelas los segundos, encuentras los patrones reales.&lt;/p&gt;

&lt;p&gt;Lo que teníamos era un sistema que reaccionaba a estados del sistema. Lo que necesitábamos era un sistema que reaccionara a eventos del negocio.&lt;/p&gt;

&lt;h2&gt;
  
  
  El proceso: aprender el dominio antes de tocar el código
&lt;/h2&gt;

&lt;p&gt;Tomé la decisión de no proponer ninguna solución técnica hasta entender cuándo debía procesarse un cargo de verdad. Esto implicó sesiones con el equipo de producto — no para que me explicaran los escenarios del sistema, sino para que me explicaran el negocio: ¿qué cambia en la vida del cliente para que se genere una obligación?&lt;/p&gt;

&lt;p&gt;Esa distinción cambió completamente la conversación. Empezamos a hablar en términos del ciclo de vida de una deuda, no de combinaciones de estados. Y cuando haces eso, la lista interminable de escenarios empieza a colapsar. Muchos de ellos eran variantes del mismo momento de negocio, disparados desde diferentes partes del código por razones históricas, no por razones lógicas.&lt;/p&gt;

&lt;p&gt;Al final, todo el ruido se redujo a &lt;strong&gt;4 eventos de negocio reales:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Adquisición del préstamo&lt;/strong&gt; — el cliente adquiere una deuda&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pago parcial del usuario&lt;/strong&gt; — el cliente realiza un abono&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fin de mes con pagos&lt;/strong&gt; — cierre del periodo, se consolidan los movimientos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liquidación de la deuda&lt;/strong&gt; — el cliente salda completamente&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cualquier escenario de la lista original era consecuencia de uno de estos cuatro. No había un quinto evento. Solo había complejidad accidental acumulada con el tiempo.&lt;/p&gt;

&lt;h2&gt;
  
  
  La solución técnica: AWS EventBridge como bus central
&lt;/h2&gt;

&lt;p&gt;Con el modelo de negocio claro, la implementación se volvió relativamente directa. Usamos AWS EventBridge como bus de eventos central.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Después — 4 eventos de negocio claros:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
    A[🏦 Adquisición del préstamo] --&amp;gt; E
    B[💸 Pago parcial del usuario] --&amp;gt; E
    C[📅 Fin de mes con pagos] --&amp;gt; E
    D[✅ Liquidación de la deuda] --&amp;gt; E
    E([EventBridge]) --&amp;gt; F[Servicio de Facturación]
    F --&amp;gt; G[✓ Cargo generado correctamente]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cada dominio publica eventos de negocio cuando algo relevante ocurre. El servicio de facturación escucha únicamente los 4 eventos que importan y ejecuta la lógica correspondiente.&lt;/p&gt;

&lt;p&gt;Una ventaja importante que ya venía del diseño del sistema: cada evento traía un identificador único derivado de los movimientos contables. Esto eliminó de raíz el problema de duplicados sin necesidad de lógica adicional — la trazabilidad ya estaba en el dato.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Dead letter queue para fallos.&lt;/strong&gt; Los eventos que no se procesan correctamente van a una DLQ para revisión, en lugar de perderse silenciosamente como pasaba antes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Logs de auditoría por evento.&lt;/strong&gt; Cada evento procesado deja un registro con su ID, timestamp y resultado. Por primera vez, el equipo tenía visibilidad completa del ciclo de facturación.&lt;/p&gt;

&lt;h2&gt;
  
  
  El resultado
&lt;/h2&gt;

&lt;p&gt;El cambio fue inmediato. Los cargos duplicados desaparecieron. Los cargos perdidos también. El equipo de producto pasó de manejar excepciones manualmente cada semana a tener un sistema que, cuando falla, falla de forma visible y recuperable.&lt;/p&gt;

&lt;p&gt;Pero lo que más me quedó no fue el resultado técnico. Fue lo que aprendí sobre cómo enfrentar sistemas en dominios que no conoces.&lt;/p&gt;

&lt;h2&gt;
  
  
  La lección de arquitectura
&lt;/h2&gt;

&lt;p&gt;Hay un concepto útil para este tipo de situaciones: la distinción entre &lt;strong&gt;complejidad esencial&lt;/strong&gt; y &lt;strong&gt;complejidad accidental&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;La complejidad esencial viene del negocio. Si la facturación de préstamos es complicada, es porque el negocio tiene reglas complicadas. No puedes eliminarla, solo modelarla bien.&lt;/p&gt;

&lt;p&gt;La complejidad accidental es la que nosotros mismos creamos: código que creció sin diseño, escenarios modelados como síntomas, lógica duplicada por urgencia. Esta sí puedes eliminarla — pero solo si primero entiendes cuál es cuál.&lt;/p&gt;

&lt;p&gt;Los 10+ escenarios que teníamos eran mayormente complejidad accidental. Los 4 eventos que encontramos eran la complejidad esencial del negocio. El trabajo fue separar una de la otra.&lt;/p&gt;

&lt;p&gt;Como gente de tech, a veces caemos en la trampa de modelar lo que vemos en lugar de modelar lo que es. Vemos muchos casos, muchos estados, muchas combinaciones — y construimos un sistema para cada uno. Pero casi siempre, detrás de esa superficie hay un modelo más simple esperando ser descubierto.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;La próxima vez que enfrentes un sistema “complejo”, antes de buscar la solución técnica, hazte esta pregunta: ¿estoy mirando el negocio o estoy mirando los síntomas del negocio?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;em&gt;¿Te ha tocado simplificar un dominio que parecía irreduciblemente complejo? Me encantaría leer cómo lo abordaste en los comentarios.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>decisionesdearquitec</category>
      <category>eventdriven</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>El algoritmo que se moría en CDMX: de O(10 ) a una consulta SQL</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Tue, 31 Mar 2026 03:28:43 +0000</pubDate>
      <link>https://forem.com/davidcuy/el-algoritmo-que-se-moria-en-cdmx-de-o107-a-una-consulta-sql-30dg</link>
      <guid>https://forem.com/davidcuy/el-algoritmo-que-se-moria-en-cdmx-de-o107-a-una-consulta-sql-30dg</guid>
      <description>&lt;h1&gt;
  
  
  El algoritmo que se moría en CDMX: cómo pasé de O(10⁷) a una consulta en base de datos
&lt;/h1&gt;

&lt;p&gt;Hace algunos años trabajé en un proyecto de marketing por teléfono. El objetivo era generar listas de números telefónicos válidos para hacer campañas de llamadas en distintas ciudades de México. Si en algún momento recibiste una de esas llamadas… lo siento. No fue personal.&lt;/p&gt;

&lt;p&gt;El punto técnico del asunto era este: necesitábamos generar números de teléfono que fueran válidos según la regulación del IFT (Instituto Federal de Telecomunicaciones). Y para eso, construimos un algoritmo. Uno que funcionaba. Pero que a cierta escala, simplemente se moría.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo funcionan los teléfonos en México (lo mínimo necesario)
&lt;/h2&gt;

&lt;p&gt;En México, todos los números telefónicos tienen 10 dígitos. Lo interesante es que los primeros 2, 3 o 4 dígitos están asignados a una localidad y estado específicos según la numeración publicada por el IFT. Esto significa que si sabes en qué ciudad quieres operar, ya conoces una parte del número desde el inicio.&lt;/p&gt;

&lt;p&gt;Pero el IFT no solo define los prefijos: también establece restricciones sobre qué combinaciones de dígitos son válidas. No todos los sufijos son permitidos. Hay reglas.&lt;/p&gt;

&lt;p&gt;Así que el problema real era: &lt;strong&gt;dado un prefijo de ciudad, generar todas las combinaciones válidas de los dígitos restantes&lt;/strong&gt; (entre 6 y 8 dígitos, dependiendo del prefijo), respetando las restricciones del regulador.&lt;/p&gt;

&lt;h2&gt;
  
  
  La primera versión: el infierno de los &lt;code&gt;for&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;La implementación inicial fue lo más natural que se me ocurrió en ese momento: ciclos &lt;code&gt;for&lt;/code&gt; anidados.&lt;/p&gt;

&lt;p&gt;La lógica era directa: iterar sobre cada posición de dígito, generar todas las combinaciones posibles, y dentro de cada iteración aplicar los &lt;code&gt;if&lt;/code&gt; con las reglas del IFT para filtrar las combinaciones inválidas.&lt;/p&gt;

&lt;p&gt;En el peor caso —ciudades con prefijo de 3 dígitos, donde quedan 7 dígitos libres— la complejidad llegaba a &lt;strong&gt;O(10⁷)&lt;/strong&gt;, es decir, hasta 10 millones de iteraciones solo para generar las combinaciones, antes siquiera de aplicar los filtros.&lt;/p&gt;

&lt;p&gt;Para ciudades pequeñas, funcionaba. Tardaba un rato, pero terminaba.&lt;/p&gt;

&lt;p&gt;Para ciudades como &lt;strong&gt;CDMX o Monterrey&lt;/strong&gt;, el proceso tardaba &lt;strong&gt;horas&lt;/strong&gt;. Literalmente. Y en aquella época yo no tenía experiencia con servidores ni procesamiento distribuido, así que el script corría en local, peleando contra los límites de memoria y CPU de mi equipo.&lt;/p&gt;

&lt;p&gt;El algoritmo era &lt;em&gt;correcto&lt;/em&gt;. El problema era el enfoque.&lt;/p&gt;

&lt;h2&gt;
  
  
  El insight clave: el problema no era el algoritmo, era la herramienta
&lt;/h2&gt;

&lt;p&gt;Llegó un momento en que me puse a pensar diferente. El cuello de botella no era la lógica de negocio —las reglas del IFT eran las mismas sin importar cómo las implementara—. El problema era que estaba usando un proceso iterativo para resolver algo que en realidad era un problema de &lt;strong&gt;combinación de conjuntos de datos&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Y los motores de bases de datos llevan décadas siendo increíblemente buenos para exactamente eso.&lt;/p&gt;

&lt;p&gt;El cambio de mentalidad fue este:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;En lugar de generar las combinaciones con código, ¿qué tal si modelo las reglas como datos y dejo que la base de datos haga el cruce?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  La solución: tablas temporales + JOINs + bulk insert
&lt;/h2&gt;

&lt;p&gt;El nuevo enfoque funcionó así:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Las reglas del IFT se convirtieron en tablas
&lt;/h3&gt;

&lt;p&gt;En lugar de tener las restricciones codificadas en &lt;code&gt;if&lt;/code&gt; dentro de los ciclos, las traduje a tablas (muchas veces temporales) en la base de datos. Cada tabla representaba los valores válidos para una posición de dígito, condicionados al estado y localidad seleccionados.&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
sql

-- Ejemplo simplificado

CREATE TEMPORARY TABLE digitos_validos_pos1 AS

SELECT valor

FROM reglas_ift

WHERE estado = 'CDMX'

AND posicion = 1;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>architecture</category>
      <category>systemdesign</category>
    </item>
    <item>
      <title>Cómo conecté Slack, Notion, Google Calendar y Sanity con Claude como orquestador para automatizar mi blog</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Wed, 25 Mar 2026 15:53:20 +0000</pubDate>
      <link>https://forem.com/davidcuy/como-conecte-slack-notion-google-calendar-y-sanity-con-claude-como-orquestador-para-automatizar-cej</link>
      <guid>https://forem.com/davidcuy/como-conecte-slack-notion-google-calendar-y-sanity-con-claude-como-orquestador-para-automatizar-cej</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjysx0taoq7lnu3d7d6a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbjysx0taoq7lnu3d7d6a.png" alt="cover" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tenía el problema clásico de cualquier persona que quiere construir una marca personal técnica: las ideas llegaban en el momento menos oportuno. En medio de una reunión, en la ducha, en el camino al trabajo. Las anotaba en cualquier lado, y la mayoría moría ahí.&lt;/p&gt;

&lt;p&gt;Cuando sí tenía tiempo para escribir, el proceso era tan manual que el costo de activación era altísimo: abrir Notion, crear la entrada, pensar el outline, escribir el post, buscar una imagen, publicar en Sanity, redactar el post de LinkedIn… Para cuando llegaba al final, ya no quería volver a hacerlo.&lt;/p&gt;

&lt;p&gt;Entonces decidí resolver el problema como lo haría cualquier arquitecto de software: construyendo un sistema.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema que quería resolver
&lt;/h2&gt;

&lt;p&gt;No era falta de ideas. Era fricción.&lt;/p&gt;

&lt;p&gt;Cada paso del proceso de publicación vivía en una herramienta diferente, sin conexión entre ellas. Y cada vez que tenía que pasar de una a otra manualmente, había una probabilidad alta de que el post nunca viera la luz.&lt;/p&gt;

&lt;p&gt;Lo que necesitaba era un pipeline de contenido que funcionara como funciona un pipeline de CI/CD: automatizado, confiable, y que me dejara enfocarme solo en lo que no se puede automatizar — la experiencia real y el criterio técnico.&lt;/p&gt;

&lt;h2&gt;
  
  
  La arquitectura del sistema
&lt;/h2&gt;

&lt;p&gt;El flujo completo quedó así:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0] IDEA          →  Slack (#mi-blog-para-mi-marca-personal)
[1] CAPTURA       →  Notion (Inspiration &amp;amp; Ideas)
[2] PRIORIZACIÓN  →  Notion + Google Calendar
[3] OUTLINE       →  Notion (Blog Posts)
[4] REDACCIÓN     →  Skill: blog-tech-writer
[5] IMAGEN        →  Skill: blog-image-prompter → Gemini
[6] REVISIÓN      →  Notion (Editing → Ready to Publish)
[7] PUBLICACIÓN   →  Sanity CMS
[8] DISTRIBUCIÓN  →  Skill: blog-linkedin-advisor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;El orquestador de todo esto es &lt;strong&gt;Claude&lt;/strong&gt;, ejecutado desde &lt;strong&gt;Cowork&lt;/strong&gt; — una herramienta de Anthropic que permite a Claude tomar acciones reales en aplicaciones conectadas vía MCP (Model Context Protocol).&lt;/p&gt;

&lt;p&gt;Los MCPs conectados son: Slack, Notion, Google Calendar, y Sanity. Cada uno expone herramientas que Claude puede invocar directamente, sin intermediarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cómo funciona en la práctica
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Paso 1: La idea nace en Slack
&lt;/h3&gt;

&lt;p&gt;El canal &lt;code&gt;#mi-blog-para-mi-marca-personal&lt;/code&gt; es mi buzón de ideas. Lo uso desde el móvil, sin pensar demasiado. Un mensaje, una frase, un contexto.&lt;/p&gt;

&lt;p&gt;Lo que antes perdía porque no tenía dónde anotarlo ahora queda registrado con timestamp, searchable, y accesible para Claude.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 2: Claude procesa y registra en Notion
&lt;/h3&gt;

&lt;p&gt;Con un solo prompt, Claude revisa el canal de Slack, extrae las ideas nuevas y las registra automáticamente en la base de datos &lt;strong&gt;Inspiration &amp;amp; Ideas&lt;/strong&gt; de Notion con el status &lt;code&gt;New&lt;/code&gt;, la fuente, y las notas de contexto.&lt;/p&gt;

&lt;p&gt;Incluso responde en el hilo de Slack confirmando el registro. No tengo que hacer nada más que lanzar el prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 3: Priorización semanal
&lt;/h3&gt;

&lt;p&gt;Cada lunes, Claude revisa las ideas pendientes en Notion y mi disponibilidad en Google Calendar. Me propone qué escribir esa semana, con justificación basada en mi audiencia y perfil, y agenda dos eventos: uno para escribir y otro para publicar.&lt;/p&gt;

&lt;p&gt;Esto resolvió uno de los problemas más sutiles: el de la parálisis por elección. Tener alguien que te diga "esta semana escribe esto y por esta razón" cambia completamente la dinámica.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 4: Redacción con el skill blog-tech-writer
&lt;/h3&gt;

&lt;p&gt;Este es el paso donde más valor genera el sistema. El skill &lt;code&gt;blog-tech-writer&lt;/code&gt; está entrenado con mi perfil, mi tono, y mi audiencia. No produce posts genéricos de internet — produce posts que suenan a mí, desde mi experiencia real.&lt;/p&gt;

&lt;p&gt;Cuando le digo "redacta el post del outline en Notion", Claude lee el outline, lo combina con el contexto de mi perfil y genera un draft completo en Markdown. El draft va directo a Notion con el status &lt;code&gt;Drafting&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 5: Imagen y distribución
&lt;/h3&gt;

&lt;p&gt;El skill &lt;code&gt;blog-image-prompter&lt;/code&gt; genera un prompt optimizado para Gemini — ahí sí hay un paso manual porque necesito generar la imagen y subirla. Pero al menos el prompt está listo y guardado en Notion.&lt;/p&gt;

&lt;p&gt;El skill &lt;code&gt;blog-linkedin-advisor&lt;/code&gt; genera dos variantes del post de LinkedIn: una más técnica y una más narrativa. Las guarda en Notion para que yo elija cuál usar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Paso 6: Publicación en Sanity
&lt;/h3&gt;

&lt;p&gt;Cuando el post está listo y aprobado, Claude lo toma de Notion y lo sube a Sanity como draft. Yo reviso una última vez en el Studio y publico.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendí construyendo esto
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Los sistemas de contenido son igual de importantes que los sistemas de software.&lt;/strong&gt; Un pipeline de publicación sin automatización tiene los mismos problemas que un proceso de deployment manual: es lento, propenso a errores, y depende de que alguien tenga energía y tiempo al mismo tiempo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El MCP es el cambio de paradigma que estaba esperando.&lt;/strong&gt; No es solo que Claude pueda responder preguntas — es que Claude puede tomar acciones reales en tus herramientas. La diferencia entre un asistente que te dice qué hacer y uno que lo hace contigo es enorme.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Las skills son el activo más valioso del sistema.&lt;/strong&gt; El skill &lt;code&gt;blog-tech-writer&lt;/code&gt; no es magia — es contexto bien documentado sobre cómo escribo, a quién le escribo y qué quiero lograr. Cualquiera puede construir el suyo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La consistencia gana a la perfección.&lt;/strong&gt; Este sistema no genera el mejor post posible. Genera el post que de otra forma no existiría. Y eso vale más.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Vale la pena el setup inicial?
&lt;/h2&gt;

&lt;p&gt;El setup tomó tiempo. Diseñar el flujo, documentar las skills, conectar los MCPs, probar cada etapa. Pero fue una inversión de arquitectura, no de operación.&lt;/p&gt;

&lt;p&gt;Hoy el costo de publicar un post es una fracción de lo que era antes. Y lo más importante: el costo de &lt;em&gt;no&lt;/em&gt; publicar subió — porque el sistema está ahí, esperando ideas.&lt;/p&gt;

&lt;p&gt;Si estás construyendo una marca personal técnica y tienes el mismo problema que yo tenía, la pregunta no es si deberías automatizar tu flujo de contenido. La pregunta es cuánto más vas a esperar para hacerlo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;¿Tienes preguntas sobre cómo armar algo similar? Cuéntame en los comentarios o encuéntrame en LinkedIn.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>iacomoherramienta</category>
      <category>cafyproductividad</category>
      <category>integracionesysistem</category>
    </item>
    <item>
      <title>Cómo rediseñamos un backend completo sin detener la operación</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Fri, 20 Mar 2026 15:00:25 +0000</pubDate>
      <link>https://forem.com/davidcuy/como-redisenamos-un-backend-completo-sin-detener-la-operacion-jl3</link>
      <guid>https://forem.com/davidcuy/como-redisenamos-un-backend-completo-sin-detener-la-operacion-jl3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7u6tjaavrazb99t8ghq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl7u6tjaavrazb99t8ghq.jpg" alt="cover" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  El sistema llevaba cinco días en producción cuando llegué
&lt;/h2&gt;

&lt;p&gt;No hubo inducción. No hubo documentación. Hubo reuniones de crisis a puerta cerrada y un silencio incómodo cada vez que preguntaba qué estaba pasando.&lt;/p&gt;

&lt;p&gt;Mi rol formal era líder de automatización de QA. Pero conforme me fui involucrando en el contexto, quedó claro que el problema no era de calidad de pruebas. Era de arquitectura.&lt;/p&gt;

&lt;p&gt;El motor principal de base de datos era DynamoDB, una base de datos NoSQL de AWS diseñada para patrones de acceso por clave, alta escala y lecturas/escrituras predecibles. Nada de eso se había aprovechado. Había sido usada como si fuera una base de datos relacional: con relaciones entre entidades, consultas complejas, joins implícitos en código de aplicación y una cantidad notable de lógica que dependía de estructuras que DynamoDB simplemente no garantiza.&lt;/p&gt;

&lt;p&gt;El sistema era técnicamente funcional. Pero estaba construido sobre una base que haría que cada nueva funcionalidad costara el doble de esfuerzo del necesario.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué es difícil cuando el sistema ya está en producción
&lt;/h2&gt;

&lt;p&gt;Aquí está el problema real: no era un prototipo. Había usuarios reales. Había operaciones financieras activas. No había opción de "apagar y rehacer".&lt;/p&gt;

&lt;p&gt;Esto es lo que hace diferente rediseñar un backend en producción versus construir uno nuevo:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No puedes romper lo que ya funciona.&lt;/strong&gt; Aunque lo que funciona sea frágil, es lo que está sosteniendo la operación. Cada cambio tiene un costo real, no solo técnico.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El conocimiento está disperso.&lt;/strong&gt; El equipo original tomó decisiones bajo presión. Parte de la lógica de negocio estaba en la cabeza de personas que ya no estaban disponibles, o directamente en el comportamiento del sistema que nadie había documentado.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El tiempo no se detiene.&lt;/strong&gt; Mientras el equipo de refactorización trabaja en el backend nuevo, los usuarios del sistema actual siguen operando. Cualquier inconsistencia entre ambos mundos se convierte en un problema de datos.&lt;/p&gt;

&lt;p&gt;La decisión que se tomó desde dirección fue sensata en ese contexto: dividir en dos equipos. Uno mantendría el sistema actual operando. El otro construiría el backend nuevo desde cero. La empresa además hizo un freeze de nuevas altas para reducir la presión de crecimiento mientras se estabilizaba.&lt;/p&gt;

&lt;p&gt;Me asignaron al equipo de soporte.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendí estando del lado que nadie quiere estar
&lt;/h2&gt;

&lt;p&gt;Mantener un sistema que sabes que va a ser reemplazado es una experiencia particular. No es glamoroso. No es lo que uno imagina cuando piensa en "trabajo técnico interesante".&lt;/p&gt;

&lt;p&gt;Pero fue donde aprendí las reglas de negocio reales.&lt;/p&gt;

&lt;p&gt;Cada bug que se destraba en soporte es una historia: por qué ese flujo existe, qué caso borde representa, qué decisión de negocio hay detrás. Con el tiempo, esa acumulación de contexto se vuelve un activo valioso, aunque en el momento no se sienta así.&lt;/p&gt;

&lt;p&gt;Cuando finalmente pude moverme al equipo de refactorización, mi jefe fue directo:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Te tocará tomar el toro por los cuernos."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Y sí. Así fue.&lt;/p&gt;

&lt;h2&gt;
  
  
  La migración de DynamoDB a PostgreSQL
&lt;/h2&gt;

&lt;p&gt;Lo más común en la industria es lo contrario: empresas que migran de bases de datos relacionales a soluciones NoSQL para escalar. Hacerlo al revés tiene sus propios retos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El modelo mental es diferente.&lt;/strong&gt; DynamoDB te obliga a pensar en patrones de acceso primero. PostgreSQL te da flexibilidad para consultar desde múltiples ángulos. Migrar el modelo de datos implicó entender qué estructura tenía el dato en Dynamo, cómo se estaba usando en realidad, y qué forma debería tener en un esquema relacional bien diseñado.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Los datos no se migran solos.&lt;/strong&gt; Hubo que escribir scripts de transformación, validar integridad, manejar datos inconsistentes que el sistema anterior había aceptado sin queja. Los bordes del modelo de DynamoDB habían permitido guardar cosas que PostgreSQL rechazaría con una restricción de llave foránea o una constraint de unicidad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El release fue nocturno.&lt;/strong&gt; Obviamente. Los releases importantes siempre son nocturnos. Hubo problemas. Siempre hay problemas. Migramos datos, apagamos servidores, movimos el switch, prendimos el nuevo sistema, y empezamos a leer logs con la misma intensidad con la que se lee un mensaje importante que tardó en llegar.&lt;/p&gt;

&lt;p&gt;Funcionó. No de forma perfecta en las primeras horas, pero funcionó.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lecciones que me quedaron
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. El contexto de negocio no está en el código.&lt;/strong&gt; Está en los tickets de soporte, en las conversaciones de emergencia, en los edge cases que nadie documentó. Si tienes la oportunidad de estar cerca de la operación antes de rediseñar algo, no la desperdicies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. DynamoDB no es una base de datos relacional.&lt;/strong&gt; Parece obvio. No siempre lo es cuando hay presión de tiempo y el equipo no tiene experiencia previa con la tecnología. Antes de elegir una base de datos, la pregunta correcta no es "¿qué bases de datos conocemos?" sino "¿qué patrones de acceso tenemos?".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Dividir el equipo fue la decisión correcta.&lt;/strong&gt; Intentar mantener la operación y construir el nuevo sistema con el mismo equipo y al mismo tiempo es una receta para hacer las dos cosas mal. La separación de responsabilidades aplica también a nivel organizacional.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. La estabilidad no siempre se ve bien desde adentro.&lt;/strong&gt; Hubo un momento donde el sistema estaba "estable". No era bonito. No era lo que queríamos. Pero estaba sosteniendo la operación. A veces eso es suficiente para avanzar al siguiente paso.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Los releases nocturnos tienen sus propios patrones de falla.&lt;/strong&gt; Cansancio, presión, menor visibilidad del impacto real. Tener runbooks claros, criterios de rollback definidos de antemano y una comunicación fluida en el equipo vale más que cualquier optimismo previo al deploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;Rediseñar un backend en producción no es solo un ejercicio técnico. Es una operación que requiere contexto de negocio, coordinación de equipos, gestión de datos sucios y tolerancia a la ambigüedad.&lt;/p&gt;

&lt;p&gt;La tecnología en sí, migrar de DynamoDB a PostgreSQL en este caso, fue la parte más predecible del proceso. Lo que realmente definió el resultado fue haber entendido el sistema antes de cambiarlo.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;La arquitectura no empieza en el diseño. Empieza en entender qué problema está resolviendo el sistema actual, aunque ese sistema esté mal construido.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>construyendosistemas</category>
      <category>decisionesdearquitec</category>
      <category>integracionesysistem</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Vibe coding funciona. Pero primero necesitas saber diseño de software</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Fri, 20 Mar 2026 03:25:20 +0000</pubDate>
      <link>https://forem.com/davidcuy/vibe-coding-funciona-pero-primero-necesitas-saber-diseno-de-software-2jgk</link>
      <guid>https://forem.com/davidcuy/vibe-coding-funciona-pero-primero-necesitas-saber-diseno-de-software-2jgk</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmah2etdp1s2yjaetx9n1.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmah2etdp1s2yjaetx9n1.jpg" alt="cover" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Tengo un blog. Escribo en Sanity porque me gusta la experiencia, tengo control total del contenido y se integra perfectamente con mi portafolio. El problema es que también quiero publicar en Dev.to y Hashnode — donde vive buena parte de la comunidad técnica.&lt;/p&gt;

&lt;p&gt;Durante un tiempo lo hacía a mano. Terminaba el post en Sanity, copiaba el markdown, abría Dev.to, pegaba, ajustaba los tags, publicaba. Repetía el proceso en Hashnode. Si después corregía algo en Sanity, volvía a hacer todo de nuevo.&lt;/p&gt;

&lt;p&gt;No tardé mucho en decidir que eso no era sostenible.&lt;/p&gt;

&lt;h2&gt;
  
  
  La idea: Sanity como hub de publicación
&lt;/h2&gt;

&lt;p&gt;Lo que quería era simple: escribir una sola vez en Sanity y que el resto pasara solo. Cuando publico un post, un webhook dispara una Lambda en AWS que toma el contenido y lo manda a todas las plataformas configuradas.&lt;/p&gt;

&lt;p&gt;El flujo completo es este:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Publico un post en Sanity CMS&lt;/li&gt;
&lt;li&gt;Sanity dispara un webhook POST /publish hacia API Gateway&lt;/li&gt;
&lt;li&gt;La Lambda valida la firma HMAC-SHA256 del webhook&lt;/li&gt;
&lt;li&gt;Consulta la GROQ API de Sanity para obtener el post completo&lt;/li&gt;
&lt;li&gt;Publica en cada plataforma configurada (Dev.to, Hashnode...)&lt;/li&gt;
&lt;li&gt;Devuelve 200 si todo salió bien, 207 si hubo éxitos parciales, 502 si todo falló&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Sencillo en papel. Pero la parte interesante está en cómo está estructurado el código.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué no usé un if-else gigante
&lt;/h2&gt;

&lt;p&gt;Mi primer instinto al implementar esto fue escribir algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;devto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# publicar en dev.to
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hashnode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# publicar en hashnode
&lt;/span&gt;&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# publicar en medium
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y funciona. Hasta que quieres agregar una cuarta plataforma, o cambiar la lógica de una sin tocar las otras, o escribir tests aislados para cada integración. En ese momento el if-else se convierte en un problema.&lt;/p&gt;

&lt;p&gt;Lo que apliqué fue el patrón Strategy: cada plataforma es un adapter independiente que implementa la misma interfaz. La Lambda no sabe qué plataforma está usando — solo llama a publish() y el adapter se encarga del resto.&lt;/p&gt;

&lt;p&gt;El contrato es mínimo e intencional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;abstractmethod&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PublishingAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blog_base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Construye el payload listo para enviar a la API de la plataforma.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="nd"&gt;@abstractmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blog_base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Publica o actualiza el post. Devuelve: action, platform, platform_id, url, title.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y el facade BlogPublisher es el punto de entrada único. Se instancia con el nombre de la plataforma y delega todo al adapter correspondiente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlogPublisher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_default_registry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;adapters.devto&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DevToAdapter&lt;/span&gt;
        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;adapters.hashnode&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HashnodeAdapter&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;devto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;DevToAdapter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hashnode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashnodeAdapter&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;adapter_kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_adapter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_registry&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;adapter_kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blog_base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_adapter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blog_base_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora agregar Medium o cualquier otra plataforma es crear un archivo nuevo, implementar la interfaz y registrarlo. Nada más cambia.&lt;/p&gt;

&lt;h2&gt;
  
  
  El handler: limpio porque la complejidad está en otro lado
&lt;/h2&gt;

&lt;p&gt;Con esa estructura, el handler de la Lambda queda legible. La lógica de orquestación es clara porque cada responsabilidad está donde debe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;publishers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blog_base_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;platform&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;publisher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;

&lt;span class="n"&gt;succeeded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;failed&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;207&lt;/span&gt;  &lt;span class="c1"&gt;# 207 Multi-Status
&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Published on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; platform(s)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;results&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Si Dev.to falla pero Hashnode tiene éxito, la Lambda devuelve 207 con el detalle de cada resultado. El problema está localizado — no explota todo.&lt;/p&gt;

&lt;p&gt;Otros detalles que sumaron a la solidez del sistema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secretos desde AWS Secrets Manager&lt;/strong&gt;, no como env vars de la Lambda. Se cachean en memoria para no pagar el costo de una llamada a Secrets Manager en cada warm start.&lt;/li&gt;
&lt;li&gt;Sin requests: todo el HTTP usa urllib de la stdlib. Mantiene el zip de la Lambda liviano y sin dependencias externas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upsert en los adapters&lt;/strong&gt;: Dev.to busca por título exacto antes de crear — si ya existe, hace PUT. Hashnode busca por slug. Sin artículos duplicados aunque el webhook se dispare dos veces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Conversión de Portable Text a Markdown&lt;/strong&gt; implementada a mano en utils/portable_text.py, también sin dependencias externas.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Dónde entró el vibe coding — y dónde no
&lt;/h2&gt;

&lt;p&gt;Seré honesto: usé vibe coding en este proyecto. Bastante. Le pedí a Claude que generara los adapters de Dev.to y Hashnode, que implementara la conversión de Portable Text, que escribiera los tests de integración.&lt;/p&gt;

&lt;p&gt;Y funcionó bien. Rápido.&lt;/p&gt;

&lt;p&gt;Pero hay algo que el vibe coding no pudo hacer por mí: &lt;strong&gt;decidir la arquitectura&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Cuando llegué con "necesito publicar un post en varias plataformas", lo que obtuve en una primera iteración fue exactamente el if-else gigante que describí arriba. Funcionaba. Pero yo sabía que no era lo que quería — porque conozco el patrón Strategy, porque he visto ese tipo de código crecer y volverse imposible de mantener, porque entiendo qué significa extensibilidad en este contexto.&lt;/p&gt;

&lt;p&gt;La conversación que tuve para llegar al diseño final no fue "hazlo", fue "implementa un adapter para Dev.to que extienda esta clase abstracta, con estos métodos, siguiendo este contrato". Eso requería saber qué pedir.&lt;/p&gt;

&lt;p&gt;El vibe coding no reemplaza el criterio de diseño. Lo amplifica. Si sabes lo que quieres construir, puedes construirlo mucho más rápido. Si no lo sabes, aceleras hacia el lugar equivocado.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agregar una nueva plataforma son exactamente 4 pasos
&lt;/h2&gt;

&lt;p&gt;Eso es lo que me gusta de cómo quedó. Si mañana quiero publicar también en Medium (el adapter está como TODO por ahora), el proceso es:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Crear adapters/medium.py implementando PublishingAdapter&lt;/li&gt;
&lt;li&gt;Registrarlo en adapters/&lt;strong&gt;init&lt;/strong&gt;.py&lt;/li&gt;
&lt;li&gt;Añadirlo al registry en adapters/base.py&lt;/li&gt;
&lt;li&gt;Agregar el manejo de credenciales en lambda_function.py&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Nada más cambia. El handler no se toca. Los tests existentes siguen pasando.&lt;/p&gt;

&lt;p&gt;Ese nivel de extensibilidad no llegó por accidente. Llegó porque antes de escribir la primera línea, pensé en cómo quería que se viera el código cuando tuviera que cambiarlo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;No estoy en contra del vibe coding — lo uso seguido y me parece una herramienta genuinamente poderosa para acelerar la ejecución. Pero hay un prerequisito que a veces se omite en la conversación: funciona mejor cuando quien lo usa tiene criterio técnico para guiarlo.&lt;/p&gt;

&lt;p&gt;Patrones de diseño como Strategy, principios como separación de responsabilidades, decisiones como "la complejidad va en el adapter, no en el handler" — esas cosas no las aprende el modelo por ti. Las aprendes trabajando, equivocándote, refactorizando código que creciste.&lt;/p&gt;

&lt;p&gt;Y cuando las tienes internalizadas, el vibe coding se convierte en lo que debería ser: un multiplicador, no un atajo.&lt;/p&gt;

&lt;p&gt;El código completo está en &lt;a href="https://github.com/DavidCuy/portfolio/tree/main/backend-integrations" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; si quieres revisarlo.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;¿Usas vibe coding en tus proyectos? ¿Cómo lo combinas con las decisiones de arquitectura? Me interesa leer cómo otros están resolviendo esto.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>developerproductivit</category>
      <category>iacomoherramienta</category>
      <category>eventdriven</category>
      <category>aws</category>
    </item>
    <item>
      <title>Agregar un CDN parece obvio. Hasta que rompe tus deployments</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Wed, 18 Mar 2026 23:59:08 +0000</pubDate>
      <link>https://forem.com/davidcuy/agregar-un-cdn-parece-obvio-hasta-que-rompe-tus-deployments-5d4d</link>
      <guid>https://forem.com/davidcuy/agregar-un-cdn-parece-obvio-hasta-que-rompe-tus-deployments-5d4d</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fweairqu4r44kc40o8gwy.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fweairqu4r44kc40o8gwy.jpg" alt="cover" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Todos los equipos de infraestructura tienen una lista de cambios que parecen ganancias rápidas. Agregar un CDN, mejorar los tiempos de carga, y seguir adelante. Suena como una tarea de una tarde. No lo es.&lt;/p&gt;

&lt;p&gt;Hace poco pasé por una implementación de CDN en una plataforma web en producción. El tipo de proyecto donde el ticket se estima en horas y termina tomando días — no porque la tecnología sea complicada, sino porque el &lt;em&gt;sistema&lt;/em&gt; es más frágil de lo que nadie esperaba.&lt;/p&gt;

&lt;p&gt;Esto es lo que aprendí.&lt;/p&gt;

&lt;h2&gt;
  
  
  La promesa
&lt;/h2&gt;

&lt;p&gt;Los beneficios de un CDN son reales y están bien documentados.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La latencia cae significativamente.&lt;/strong&gt; Servir assets desde un nodo edge cercano al usuario, en lugar de enrutar cada request de vuelta al origen, es una ganancia directa — especialmente para usuarios distribuidos globalmente.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Obtenés visibilidad que antes no tenías.&lt;/strong&gt; Con un CDN frente a tu aplicación, empezás a ver los patrones de tráfico con una resolución diferente. Granjas de bots, picos geográficos inusuales, tasas de requests anómalas — cosas que antes eran invisibles se vuelven observables. Eso solo ya justifica el costo de implementación en muchos casos.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La resiliencia mejora.&lt;/strong&gt; El contenido cacheado sigue sirviéndose incluso cuando el origen tiene problemas. Para una plataforma con mucha carga de lectura, esto cambia los modos de falla de manera significativa.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué pasa en realidad cuando simplemente lo "encendés"
&lt;/h2&gt;

&lt;p&gt;Acá es donde vive la complejidad.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invalidación de caché en cada release.&lt;/strong&gt; Si los cache headers no están configurados correctamente, los usuarios verán estilos desactualizados, layouts rotos o JavaScript obsoleto después de cada deployment. Es el problema más común y el que genera más tickets de soporte. La solución requiere coordinar el pipeline de deployment con la estrategia de purga de caché del CDN — algo que rara vez existe antes de implementar un CDN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Políticas de seguridad que no sabías que faltaban.&lt;/strong&gt; Un CDN expone tus response headers a un escrutinio que una conexión directa al origen no hace. De repente, la ausencia de una Content Security Policy, headers HSTS faltantes, o cookies mal configuradas se vuelven visibles. Estos no eran problemas nuevos — siempre estuvieron ahí. El CDN simplemente los hizo aflorar. Eso es útil, pero es scope creep que nadie planeó.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cadenas de redirecciones que se rompen silenciosamente.&lt;/strong&gt; Los 301s y 302s que funcionaban bien en el origen pueden comportarse de manera inesperada cuando un CDN empieza a cachearlos o cortocircuitarlos. Una redirección que antes tomaba dos saltos ahora puede crear un loop, o no redirigir del todo si el CDN está sirviendo una respuesta cacheada de la ubicación incorrecta.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impacto en SEO por headers mal configurados.&lt;/strong&gt; Las canonical tags, los cache-control headers para crawlers y el manejo correcto de variantes de URL (con/sin trailing slash, www vs. sin www) necesitan configurarse explícitamente. Si se hacen mal, se pueden introducir problemas de contenido duplicado o impedir que los motores de búsqueda indexen las páginas correctas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué los cambios de infraestructura revelan debilidades en la arquitectura
&lt;/h2&gt;

&lt;p&gt;Esta es la parte que me parece genuinamente interesante.&lt;/p&gt;

&lt;p&gt;Cuando el CDN empezó a comportarse mal en staging, el instinto fue culpar a la configuración del CDN. Pero la mayoría de los problemas los rastreamos hasta issues preexistentes en la capa de aplicación: assets sin nombres de archivo versionados, headers que nunca se setearon porque "el origen se encargaba de eso", lógica de redirecciones dispersa en múltiples lugares sin una única fuente de verdad.&lt;/p&gt;

&lt;p&gt;El CDN no creó esos problemas. Los hizo imposibles de ignorar.&lt;/p&gt;

&lt;p&gt;Es un patrón que he visto repetidamente. Introducir una nueva capa de infraestructura — ya sea un CDN, un API gateway, un service mesh o incluso un load balancer — no solo cambia cómo fluye el tráfico. Cambia &lt;em&gt;lo que podés ver&lt;/em&gt; sobre tu sistema. Y lo que podés ver siempre revela cosas que estaban escondidas.&lt;/p&gt;

&lt;p&gt;Un CDN bien implementado mejora la experiencia del usuario. Uno mal implementado la degrada de maneras difíciles de diagnosticar, porque los síntomas (estilos rotos, contenido incorrecto, redirects fallidos) parecen bugs de aplicación, no problemas de infraestructura.&lt;/p&gt;

&lt;h2&gt;
  
  
  Qué fue lo que realmente funcionó
&lt;/h2&gt;

&lt;p&gt;El enfoque que estabilizó la implementación:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nombres de archivo de assets versionados desde el pipeline de build.&lt;/strong&gt; Cada archivo JS y CSS recibe un hash de contenido en su nombre. El CDN puede cachear agresivamente con TTLs largos, y la invalidación de caché deja de ser una preocupación en los deployments. Las URLs de los archivos viejos simplemente dejan de ser solicitadas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Políticas de caché explícitas por patrón de path.&lt;/strong&gt; Assets estáticos: TTL largo, inmutables. Páginas HTML: TTL corto o sin caché, siempre revalidadas. Rutas de API: bypasseadas completamente. Esto requirió mapear toda la superficie de routing de la aplicación antes de tocar la configuración del CDN.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Un paso de purga de caché en el CI/CD.&lt;/strong&gt; Después de cada deployment a producción, el pipeline dispara una purga de los paths que sirven HTML vía la API del CDN. Es simple de implementar, pero requiere tratarlo como parte del proceso de deployment, no como algo que se agrega después.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Headers de seguridad movidos al edge del CDN.&lt;/strong&gt; CSP, HSTS, X-Frame-Options — configurados una sola vez a nivel CDN en lugar de por aplicación. Más fácil de auditar, consistente en todos los orígenes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lecciones aprendidas
&lt;/h2&gt;

&lt;p&gt;La lección operativa es directa: no estimes una implementación de CDN mirando el CDN. Estimala mirando qué tan preparada está tu aplicación para recibirlo.&lt;/p&gt;

&lt;p&gt;La lección más profunda tiene que ver con lo que realmente son los cambios de infraestructura "simples". No son simples. Son &lt;em&gt;familiares&lt;/em&gt;. Y esa familiaridad es lo que los hace peligrosos. Los ingenieros los subestiman porque han visto funcionar CDNs antes. Pero los han visto funcionar en otros contextos, sobre otras aplicaciones, con otros supuestos implícitos.&lt;/p&gt;

&lt;p&gt;En ingeniería, "esto ya lo hicimos antes" no es lo mismo que "esto va a ser sencillo".&lt;/p&gt;

&lt;p&gt;El cambio de infraestructura fue exitoso. La experiencia del usuario mejoró de manera medible. Pero lo que voy a recordar de este proyecto no son los números de performance — sino la lista de cosas que encontramos en el camino que no tenían nada que ver con CDNs y sí mucho con cómo el sistema había crecido sin un dueño claro para algunos de sus comportamientos fundamentales.&lt;/p&gt;

&lt;p&gt;Esa lista se convirtió en un backlog. Ese backlog se convirtió en un sistema mejor.&lt;/p&gt;

</description>
      <category>decisionesdearquitec</category>
      <category>construyendosistemas</category>
      <category>arquitecturacloud</category>
      <category>integracionesysistem</category>
    </item>
    <item>
      <title>Cómo la IA Finalmente Me Ayudó a Lanzar el Blog que Venía Postergando desde 2024</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Tue, 17 Mar 2026 17:35:19 +0000</pubDate>
      <link>https://forem.com/davidcuy/como-la-ia-finalmente-me-ayudo-a-lanzar-el-blog-que-venia-postergando-desde-2024-6c7</link>
      <guid>https://forem.com/davidcuy/como-la-ia-finalmente-me-ayudo-a-lanzar-el-blog-que-venia-postergando-desde-2024-6c7</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqlbngbl593a6ad3ep7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpqlbngbl593a6ad3ep7x.png" alt="cover" width="800" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Todo ingeniero tiene un cementerio de side projects. En el mío había un blog. Dos veces.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;La primera vez que pensé en crear un blog fue a principios de 2024. Tenía cosas que decir — cosas reales, de proyectos reales. Decisiones de arquitectura que salieron mal. Patrones serverless que se veían perfectos en la pizarra y se desmoronaban en producción. Las historias de guerra que uno acumula después de años construyendo sistemas de los que otros dependen.&lt;/p&gt;

&lt;p&gt;Pero entonces: arrancó un nuevo sprint. Un cliente necesitaba algo urgente. Una migración de infraestructura no podía esperar. Y el borrador del blog se quedó en una nota de Notion, acumulando polvo digital, junto a otros tres proyectos de "ya lo retomo cuando pueda".&lt;/p&gt;

&lt;p&gt;Esto no pasó una vez. Pasó dos. Dos intentos. Dos borradores abandonados. El mismo patrón cada vez: dos pasitos para adelante, luego la vida real aparecía y cerraba silenciosamente la pestaña.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Problema Real No Era el Tiempo. Era la Energía de Arranque.
&lt;/h2&gt;

&lt;p&gt;Lo que eventualmente entendí fue esto: no es que &lt;em&gt;no tuviera tiempo&lt;/em&gt;. Era que el costo de sentarme a producir algo listo para publicar era suficientemente alto como para que siempre perdiera contra cualquier otra cosa que compitiera por mi atención.&lt;/p&gt;

&lt;p&gt;Escribir un post implicaba: redactar, editar, encontrar o crear una imagen, darle formato para la web, pensar en SEO, publicarlo, y luego repetir todo eso para cada plataforma donde quería que viviera. Cada uno de esos pasos es pequeño por separado. Juntos, forman una pared.&lt;/p&gt;

&lt;p&gt;Y si eres alguien que diseña sistemas profesionalmente, sabes exactamente qué pasa cuando la fricción en un proceso es demasiado alta: la gente lo esquiva o lo abandona.&lt;/p&gt;

&lt;p&gt;Yo estaba esquivando mi propio blog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entonces Decidí Tratarlo Como un Problema de Ingeniería
&lt;/h2&gt;

&lt;p&gt;En 2025 finalmente construí el blog — pero lo encaré de forma diferente. En lugar de buscar una fuerza de voluntad que no tenía, reduje la energía de arranque.&lt;/p&gt;

&lt;p&gt;El blog en sí fue construido principalmente con &lt;strong&gt;vibe coding usando Claude Code&lt;/strong&gt;. No voy a pretender que escribí cada línea desde cero. Tenía una visión clara de lo que quería: un sitio de portafolio rápido y limpio, con sección de blog, soporte de internacionalización, Sanity CMS en el backend, y algo que se sintiera como &lt;em&gt;yo&lt;/em&gt; y no como un template genérico. Claude Code me ayudó a pasar de idea a implementación funcionando mucho más rápido de lo que hubiera podido solo. Yo guié el proceso, revisé el código, tomé las decisiones — pero no me quedé bloqueado en los detalles de implementación.&lt;/p&gt;

&lt;p&gt;¿Las imágenes? Generadas con &lt;strong&gt;Gemini (Nano Banana Pro)&lt;/strong&gt;. Escribo un prompt descriptivo para cada post, enfocado en un estilo de ilustración tech limpio, y obtengo un visual que le hace justicia al artículo sin tener que pasar horas en Figma ni buscar stock photos que "más o menos" sirvan.&lt;/p&gt;

&lt;p&gt;El contenido — incluyendo este post — está escrito &lt;em&gt;con&lt;/em&gt; asistencia de IA. No generado de forma automática y publicado a ciegas, sino redactado, refinado y moldeado con la IA como compañero de pensamiento. Las ideas son mías. Las experiencias son mías. La IA me ayuda a sacarlas de la cabeza y llevarlas a la página más rápido.&lt;/p&gt;

&lt;h2&gt;
  
  
  De Qué Va a Tratar Este Blog
&lt;/h2&gt;

&lt;p&gt;Más allá de la historia de cómo fue construido, quiero ser claro sobre lo que estoy haciendo acá.&lt;/p&gt;

&lt;p&gt;Este blog va a cubrir cosas que realmente viví durante mi carrera como Cloud Architect y Software Engineer. Eso incluye:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decisiones de arquitectura y los trade-offs reales que hay detrás&lt;/li&gt;
&lt;li&gt;Sistemas serverless y event-driven — cuándo funcionan y cuándo no&lt;/li&gt;
&lt;li&gt;Integrar sistemas legacy sin romper producción&lt;/li&gt;
&lt;li&gt;Diseño de SaaS multi-tenant en AWS&lt;/li&gt;
&lt;li&gt;Liderazgo técnico y cómo explicar arquitectura a personas que no son arquitectos&lt;/li&gt;
&lt;li&gt;Y sí, alguna que otra anécdota del campo — porque ciertas lecciones solo quedan cuando vienen con una historia&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El objetivo no es ser un sitio de tutoriales. Hay miles de esos. El objetivo es compartir el &lt;em&gt;razonamiento&lt;/em&gt; detrás de las decisiones — el por qué, no solo el cómo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Un Detalle de Ingeniería Más: El Pipeline de Publicación Cruzada
&lt;/h2&gt;

&lt;p&gt;Porque al parecer soy incapaz de resolver un problema sin construir también un sistema alrededor de él:&lt;/p&gt;

&lt;p&gt;Armé un pipeline para que cuando publique un post aquí, se replique automáticamente a &lt;strong&gt;Hashnode&lt;/strong&gt; y &lt;strong&gt;dev.to&lt;/strong&gt;. Escribir una vez, publicar en todas partes.&lt;/p&gt;

&lt;p&gt;Si eres desarrollador, ya sabes por qué esto importa. La mejor estrategia de distribución de contenido es la que realmente vas a mantener. Copiar y reformatear posts manualmente en tres plataformas distintas es exactamente el tipo de fricción que mató mis intentos anteriores. Así que lo eliminé.&lt;/p&gt;

&lt;p&gt;El sistema maneja las diferencias de formato entre plataformas, propaga el contenido, y me deja enfocarme en escribir en lugar de en la logística de publicación.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Punto Principal
&lt;/h2&gt;

&lt;p&gt;No estoy escribiendo este post para decirte que la IA es mágica. No lo es. Es una herramienta — y como cualquier herramienta, su valor depende completamente de cómo la uses.&lt;/p&gt;

&lt;p&gt;Lo que la IA hizo por mí fue bajar el costo de empezar. No reemplazó el criterio, la experiencia ni las ideas. Eliminó la barrera de energía de arranque que había mantenido un blog a medio terminar en mi cementerio de proyectos por más de un año.&lt;/p&gt;

&lt;p&gt;Para ingenieros en particular, creo que esta es la aplicación más subestimada de estas herramientas ahora mismo. No reemplazar tu trabajo central — sino eliminar la fricción circundante que te impide lanzar las cosas que realmente te importan.&lt;/p&gt;

&lt;p&gt;El blog existe ahora. Lo estás leyendo.&lt;/p&gt;

&lt;p&gt;Eso ya es suficiente prueba de concepto para mí.&lt;/p&gt;

</description>
      <category>caféyproductividad</category>
      <category>iacomoherramienta</category>
    </item>
    <item>
      <title>Por qué pusimos un CDN frente a nuestro balanceador de carga (y por qué las cookies fueron el verdadero problema)</title>
      <dc:creator>David De Los Santos Cuy Sánchez</dc:creator>
      <pubDate>Fri, 13 Mar 2026 05:50:07 +0000</pubDate>
      <link>https://forem.com/davidcuy/por-que-pusimos-un-cdn-frente-a-nuestro-balanceador-de-carga-y-por-que-las-cookies-fueron-el-4mfh</link>
      <guid>https://forem.com/davidcuy/por-que-pusimos-un-cdn-frente-a-nuestro-balanceador-de-carga-y-por-que-las-cookies-fueron-el-4mfh</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qj5hh0wuzaw2knkj37l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8qj5hh0wuzaw2knkj37l.png" alt="cover" width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Agregar un CDN parece algo obvio. Hasta que es lo único que se interpone entre tú y un deployment roto…&lt;/p&gt;

&lt;h2&gt;
  
  
  El contexto
&lt;/h2&gt;

&lt;p&gt;Estábamos en medio de dos cambios en paralelo: un rediseño completo del sitio web y una migración de servidores. Ambos estaban planeados, con scope definido, y avanzando sin problemas... hasta que notamos que un segmento de usuarios empezó a tener fallas después de la migración. No todos. No de forma consistente. Pero suficiente para ser un problema real.&lt;/p&gt;

&lt;p&gt;El culpable: las cookies.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema
&lt;/h2&gt;

&lt;p&gt;El servidor anterior había acumulado cookies con el tiempo. El nuevo servidor introdujo las suyas propias. Cuando un usuario llegaba en medio de la migración —todavía cargando las cookies antiguas y recibiendo las nuevas del sitio rediseñado— el payload combinado superaba el tamaño máximo aceptado por el balanceador de carga: &lt;strong&gt;16 KB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Este límite no es arbitrario. Es una restricción dura que aplican la mayoría de los balanceadores de carga y CDNs a nivel HTTP. Una vez que lo cruzas, las solicitudes empiezan a fallar silenciosamente o a ser rechazadas.&lt;/p&gt;

&lt;p&gt;Para hacerlo más complejo: el dominio no estaba bajo nuestro control directo. Apuntaba al balanceador mediante un CNAME de un proveedor DNS externo. Eso significaba que no podíamos cambiar el ruteo sin coordinar con terceros, ni tocar la configuración del balanceador directamente.&lt;/p&gt;

&lt;h2&gt;
  
  
  Por qué era difícil
&lt;/h2&gt;

&lt;p&gt;Lo complicado no era el fix técnico. Era la restricción principal: &lt;strong&gt;no podíamos permitirnos ninguna afectación al usuario durante el cambio&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;El error too_many_redirects solo aparecía para usuarios con payloads de cookies grandes —sesiones específicas, navegadores específicos, rutas específicas entre el sistema viejo y el nuevo—. Un cutover total habría expuesto a todos al riesgo. Y hacer rollback tampoco era una opción limpia, porque la migración ya estaba parcialmente en vivo.&lt;/p&gt;

&lt;p&gt;Necesitábamos una solución que:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Interceptara las solicitudes antes de llegar al balanceador&lt;/li&gt;
&lt;li&gt;Limpiara las cookies basándose en una whitelist (conservando solo lo necesario)&lt;/li&gt;
&lt;li&gt;Solo modificara el request, sin disparar redirects&lt;/li&gt;
&lt;li&gt;Pudiera desplegarse sin tocar el registrador del dominio ni el balanceador&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  La solución
&lt;/h2&gt;

&lt;p&gt;Introdujimos una capa de CDN entre el DNS y el balanceador, y cambiamos el CNAME para que apuntara al CDN en lugar de ir directamente al balanceador.&lt;/p&gt;

&lt;p&gt;En el edge del CDN, adjuntamos una &lt;strong&gt;función Lambda@Edge&lt;/strong&gt; que se ejecutaba en cada solicitud entrante. Su trabajo era simple: leer las cookies, aplicar la whitelist, eliminar todo lo demás, y reenviar el request limpio hacia abajo.&lt;/p&gt;

&lt;p&gt;Sin redirects. Sin pérdida de sesión para las cookies en la whitelist. Sin cambios en el código de la aplicación ni en la configuración del balanceador.&lt;/p&gt;

&lt;p&gt;La arquitectura final quedó así:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[DNS → Firewall] → CDN → (Lambda@Edge) → Balanceador → Cluster&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;El CDN nos dio la capa de ejecución en el edge que necesitábamos. La función Lambda@Edge nos dio control preciso a nivel de request. Y como solo modificábamos los headers del request —nunca la respuesta—, el loop de too_many_redirects nunca se disparó.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo que aprendimos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Entiende qué está bajo tu control y qué no.&lt;/strong&gt; El dominio estaba fuera de nuestro registro. El balanceador tenía límites fijos. Las cookies existentes de los usuarios no eran negociables. Trabajar dentro de esas restricciones moldeó cada decisión.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;El CDN no era el objetivo, era el habilitador.&lt;/strong&gt; No lo agregamos por rendimiento ni caché. Lo agregamos porque nos daba una capa programable exactamente donde la necesitábamos: entre el usuario y el balanceador.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Whitelist es más seguro que blacklist.&lt;/strong&gt; En lugar de intentar identificar cuáles cookies causaban el desbordamiento y eliminar solo esas, invertimos el enfoque: definir qué debe sobrevivir, eliminar todo lo demás. Más simple de mantener, más seguro de desplegar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Los cambios sin downtime son decisiones de arquitectura, no trucos de deployment.&lt;/strong&gt; La razón por la que pudimos hacer este cambio sin afectar a los usuarios no fue suerte: fue que la arquitectura nos permitió insertar una nueva capa sin modificar nada de lo que estaba debajo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusión
&lt;/h2&gt;

&lt;p&gt;La buena arquitectura no siempre se trata del diseño más elegante. A veces se trata de entender cómo interactúan tus sistemas entre sí, identificar dónde tienes apalancamiento real, y elegir el recurso correcto según las restricciones que tienes hoy —no las que quisieras tener—.&lt;/p&gt;

&lt;p&gt;Un CDN y una pequeña función Lambda resolvieron lo que parecía un problema de cookies. Pero lo que realmente resolvieron fue un problema de control.&lt;/p&gt;

</description>
      <category>arquitecturacloud</category>
      <category>decisionesdearquitec</category>
    </item>
  </channel>
</rss>
