<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Blog de Daniel Hidalgo</title><description>Arquitectura backend, diseño de sistemas y liderazgo técnico.</description><link>https://hrdi.dev/</link><language>es</language><item><title>Un setup de IA, dos mundos: cómo uso agentes de IA como Tech Lead y como emprendedor</title><link>https://hrdi.dev/es/blog/ai-setup-work-and-business/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/ai-setup-work-and-business/</guid><description>El setup real que uso todos los días en mi máquina, cómo se adapta entre mi trabajo como Tech Lead y mi propia empresa, y las reglas que hacen que la IA sea útil en lugar de peligrosa.</description><pubDate>Fri, 23 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Antes escribí sobre &lt;a href=&quot;/es/blog/ai-developer-workflow/&quot;&gt;qué funciona de verdad con la IA en el flujo de trabajo del desarrollador&lt;/a&gt;. Ese artículo trataba de principios. Este trata del setup en sí: las herramientas que corren en mi máquina todos los días, cómo el mismo setup sirve a dos contextos muy distintos y las reglas que sigo para que siga siendo una herramienta y no un riesgo.&lt;/p&gt;
&lt;p&gt;Los dos contextos son mi trabajo como Tech Lead y Arupo, la empresa que estoy construyendo como socio, además de algunos proyectos personales. Restricciones muy diferentes, un solo ingeniero, una sola laptop.&lt;/p&gt;
&lt;h2&gt;La base: configurar una vez, usar en todos lados&lt;/h2&gt;
&lt;p&gt;El error más grande que veo es tratar cada herramienta de IA como una isla: unas instrucciones en un editor, otros hábitos en otro, nada compartido entre ellos. Yo fui por el camino contrario. Hay una sola base, y todos los agentes se apoyan en ella.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Un harness compartido.&lt;/strong&gt; Uso &lt;a href=&quot;https://github.com/Gentleman-Programming/gentle-ai&quot;&gt;Gentle AI&lt;/a&gt; para darle a cada agente el mismo flujo de trabajo, la misma persona y las mismas reglas de revisión. Está configurado para Claude Code, Codex, OpenCode, Gemini y Copilot. Abra el agente que abra, trabaja igual.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memoria persistente.&lt;/strong&gt; &lt;a href=&quot;https://github.com/Gentleman-Programming/engram&quot;&gt;Engram&lt;/a&gt; guarda decisiones, correcciones de bugs, descubrimientos y preferencias entre sesiones y entre agentes. Cuando vuelvo a un proyecto después de dos semanas, el agente no arranca de cero, y yo tampoco.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un mapa del código.&lt;/strong&gt; Codegraph indexa cada repositorio para que un agente entienda la estructura sin leer archivo por archivo. Eso importa cuando el código es grande y la ventana de contexto no lo es.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentación actualizada.&lt;/strong&gt; Context7 les da a los agentes la documentación vigente de las librerías que uso, en lugar de la versión que memorizaron durante el entrenamiento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Una segunda terminal de IA.&lt;/strong&gt; Pi, con Gentle Shell, es donde corro el trabajo orquestado más largo, con perfiles para distintos modelos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un guardián en los commits.&lt;/strong&gt; Un revisor previo al commit revisa los cambios antes de que entren.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un entorno cómodo.&lt;/strong&gt; Ghostty, Starship y los dotfiles de Gentleman. Parece cosmético, pero ahí se pasa el día entero.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ninguna de estas piezas es mágica por sí sola. El valor está en que están conectadas: las mismas reglas, la misma memoria, la misma forma de trabajar, sin importar el agente ni el proyecto.&lt;/p&gt;
&lt;h2&gt;Dos mundos en una sola máquina&lt;/h2&gt;
&lt;p&gt;Esta es la parte de la que casi nadie habla. Mi trabajo y mi empresa nunca deben mezclarse. Cuentas distintas, identidades distintas, reglas distintas sobre qué puede salir de la máquina.&lt;/p&gt;
&lt;p&gt;Por eso la separación es automática, por carpeta. Cuando entro a la carpeta de proyectos personales, se activan solos el perfil personal de IA y mi identidad personal de git y SSH. Cuando entro a la carpeta de trabajo, toman el control el perfil corporativo de IA y la identidad corporativa de git. No tengo que acordarme de cambiar, y ese es justamente el punto: una regla que hay que recordar es una regla que tarde o temprano se rompe.&lt;/p&gt;
&lt;p&gt;No es perfecto, y sé dónde están los huecos. La CLI de GitHub, por ejemplo, no cambia de cuenta por carpeta; usa una sola sesión global. Conocer los huecos de tu propio setup también es hacerte cargo de él.&lt;/p&gt;
&lt;h2&gt;En el trabajo: liderar dentro de las restricciones&lt;/h2&gt;
&lt;p&gt;Una empresa tiene reglas sobre herramientas y datos, y existen por buenas razones. Mi regla es simple: la forma de trabajar viaja conmigo, y las políticas de la empresa deciden qué herramientas puedo usar. Adapto el setup a esas políticas, nunca al revés.&lt;/p&gt;
&lt;p&gt;Pero el cambio más grande en el trabajo no es técnico. Es cómo uso mi tiempo como Tech Lead. Los agentes se llevan buena parte del trabajo mecánico: explorar un servicio desconocido, preparar el borrador de una migración, escribir los tests aburridos. Eso me deja más tiempo para lo que realmente le toca a un Tech Lead: decisiones de diseño, revisiones de código, destrabar al equipo, incidentes y postmortems.&lt;/p&gt;
&lt;p&gt;Hay algo que no cambia: &lt;strong&gt;la responsabilidad sigue siendo humana&lt;/strong&gt;. Un agente puede proponer un cambio en un servicio de producción. El que responde por él a las 3 de la mañana soy yo.&lt;/p&gt;
&lt;h2&gt;En Arupo: velocidad sin bajar el estándar&lt;/h2&gt;
&lt;p&gt;En mi propia empresa la restricción es la opuesta. No hay reglas corporativas, pero hay muy poco tiempo y un equipo muy chico. La IA es lo que permite que un equipo chico entregue como uno más grande, siempre que el estándar de calidad no baje.&lt;/p&gt;
&lt;p&gt;El mejor ejemplo es el sitio que estás leyendo. En unas pocas sesiones de trabajo pasó por:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un rediseño completo, con versiones en inglés y en español.&lt;/li&gt;
&lt;li&gt;Una limpieza de SEO.&lt;/li&gt;
&lt;li&gt;Endurecimiento de seguridad: una Content Security Policy generada en el build, con un chequeo que hace fallar el build si a un script inline le falta su entrada.&lt;/li&gt;
&lt;li&gt;Una migración de Astro 5 a Astro 7.&lt;/li&gt;
&lt;li&gt;Una revisión de accesibilidad para cumplir el contraste WCAG AA, y una auditoría de dependencias llevada a cero vulnerabilidades conocidas.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cada uno de esos cambios salió en su propio pull request, con deploys de preview, antes de llegar a producción. Los agentes escribieron la mayor parte del código. Yo tomé cada decisión que importaba: qué actualizar y qué dejar como estaba, qué trade-off de seguridad aceptar, cuándo algo estaba listo para mergear.&lt;/p&gt;
&lt;p&gt;El mismo setup corre mis otros proyectos: productos personales de finanzas y fitness, una tesis y los distintos repositorios detrás de Arupo.&lt;/p&gt;
&lt;h2&gt;El flujo que lo une todo&lt;/h2&gt;
&lt;p&gt;Las herramientas son la parte fácil. Lo que hace que esto funcione es el proceso que sigo con ellas, igual en los dos mundos:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Aclarar primero.&lt;/strong&gt; Si el pedido es ambiguo, el agente pregunta antes de escribir una sola línea.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dimensionar la tarea.&lt;/strong&gt; Los cambios chicos y bien entendidos se hacen directo. Los grandes se siguen con tareas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escribir la especificación.&lt;/strong&gt; Un cambio grande tiene su documento: los requisitos citados tal como los dije, una lista numerada de tareas y un registro de decisiones. Cualquiera, humano o agente, puede retomar desde ahí.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tests primero.&lt;/strong&gt; Cuando el comportamiento se puede testear, el test que falla va antes que la corrección.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commits chicos.&lt;/strong&gt; Porciones revisables, nunca un diff gigante que nadie puede revisar de verdad.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Revisión según el riesgo.&lt;/strong&gt; Cuando un cambio toca seguridad, datos, contratos o despliegues, lo mira un revisor independiente. Cuando no, no pago por una ceremonia que no necesito.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El merge lo hago yo.&lt;/strong&gt; Hacer push, abrir el pull request y mergear siguen siendo decisiones mías. Siempre.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Lo que (todavía) no funciona&lt;/h2&gt;
&lt;p&gt;Para ser honesto:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Las herramientas se rompen.&lt;/strong&gt; Algunas integraciones piden volver a autenticarse, otras no logran conectarse. Un setup tan conectado necesita mantenimiento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La memoria se desactualiza.&lt;/strong&gt; Una decisión guardada hace tres meses puede que ya no sea cierta. La memoria es contexto, no verdad.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Los agentes se equivocan con total seguridad.&lt;/strong&gt; Te dicen que un build pasa cuando no pasa, o inventan una API. Por eso cada afirmación necesita evidencia: la salida del test, el log del build, el diff.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reflexiones finales&lt;/h2&gt;
&lt;p&gt;La IA no me convirtió en otro ingeniero. Amplificó al que ya era. Los fundamentos que aprendí en ocho años, desde la infraestructura de red hasta liderar equipos de producto, son exactamente lo que me permite dirigir a estos agentes y atraparlos cuando se equivocan.&lt;/p&gt;
&lt;p&gt;La habilidad que importa ahora no es escribir rápido. Es saber qué pedir, reconocer cuándo la respuesta está mal y hacerse cargo del resultado. Esa parte sigue siendo, y va a seguir siendo, nuestra.&lt;/p&gt;
</content:encoded></item><item><title>Del monolito al BFF: cómo migramos de verdad sin quemarlo todo</title><link>https://hrdi.dev/es/blog/monolith-to-bff-clean-architecture/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/monolith-to-bff-clean-architecture/</guid><description>Un relato real de la migración de un backend monolítico a un patrón BFF con arquitectura limpia: las decisiones, los errores y los trade-offs de los que nadie te advierte.</description><pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Voy a contarte algo que todo ingeniero senior termina aprendiendo: nadie se despierta una mañana y decide migrar de un monolito por diversión. Lo haces porque el monolito está perjudicando activamente a tu equipo, tus deploys y tus clientes. Eso es exactamente lo que nos pasaba hace unos dos años, y quiero contarte cómo lo logramos en realidad: no la versión depurada de las charlas de conferencias, sino la real.&lt;/p&gt;
&lt;h2&gt;El monolito pedía ayuda a gritos&lt;/h2&gt;
&lt;p&gt;Nuestro monolito era una aplicación Node.js con Express que llevaba tres años creciendo. Empezó limpio. No se mantuvo así. Así se veían los síntomas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Miedo a desplegar&lt;/strong&gt;: un cambio en el módulo de pagos requería desplegar toda la aplicación. Un viernes por la tarde, un arreglo de CSS en el panel de administración tumbó el flujo de checkout. Fue un fin de semana muy divertido.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependencias enredadas&lt;/strong&gt;: el servicio de órdenes importaba directamente el repositorio de usuarios. El módulo de notificaciones conocía los niveles de inventario. Todo conocía a todo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cuellos de botella entre equipos&lt;/strong&gt;: tres squads trabajando sobre la misma base de código significaban conflictos de merge constantes y una cola de deploys que a veces se acumulaba durante días.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Techo de rendimiento&lt;/strong&gt;: no podíamos escalar el módulo de reportes de forma independiente del flujo de checkout en tiempo real. Ambos vivían en el mismo proceso, compitiendo por los mismos recursos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El monolito no era solo deuda técnica: era deuda organizacional. Y ahí es cuando sabes que llegó la hora.&lt;/p&gt;
&lt;h2&gt;Por qué BFF (y no solo &quot;microservicios&quot;)&lt;/h2&gt;
&lt;p&gt;La reacción instintiva es &quot;¡hagamos microservicios!&quot;. Pero los microservicios sin una estrategia clara de frontend solo mueven los espaguetis del backend a la capa de API. Teníamos tres clientes: una SPA web (Angular), una app móvil (React Native) y un dashboard interno de administración. Cada uno necesitaba formas de datos distintas, flujos de autenticación distintos y características de rendimiento distintas.&lt;/p&gt;
&lt;p&gt;Ahí entra Backend for Frontend. La idea es simple: cada cliente tiene su propio backend liviano que orquesta las llamadas a los servicios de dominio y da forma a la respuesta según las necesidades de ese cliente en particular.&lt;/p&gt;
&lt;p&gt;La clave fue esta: &lt;strong&gt;el BFF no es un servicio de dominio&lt;/strong&gt;. Tiene cero lógica de negocio. Es una capa de orquestación y transformación. En el momento en que pones reglas de negocio en un BFF, acabas de construir otro monolito con saltos de red adicionales.&lt;/p&gt;
&lt;h2&gt;Arquitectura hexagonal en los servicios de dominio&lt;/h2&gt;
&lt;p&gt;Para los servicios de dominio propiamente dichos (órdenes, inventario, usuarios, pagos), optamos por la arquitectura hexagonal. La razón: queríamos que la lógica de dominio estuviera completamente desacoplada de las preocupaciones de infraestructura. ¿Base de datos? Eso es un adapter. ¿Cola de mensajes? Adapter. ¿Pasarela de pagos externa? Adapter.&lt;/p&gt;
&lt;p&gt;Esta es la estructura que usamos en NestJS:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/
├── domain/
│   ├── entities/
│   │   └── order.entity.ts
│   ├── ports/
│   │   ├── order.repository.port.ts
│   │   └── payment-gateway.port.ts
│   └── use-cases/
│       └── create-order.use-case.ts
├── infrastructure/
│   ├── adapters/
│   │   ├── postgres-order.repository.ts
│   │   └── stripe-payment.gateway.ts
│   └── modules/
│       └── order.module.ts
└── application/
    └── bff/
        └── web-bff.controller.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La definición del port es limpia y agnóstica a la infraestructura:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// domain/ports/order.repository.port.ts
export interface OrderRepositoryPort {
  findById(id: string): Promise&amp;lt;Order | null&amp;gt;;
  save(order: Order): Promise&amp;lt;Order&amp;gt;;
  findByCustomer(customerId: string, pagination: PaginationParams): Promise&amp;lt;PaginatedResult&amp;lt;Order&amp;gt;&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Y el controller del BFF orquesta sin poseer lógica:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// application/bff/web-bff.controller.ts
@Controller(&apos;web/orders&apos;)
export class WebBffController {
  constructor(
    private readonly createOrderUseCase: CreateOrderUseCase,
    private readonly getCustomerUseCase: GetCustomerUseCase,
  ) {}

  @Post()
  async createOrder(@Body() dto: CreateOrderDto, @Req() req: AuthRequest) {
    const order = await this.createOrderUseCase.execute({
      customerId: req.user.id,
      items: dto.items,
      shippingAddress: dto.shippingAddress,
    });

    // Responsabilidad del BFF: dar forma a la respuesta para el cliente web
    return {
      orderId: order.id,
      status: order.status,
      estimatedDelivery: order.estimatedDelivery?.toISOString(),
      items: order.items.map(item =&amp;gt; ({
        name: item.productName,
        qty: item.quantity,
        total: formatCurrency(item.totalPrice),
      })),
    };
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fíjate: el BFF llama al use case y luego transforma la respuesta para el cliente web. El BFF móvil devolvería otra forma, quizás con menos datos para ahorrar ancho de banda, o con URLs de imágenes optimizadas para pantallas móviles.&lt;/p&gt;
&lt;h2&gt;La estrategia de migración: Strangler Fig&lt;/h2&gt;
&lt;p&gt;No hicimos una migración big bang. Así es como lo quemas todo. Usamos el patrón strangler fig, y así funcionó en la práctica:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;API Gateway como router&lt;/strong&gt;: pusimos un API Gateway (Kong) delante de todo. Al principio, el 100 % del tráfico iba al monolito.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extraer un dominio a la vez&lt;/strong&gt;: empezamos por el servicio de notificaciones porque era el más aislado y el de menor riesgo. Lo construimos como un servicio NestJS independiente con arquitectura hexagonal.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Período de doble escritura&lt;/strong&gt;: durante unas dos semanas, tanto el monolito como el nuevo servicio procesaron notificaciones. Comparamos las salidas. Así encontramos tres bugs en el servicio nuevo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cambio de ruta&lt;/strong&gt;: cuando tuvimos confianza, actualizamos el gateway para enrutar el tráfico de notificaciones al nuevo servicio. El código del monolito se quedó en su lugar, pero inactivo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repetir&lt;/strong&gt;: órdenes fue lo siguiente, luego inventario y por último pagos (el más aterrador, que dejamos para el final).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Toda la migración tomó unos ocho meses. No ocho días, no ocho semanas. Ocho meses.&lt;/p&gt;
&lt;h2&gt;Los errores que cometimos&lt;/h2&gt;
&lt;p&gt;Mentiría si dijera que todo salió sin problemas. Estos son los errores que nos costaron tiempo real:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Error 1: compartir la base de datos durante demasiado tiempo.&lt;/strong&gt; Al principio dejamos que el nuevo servicio de órdenes leyera de la base de datos del monolito. &quot;Lo separamos después&quot;, dijimos. Ese después tardó cuatro meses, y durante ese tiempo tuvimos un acoplamiento de esquema que echó por tierra la mitad del propósito de extraer el servicio.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Error 2: un BFF que hacía demasiado.&lt;/strong&gt; En la primera iteración, nuestro BFF web empezó a acumular lógica de negocio. &quot;Es solo una validación pequeña&quot;, dijo alguien. Luego otra. En un mes, el BFF tenía 40 líneas de reglas de negocio que debían vivir en el servicio de dominio. Tuvimos que refactorizarlas de vuelta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Error 3: no invertir en observabilidad desde el principio.&lt;/strong&gt; Con un monolito puedes seguir un request en un único flujo de logs. Con servicios distribuidos necesitas trazado distribuido desde el primer día. Agregamos OpenTelemetry en el mes tres. Debió ser el día uno.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Error 4: subestimar la consistencia de datos.&lt;/strong&gt; Pasar de una única transacción de base de datos a consistencia eventual entre servicios fue un cambio de mentalidad para todo el equipo. Tuvimos que implementar el patrón saga para el flujo orden-pago-inventario, y nos tomó tres iteraciones hacerlo bien.&lt;/p&gt;
&lt;h2&gt;Los trade-offs de los que nadie te habla&lt;/h2&gt;
&lt;p&gt;Seamos realistas sobre en qué te estás metiendo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;La complejidad operativa aumenta significativamente.&lt;/strong&gt; Ahora tienes 6 servicios en lugar de 1. Eso son 6 pipelines de CI/CD, 6 conjuntos de logs, 6 health checks, 6 cosas que pueden fallar de forma independiente.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El desarrollo local se complica.&lt;/strong&gt; Docker Compose pasó a ser obligatorio. Incorporar a un nuevo desarrollador pasó de &quot;clonar y ejecutar&quot; a un proceso de configuración de 45 minutos (que con el tiempo automatizamos).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La red ahora es un modo de falla.&lt;/strong&gt; Las llamadas entre servicios pueden agotar el tiempo de espera, fallar o devolver datos desactualizados. Necesitas lógica de reintentos, circuit breakers y estrategias de fallback en todas partes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Las pruebas cambian de raíz.&lt;/strong&gt; Los tests unitarios son fáciles. ¿Tests de integración entre servicios? Eso requiere contract testing (usamos Pact) y un entorno de staging adecuado.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pero esta es la otra cara:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Los equipos avanzan de forma independiente.&lt;/strong&gt; El squad de pagos despliega tres veces al día sin coordinarse con nadie.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El escalado es quirúrgico.&lt;/strong&gt; ¿Black Friday? Escala el servicio de órdenes y el de pagos. Deja tranquilo al BFF de administración.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Las fallas quedan aisladas.&lt;/strong&gt; El servicio de notificaciones estuvo caído 20 minutos el mes pasado. Las órdenes siguieron fluyendo. En la época del monolito, eso habría sido una caída total.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;La conclusión honesta&lt;/h2&gt;
&lt;p&gt;Si tu monolito funciona y tu equipo es pequeño, no migres. En serio. Un monolito bien estructurado le gana todos los días a un sistema distribuido mal implementado. Nosotros migramos porque teníamos un dolor real y medible, no porque los microservicios estuvieran de moda.&lt;/p&gt;
&lt;p&gt;Pero si estás sintiendo ese dolor (miedo a desplegar, cuellos de botella entre equipos, techos de escalado), entonces una migración bien pensada con BFF + arquitectura hexagonal + strangler fig es un enfoque probado en batalla. Solo entra con los ojos abiertos respecto a los trade-offs, invierte en observabilidad desde el primer día y, por el amor de todo lo bueno en la ingeniería, mantén la lógica de negocio fuera del BFF.&lt;/p&gt;
&lt;p&gt;La mejor arquitectura es la que le permite a tu equipo entregar con confianza. Todo lo demás son detalles.&lt;/p&gt;
</content:encoded></item><item><title>Por qué la mayoría de los sistemas backend fallan a escala (y los errores que he visto de primera mano)</title><link>https://hrdi.dev/es/blog/why-backend-systems-fail-at-scale/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/why-backend-systems-fail-at-scale/</guid><description>Siete fallos reales en producción que presencié, desde queries N+1 hasta cache stampedes, y las soluciones que realmente funcionaron. Sin teoría, solo cicatrices de batalla.</description><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Llevo más de seis años construyendo y escalando sistemas backend, y puedo decirte esto con absoluta seguridad: la mayoría de los sistemas no fallan por algún caso borde exótico. Fallan por errores bien conocidos que los equipos siguen repitiendo. No porque los ingenieros sean malos, sino porque estos problemas son invisibles con poco tráfico y solo se vuelven letales a escala.&lt;/p&gt;
&lt;p&gt;Estos son siete errores que he visto acabar con sistemas en producción, lo que pasó realmente y cómo los solucionamos. Cada uno de ellos viene de un proyecto real.&lt;/p&gt;
&lt;h2&gt;1. La query N+1 que nadie notó&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Qué pasó:&lt;/strong&gt; teníamos un endpoint que devolvía una lista de órdenes con sus productos asociados. Con 50 órdenes iba bien: 200 ms de tiempo de respuesta. Con 500 órdenes durante una venta relámpago, tardó 14 segundos y el CPU de la base de datos llegó al 98 %.&lt;/p&gt;
&lt;p&gt;El ORM hacía exactamente lo que le pedimos: cargar las órdenes y, para cada orden, cargar sus productos. Una query para las órdenes, 500 queries para los productos. N+1 clásico.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qué hicimos:&lt;/strong&gt; la solución inmediata fue el eager loading: una sola query con un JOIN. El tiempo de respuesta bajó a 180 ms sin importar la cantidad de órdenes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Antes: desastre N+1
const orders = await orderRepo.find({ where: { userId } });
// Esto dispara 1 query por orden para cargar los productos

// Después: una sola query cargando las relaciones
const orders = await orderRepo.find({
  where: { userId },
  relations: [&apos;items&apos;, &apos;items.product&apos;],
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pero la solución de fondo fue agregar logging de queries en staging con una alerta por umbral. Cualquier endpoint que ejecute más de 10 queries por request queda marcado automáticamente. Ahora detectamos los N+1 antes de que lleguen a producción.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lección:&lt;/strong&gt; los ORMs hacen que las queries N+1 sean fáciles de escribir y difíciles de detectar. Necesitas detección automatizada, no solo code reviews.&lt;/p&gt;
&lt;h2&gt;2. Falta de circuit breakers&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Qué pasó:&lt;/strong&gt; nuestro servicio de órdenes llamaba a una API de tarifas de envío de un tercero. Una tarde, esa API empezó a responder con timeouts de 30 segundos en lugar de sus habituales 200 ms. Cada request al flujo de checkout quedó en cola esperando las tarifas de envío. Se agotó el thread pool. Todo nuestro checkout cayó, no porque NUESTRO sistema fallara, sino porque falló el de otro.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qué hicimos:&lt;/strong&gt; implementamos circuit breakers con un patrón similar al Hystrix de Netflix (usamos &lt;code&gt;opossum&lt;/code&gt; en Node.js):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import CircuitBreaker from &apos;opossum&apos;;

const shippingBreaker = new CircuitBreaker(fetchShippingRates, {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000,
});

shippingBreaker.fallback(() =&amp;gt; ({
  rates: getCachedShippingRates(),
  isFallback: true,
}));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cuando la API de envíos falla, el circuito se abre y servimos tarifas en cache con una marca que le indica al frontend que son estimaciones. Los clientes siguen comprando. Conciliamos los costos de envío reales de forma asíncrona.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lección:&lt;/strong&gt; toda dependencia externa es un pasivo. Si no tienes una estrategia de fallback para cada una, has acoplado tu uptime al de ellos.&lt;/p&gt;
&lt;h2&gt;3. Base de datos compartida entre servicios&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Qué pasó:&lt;/strong&gt; dos servicios, órdenes e inventario, leían y escribían en la misma base de datos PostgreSQL. El servicio de inventario ejecutaba cada 5 minutos una query analítica pesada para los reportes de stock. En horas pico, esa query bloqueaba filas que el servicio de órdenes necesitaba actualizar. Las órdenes empezaron a fallar con errores de lock timeout.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qué hicimos:&lt;/strong&gt; le dimos a cada servicio su propia base de datos. Los cambios de inventario se publican como eventos vía SNS, y el servicio de órdenes mantiene su propio modelo de lectura del stock disponible. Sí, es eventualmente consistente. Pero &quot;eventualmente consistente con un checkout que funciona&quot; le gana a &quot;fuertemente consistente con un checkout que falla&quot; siempre.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lección:&lt;/strong&gt; una base de datos compartida es un destino compartido. Si dos servicios comparten una base de datos, no son dos servicios: son un monolito distribuido.&lt;/p&gt;
&lt;h2&gt;4. Sin backpressure en las colas&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Qué pasó:&lt;/strong&gt; usábamos SQS para procesar las confirmaciones de órdenes. Cada mensaje disparaba un email, la generación de una factura en PDF y una actualización de inventario. Durante un evento promocional entraron 50.000 órdenes en una hora. La cola de SQS se acumuló hasta 200.000 mensajes (algunas órdenes generaban varios mensajes). Nuestro consumidor intentó procesarlos todos a máxima velocidad, saturó el servicio de email y empezó a recibir errores 429 de rate limit. Los mensajes fallidos fueron a la dead-letter queue. 12.000 clientes no recibieron su email de confirmación.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qué hicimos:&lt;/strong&gt; tres cambios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Límites de concurrencia en los consumidores:&lt;/strong&gt; en lugar de procesar lo más rápido posible, fijamos un tope de 20 mensajes concurrentes por instancia de consumidor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate limiting por dependencia downstream:&lt;/strong&gt; agregamos un rate limiter de tipo token bucket antes de llamar a la API de email.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Backoff exponencial con jitter:&lt;/strong&gt; los mensajes fallidos se reintentan con retrasos crecientes más aleatoriedad para evitar un thundering herd durante la recuperación.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Consumidor de SQS con concurrencia controlada
const consumer = Consumer.create({
  queueUrl: process.env.ORDER_QUEUE_URL,
  batchSize: 10,
  visibilityTimeout: 60,
  handleMessage: async (message) =&amp;gt; {
    await rateLimiter.acquire(&apos;email-service&apos;, { maxPerSecond: 50 });
    await processOrderConfirmation(JSON.parse(message.Body));
  },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Lección:&lt;/strong&gt; una cola sin backpressure es solo una forma de convertir un pico de tráfico en un fallo en cascada con pasos extra.&lt;/p&gt;
&lt;h2&gt;5. Ignorar los cold starts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Qué pasó:&lt;/strong&gt; movimos un servicio crítico a AWS Lambda para ahorrar costos. Tiempo de respuesta promedio: 80 ms. Pero después de periodos de inactividad, el primer request tardaba entre 4 y 6 segundos por los cold starts. Era el servicio de autenticación. Los usuarios sufrían demoras aleatorias de 5 segundos al iniciar sesión. Los tickets de soporte se acumularon.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qué hicimos:&lt;/strong&gt; evaluamos tres opciones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Provisioned concurrency:&lt;/strong&gt; mantiene las instancias de Lambda calientes. Efectivo pero caro; anuló el ahorro de costos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pings de calentamiento:&lt;/strong&gt; un evento programado de CloudWatch que invoca la función cada 5 minutos. Barato pero poco confiable bajo carga concurrente: solo mantiene caliente una instancia.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Volver a ECS Fargate:&lt;/strong&gt; lo que terminamos haciendo. El servicio de auth necesita una latencia consistente por debajo de 100 ms. El modelo de Lambda no encaja con ese requisito.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Lección:&lt;/strong&gt; serverless es fantástico, para las cargas de trabajo adecuadas. Los servicios sensibles a la latencia y siempre activos no lo son. Ajusta el modelo de cómputo a los requisitos reales, no a la moda.&lt;/p&gt;
&lt;h2&gt;6. Cache stampede&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Qué pasó:&lt;/strong&gt; cacheábamos nuestro catálogo de productos en Redis con un TTL de 5 minutos. En el momento exacto en que expiró el cache, 3.000 requests concurrentes llegaron al endpoint. Los 3.000 vieron un cache miss. Los 3.000 consultaron la base de datos al mismo tiempo. Se agotó el connection pool de la base de datos. Errores 500 para todos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qué hicimos:&lt;/strong&gt; implementamos un patrón de cache lock (a veces llamado &quot;probabilistic early expiration&quot;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function getProductCatalog(categoryId: string): Promise&amp;lt;Product[]&amp;gt; {
  const cacheKey = `catalog:${categoryId}`;
  const cached = await redis.get(cacheKey);

  if (cached) {
    const { data, expiry, delta } = JSON.parse(cached);
    const now = Date.now();

    // Recómputo anticipado probabilístico
    // A medida que nos acercamos a la expiración, es cada vez más probable que se refresque
    if (now - delta * Math.log(Math.random()) &amp;lt; expiry) {
      return data;
    }
  }

  // Lock distribuido: solo un proceso reconstruye
  const lockKey = `lock:${cacheKey}`;
  const acquired = await redis.set(lockKey, &apos;1&apos;, &apos;EX&apos;, 30, &apos;NX&apos;);

  if (!acquired) {
    // Otro proceso está reconstruyendo; servimos datos obsoletos si hay
    if (cached) return JSON.parse(cached).data;
    // No hay datos obsoletos; esperamos un momento y reintentamos
    await sleep(100);
    return getProductCatalog(categoryId);
  }

  try {
    const products = await productRepo.findByCategory(categoryId);
    const ttl = 300; // 5 minutos
    await redis.set(cacheKey, JSON.stringify({
      data: products,
      expiry: Date.now() + ttl * 1000,
      delta: ttl * 1000 * 0.1,
    }), &apos;EX&apos;, ttl + 60); // Margen extra para servir datos obsoletos
    return products;
  } finally {
    await redis.del(lockKey);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Solo un proceso reconstruye el cache. Todos los demás reciben datos ligeramente desactualizados. En un catálogo de e-commerce, nadie nota datos de producto con 30 segundos de antigüedad. Todos notan un error 500.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lección:&lt;/strong&gt; la expiración del cache es un evento coordinado que puede generar thundering herds. Diséñalo de forma explícita.&lt;/p&gt;
&lt;h2&gt;7. No diseñar para el fallo&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Qué pasó:&lt;/strong&gt; este es el meta-error que engloba a todos los demás. Construimos la primera versión de la plataforma asumiendo que todo iba a funcionar. Camino feliz en todas partes. Sin reintentos, sin fallbacks, sin degradación elegante. El sistema funcionaba perfecto en desarrollo y en staging. Producción nos enseñó lo contrario durante la primera semana.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qué hicimos:&lt;/strong&gt; adoptamos un principio: &lt;strong&gt;toda llamada externa debe responder tres preguntas antes de escribirse:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;¿Qué pasa si esta llamada falla?&lt;/li&gt;
&lt;li&gt;¿Qué pasa si esta llamada es lenta (10 veces la latencia normal)?&lt;/li&gt;
&lt;li&gt;¿Qué pasa si esta llamada devuelve datos inesperados?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Lo codificamos en nuestro checklist de code review. Los pull requests que agregan llamadas externas sin responder estas tres preguntas se devuelven.&lt;/p&gt;
&lt;p&gt;También empezamos a hacer game days: inyectar fallos de forma intencional en staging con AWS Fault Injection Simulator. Te sorprendería cuántos supuestos se rompen cuando apagas una réplica de la base de datos o le agregas 500 ms de latencia a un servicio interno.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lección:&lt;/strong&gt; el fallo no es un caso borde. Es una característica de los sistemas distribuidos. Si no has diseñado para él, no has diseñado el sistema.&lt;/p&gt;
&lt;h2&gt;El patrón detrás de todos estos fallos&lt;/h2&gt;
&lt;p&gt;Mira los siete errores. Hay un hilo común: &lt;strong&gt;todos son invisibles a baja escala.&lt;/strong&gt; Las queries N+1 son rápidas cuando N es pequeño. La falta de circuit breakers no importa cuando las dependencias están sanas. Los cache stampedes no ocurren con 10 usuarios concurrentes.&lt;/p&gt;
&lt;p&gt;Por eso las pruebas de carga y la ingeniería del caos no son lujos opcionales: son la forma de encontrar estos problemas antes que tus clientes. Ejecutamos pruebas de carga que simulan 3 veces nuestro tráfico pico antes de cada release importante. Hacemos experimentos de caos todos los meses. No es paranoia. Es profesionalismo.&lt;/p&gt;
&lt;p&gt;Los sistemas que sobreviven a escala no son los construidos por los ingenieros más brillantes. Son los construidos por ingenieros que asumieron que todo se iba a romper y planificaron en consecuencia. Construye primero para el caso de fallo. El camino feliz se cuida solo.&lt;/p&gt;
</content:encoded></item><item><title>Redis en producción: estrategias de caching que realmente funcionan (y algunas que no)</title><link>https://hrdi.dev/es/blog/redis-caching-strategies-production/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/redis-caching-strategies-production/</guid><description>Una guía probada en producción sobre estrategias de caching con Redis: cache-aside, write-through, patrones de invalidación, diseño de TTL y los errores que nos costaron dinero de verdad.</description><pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Llevo varios años corriendo Redis en producción en plataformas de e-commerce y puedo decirte que la mayoría de los tutoriales de caching omiten las partes que realmente importan. Te muestran &lt;code&gt;SET key value EX 300&lt;/code&gt; y dan el trabajo por terminado. El caching real en producción trata de trade-offs, estrategias de invalidación, modos de falla y saber cuándo NO cachear algo.&lt;/p&gt;
&lt;p&gt;Esto es todo lo que me hubiera gustado que alguien me contara antes de aprenderlo a golpes.&lt;/p&gt;
&lt;h2&gt;Las tres estrategias principales (y cuándo encaja cada una)&lt;/h2&gt;
&lt;h3&gt;Cache-Aside (Lazy Loading)&lt;/h3&gt;
&lt;p&gt;Es el patrón más común, y con razón. La aplicación consulta primero el cache. Si no está (miss), lee de la base de datos, escribe en el cache y devuelve los datos.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function getProduct(id: string): Promise&amp;lt;Product&amp;gt; {
  const cacheKey = `product:${id}`;

  // 1. Consultar el cache
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  // 2. Cache miss: leer de la BD
  const product = await productRepo.findById(id);
  if (!product) throw new NotFoundException();

  // 3. Poblar el cache
  await redis.set(cacheKey, JSON.stringify(product), &apos;EX&apos;, 600);

  return product;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Cuándo funciona:&lt;/strong&gt; cargas de trabajo con mucha lectura donde los datos desactualizados son aceptables por períodos cortos. Catálogos de productos, perfiles de usuario, datos de configuración.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cuándo no funciona:&lt;/strong&gt; datos que cambian con frecuencia y deben ser consistentes de inmediato. Si cacheas el carrito de un usuario con cache-aside y agrega un ítem, verá el carrito viejo hasta que expire el TTL (a menos que invalides activamente; más sobre eso adelante).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trade-off:&lt;/strong&gt; simple de implementar, pero cada cache miss es un request lento. Un cache frío después de un deploy o un reinicio de Redis significa una estampida (thundering herd) hacia la base de datos.&lt;/p&gt;
&lt;h3&gt;Write-Through&lt;/h3&gt;
&lt;p&gt;Cada escritura va al cache y a la base de datos de forma simultánea. Las lecturas siempre van al cache.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function updateProduct(id: string, data: UpdateProductDto): Promise&amp;lt;Product&amp;gt; {
  // 1. Actualizar la base de datos
  const product = await productRepo.update(id, data);

  // 2. Actualizar el cache (de forma síncrona, como parte del camino de escritura)
  const cacheKey = `product:${id}`;
  await redis.set(cacheKey, JSON.stringify(product), &apos;EX&apos;, 600);

  return product;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Cuándo funciona:&lt;/strong&gt; datos que se leen con frecuencia y se actualizan de vez en cuando, Y en los que necesitas una fuerte consistencia de lectura. Datos de sesión, feature flags, reglas de precios.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cuándo no funciona:&lt;/strong&gt; cargas con mucha escritura. Pagas el costo de latencia de una escritura en Redis en cada escritura a la base de datos, incluso para datos que quizás nadie lea.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trade-off:&lt;/strong&gt; el cache siempre está fresco, pero las escrituras son más lentas. Y aun así necesitas un TTL como red de seguridad: si el write-through falla en silencio, sin TTL el cache diverge para siempre.&lt;/p&gt;
&lt;h3&gt;Write-Behind (Write-Back)&lt;/h3&gt;
&lt;p&gt;Las escrituras van primero al cache, y el cache las vuelca a la base de datos de forma asíncrona. Es lo más rápido para escribir, pero lo más peligroso.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cuándo funciona:&lt;/strong&gt; contadores de alta frecuencia, eventos de analítica, métricas en tiempo real, donde perder algunos puntos de datos es aceptable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cuándo no funciona:&lt;/strong&gt; cualquier cosa transaccional. Si Redis se cae antes de volcar a la base de datos, esos datos se pierden. Yo jamás lo usaría para órdenes, pagos ni nada financiero.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Trade-off:&lt;/strong&gt; máximo rendimiento de escritura, pero aceptas una posible pérdida de datos. En mi experiencia, este patrón rara vez es la opción correcta para los datos centrales del negocio. Lo usamos para contadores de vistas y pipelines de analítica, y poco más.&lt;/p&gt;
&lt;h2&gt;Estrategias de TTL más allá de &quot;ponlo en 5 minutos&quot;&lt;/h2&gt;
&lt;p&gt;El antipatrón de TTL número uno que veo es ponerle el mismo TTL a todo. Tu catálogo de productos y tu conteo de inventario en tiempo real tienen requisitos de frescura totalmente distintos. Trátalos de forma diferente.&lt;/p&gt;
&lt;p&gt;Así pensamos el diseño de TTL:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Datos de referencia estáticos&lt;/strong&gt; (países, categorías, tasas de impuestos): TTL de 24 horas. Invalida explícitamente al cambiar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datos semiestáticos&lt;/strong&gt; (detalles de producto, perfiles de usuario): TTL de 10-30 minutos. Los datos desactualizados molestan, pero no hacen daño.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datos volátiles&lt;/strong&gt; (conteos de inventario, precios): TTL de 30-60 segundos. O no cachees en absoluto: a veces la base de datos es la respuesta correcta.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datos de sesión/autenticación&lt;/strong&gt;: el TTL coincide con la duración de la sesión. Ni más, ni menos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;También usamos &lt;strong&gt;TTLs adaptativos&lt;/strong&gt; según los patrones de acceso:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function getWithAdaptiveTTL(key: string, fetcher: () =&amp;gt; Promise&amp;lt;any&amp;gt;): Promise&amp;lt;any&amp;gt; {
  const cached = await redis.get(key);
  if (cached) {
    // Extender el TTL al acceder (lo usado más recientemente se mantiene caliente)
    const currentTTL = await redis.ttl(key);
    if (currentTTL &amp;lt; 120) {
      await redis.expire(key, 600); // Reiniciar a 10 min al acceder
    }
    return JSON.parse(cached);
  }

  const data = await fetcher();
  await redis.set(key, JSON.stringify(data), &apos;EX&apos;, 600);
  return data;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Los datos de acceso frecuente se mantienen calientes. Los de acceso poco frecuente se expulsan de forma natural. La memoria de tu cache se mantiene enfocada en lo que importa.&lt;/p&gt;
&lt;h2&gt;Invalidación de cache: el verdadero problema difícil&lt;/h2&gt;
&lt;p&gt;Phil Karlton dijo que hay dos cosas difíciles en ciencias de la computación: la invalidación de cache y ponerle nombres a las cosas. No se equivocaba con la primera.&lt;/p&gt;
&lt;p&gt;Estos son los patrones de invalidación que usamos, del más simple al más robusto:&lt;/p&gt;
&lt;h3&gt;Expiración basada en TTL (pasiva)&lt;/h3&gt;
&lt;p&gt;Déjalo expirar de forma natural. Está bien cuando los datos desactualizados son aceptables. Es simple, y la simplicidad está subestimada.&lt;/p&gt;
&lt;h3&gt;Invalidación explícita (activa)&lt;/h3&gt;
&lt;p&gt;Cuando los datos cambian, borra la clave del cache:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function updateProduct(id: string, data: UpdateProductDto): Promise&amp;lt;Product&amp;gt; {
  const product = await productRepo.update(id, data);

  // Borrar, no actualizar: deja que la siguiente lectura lo repueble
  await redis.del(`product:${id}`);

  // ¡No olvides los caches relacionados!
  await redis.del(`catalog:${product.categoryId}`);
  await redis.del(`search:products:*`); // Borrado por patrón para los caches de búsqueda

  return product;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Importante:&lt;/strong&gt; borra, no actualices. Si actualizas el cache y la escritura en la base de datos falla o hace rollback, tu cache queda con datos fantasma. Borrar es idempotente y seguro.&lt;/p&gt;
&lt;p&gt;Pero fíjate en &lt;code&gt;search:products:*&lt;/code&gt;: ahí es donde la invalidación se pone fea. Un cambio de producto puede afectar listados de categorías, resultados de búsqueda, feeds de recomendaciones y destacados de la página de inicio. Olvidar aunque sea uno crea un bug de datos desactualizados increíblemente difícil de rastrear.&lt;/p&gt;
&lt;h3&gt;Invalidación basada en eventos&lt;/h3&gt;
&lt;p&gt;Esto es en lo que nos asentamos para cualquier caso no trivial. Los cambios en la base de datos publican eventos, y un consumidor dedicado a la invalidación del cache se encarga de la limpieza:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Manejador de eventos: servicio/consumidor separado
@OnEvent(&apos;product.updated&apos;)
async handleProductUpdate(event: ProductUpdatedEvent) {
  const { productId, categoryId, previousCategoryId } = event;

  const keysToInvalidate = [
    `product:${productId}`,
    `catalog:${categoryId}`,
    `homepage:featured`,
  ];

  // Si cambió la categoría, invalidar también la categoría anterior
  if (previousCategoryId &amp;amp;&amp;amp; previousCategoryId !== categoryId) {
    keysToInvalidate.push(`catalog:${previousCategoryId}`);
  }

  await Promise.all(keysToInvalidate.map(key =&amp;gt; redis.del(key)));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esto centraliza la lógica de invalidación. Cuando se agrega un cache nuevo, añades su regla de invalidación al manejador de eventos: un solo lugar que mantener en lugar de llamadas a &lt;code&gt;redis.del()&lt;/code&gt; dispersas por toda la base de código.&lt;/p&gt;
&lt;h2&gt;Redis Cluster en AWS ElastiCache: lo que aprendimos&lt;/h2&gt;
&lt;p&gt;Correr Redis en ElastiCache en modo cluster tiene algunas aristas filosas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;La distribución de claves importa.&lt;/strong&gt; Redis Cluster usa hash slots. Si usas &lt;code&gt;MGET&lt;/code&gt; sobre claves que caen en slots distintos, falla. Usa hash tags (&lt;code&gt;{product}:123&lt;/code&gt;, &lt;code&gt;{product}:456&lt;/code&gt;) para ubicar las claves relacionadas en el mismo shard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El failover no es instantáneo.&lt;/strong&gt; Cuando falla un nodo primario, ElastiCache promueve una réplica. Esto toma entre 10 y 30 segundos. Tu aplicación debe manejar los errores de conexión con elegancia durante el failover. Nosotros usamos &lt;code&gt;ioredis&lt;/code&gt; con &lt;code&gt;retryStrategy&lt;/code&gt; y &lt;code&gt;reconnectOnError&lt;/code&gt; bien configurados.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La gestión de memoria es crítica.&lt;/strong&gt; Configura &lt;code&gt;maxmemory-policy&lt;/code&gt; en &lt;code&gt;allkeys-lru&lt;/code&gt; para cargas de caching. Hemos visto equipos dejarlo en &lt;code&gt;noeviction&lt;/code&gt; (el valor por defecto) y luego preguntarse por qué Redis empieza a devolver errores cuando se llena.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitorea el lag de replicación.&lt;/strong&gt; Si tus réplicas de lectura se atrasan, las lecturas desde réplicas sirven datos desactualizados. Alertamos con 1 segundo de lag de replicación.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Cuándo NO cachear&lt;/h2&gt;
&lt;p&gt;Esta es la sección que la mayoría de los artículos se salta. Estos son los casos en que cachear empeora las cosas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Datos con mucha escritura y poca lectura.&lt;/strong&gt; Si escribes 10 veces más de lo que lees, pagas el costo de escritura del cache sin el beneficio de lectura.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datos muy personalizados con poca reutilización.&lt;/strong&gt; Cachear un feed de recomendaciones único por usuario suena bien hasta que te das cuenta de que tu tasa de aciertos es del 3 %. Estás usando Redis como una segunda base de datos cara y volátil.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Datos que deben ser consistentes en tiempo real.&lt;/strong&gt; Saldos de cuentas, inventario de lanzamientos de edición limitada, pujas de subastas. El costo de los datos desactualizados supera el costo de una consulta a la base de datos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Objetos grandes que rara vez se repiten.&lt;/strong&gt; Cachear en Redis un PDF de reporte de 2 MB es desperdiciar memoria. Ponlo en S3.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;La regla general:&lt;/strong&gt; si tu tasa de aciertos del cache está por debajo del 80 %, investiga si ese cache se está ganando su lugar. Por debajo del 50 %, probablemente perjudica más de lo que ayuda.&lt;/p&gt;
&lt;h2&gt;Monitoreo de la salud del cache&lt;/h2&gt;
&lt;p&gt;Un cache que no monitoreas es un cache esperando para traicionarte. Esto es lo que medimos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Hit rate&lt;/strong&gt; (objetivo: &amp;gt;90 %): &lt;code&gt;cache_hits / (cache_hits + cache_misses)&lt;/code&gt;. Lo exponemos como métrica de Prometheus y alertamos si baja del 80 %.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Percentiles de latencia&lt;/strong&gt;: p50, p95, p99 para las operaciones de Redis. Si el p99 se dispara, puede haber una hot key o un problema de red.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Uso de memoria&lt;/strong&gt;: porcentaje de la memoria disponible en uso. Alerta al 80 %.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tasa de evicción&lt;/strong&gt;: si las claves se expulsan más rápido de lo esperado, tu cache es demasiado pequeño para la carga de trabajo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cantidad de conexiones&lt;/strong&gt;: un número de conexiones repentinamente alto puede indicar una fuga de conexiones en tu aplicación.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Usamos OpenTelemetry para instrumentar todas las llamadas a Redis y enviar los traces a nuestro stack de observabilidad. Cada operación de cache recibe un span con el patrón de la clave (no la clave completa: no quieres labels de alta cardinalidad), el tipo de operación y si fue un hit o un miss.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Wrapper de instrumentación OTEL simplificado
async function cachedGet&amp;lt;T&amp;gt;(key: string, fetcher: () =&amp;gt; Promise&amp;lt;T&amp;gt;, ttl: number): Promise&amp;lt;T&amp;gt; {
  const span = tracer.startSpan(&apos;cache.get&apos;, {
    attributes: { &apos;cache.key_pattern&apos;: key.split(&apos;:&apos;)[0], &apos;cache.ttl&apos;: ttl },
  });

  try {
    const cached = await redis.get(key);
    if (cached) {
      span.setAttribute(&apos;cache.hit&apos;, true);
      cacheHitCounter.inc({ pattern: key.split(&apos;:&apos;)[0] });
      return JSON.parse(cached);
    }

    span.setAttribute(&apos;cache.hit&apos;, false);
    cacheMissCounter.inc({ pattern: key.split(&apos;:&apos;)[0] });
    const data = await fetcher();
    await redis.set(key, JSON.stringify(data), &apos;EX&apos;, ttl);
    return data;
  } finally {
    span.end();
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;La conclusión&lt;/h2&gt;
&lt;p&gt;Redis es una de las herramientas más poderosas del arsenal de un ingeniero de backend, pero no es magia. Es una máquina de trade-offs: memoria por velocidad, consistencia por rendimiento, simplicidad por resiliencia. Toda decisión de caching debería empezar con &quot;¿cuál es el costo de los datos desactualizados?&quot; y &quot;¿cuál es el costo de un cache miss?&quot;, no con &quot;cacheemos todo y esperemos lo mejor&quot;.&lt;/p&gt;
&lt;p&gt;Empieza con cache-aside para la mayoría de las cosas. Agrega write-through solo donde la consistencia importe. Usa invalidación basada en eventos para todo lo complejo. Monitorea tus hit rates religiosamente. Y nunca, jamás olvides: el cache miss más rápido es el que evitas al no cachear datos que no deberían cachearse en primer lugar.&lt;/p&gt;
</content:encoded></item><item><title>Cómo diseñar sistemas cuando no controlas todas las dependencias</title><link>https://hrdi.dev/es/blog/designing-systems-without-control/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/designing-systems-without-control/</guid><description>Cómo construir sistemas resilientes cuando las APIs de terceros, los servicios legacy y los equipos externos no son confiables. Circuit breakers, bulkheads, fallbacks y el concepto de trust boundary, a partir de experiencia real en e-commerce.</description><pubDate>Sat, 28 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hay un momento en la carrera de todo ingeniero en el que te das cuenta de algo incómodo: el sistema del que eres responsable es tan confiable como su dependencia externa más débil. Y esa dependencia no la controlas tú.&lt;/p&gt;
&lt;p&gt;Quizás sea una pasarela de pagos que agota el tiempo de espera en horas pico. Quizás sea un sistema de inventario legacy mantenido por un equipo en otra zona horaria que hace deploy los viernes. Quizás sea un proveedor de envíos cuya API devuelve 500 cada vez que publica una &quot;actualización menor&quot;.&lt;/p&gt;
&lt;p&gt;He vivido todo esto. Y esto es lo que aprendí: &lt;strong&gt;los sistemas resilientes no se diseñan esperando que todo funcione. Se diseñan asumiendo que todo va a fallar.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;El concepto de trust boundary&lt;/h2&gt;
&lt;p&gt;Antes de entrar en los patrones, establezcamos un modelo mental que uso constantemente: el &lt;strong&gt;trust boundary&lt;/strong&gt; (límite de confianza).&lt;/p&gt;
&lt;p&gt;Todo sistema tiene un límite entre lo que controlas y lo que no. Dentro del límite, eres dueño del código, los deploys, el monitoreo y la rotación de guardias. Fuera del límite, estás a merced de las prioridades, el calendario de releases y la definición de &quot;confiable&quot; de alguien más.&lt;/p&gt;
&lt;p&gt;El error que cometen la mayoría de los equipos es tratar las dependencias externas como si estuvieran dentro del trust boundary. Llaman a una API de terceros y asumen que responderá en 200 ms. Se integran con un servicio legacy interno y asumen que el contrato no cambiará. Confían.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No confíes. Verifica, aísla y planifica para el fallo.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Así es como trazo los trust boundaries en la práctica:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Nivel 1 (confianza total):&lt;/strong&gt; Servicios que mi equipo posee y despliega. Nosotros controlamos los SLA.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nivel 2 (confianza condicional):&lt;/strong&gt; Servicios internos de otros equipos. Tenemos algo de influencia, pero ningún control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nivel 3 (confianza cero):&lt;/strong&gt; APIs de terceros, sistemas legacy, cualquier cosa detrás de una VPN que no podemos monitorear.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cada nivel recibe una estrategia de resiliencia distinta. Las dependencias de Nivel 3 reciben el tratamiento completo.&lt;/p&gt;
&lt;h2&gt;Patrón Circuit Breaker: deja de golpear servicios muertos&lt;/h2&gt;
&lt;p&gt;El circuit breaker es el primer patrón al que deberías recurrir cuando lidias con dependencias poco confiables. El concepto está tomado de la ingeniería eléctrica: cuando un servicio downstream está fallando, deja de enviarle solicitudes.&lt;/p&gt;
&lt;p&gt;Aquí hay una implementación simplificada que he usado en servicios Node.js:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class CircuitBreaker {
  private failures = 0;
  private lastFailure: number = 0;
  private state: &apos;closed&apos; | &apos;open&apos; | &apos;half-open&apos; = &apos;closed&apos;;

  constructor(
    private readonly threshold: number = 5,
    private readonly resetTimeout: number = 30_000
  ) {}

  async execute&amp;lt;T&amp;gt;(fn: () =&amp;gt; Promise&amp;lt;T&amp;gt;, fallback: () =&amp;gt; T): Promise&amp;lt;T&amp;gt; {
    if (this.state === &apos;open&apos;) {
      if (Date.now() - this.lastFailure &amp;gt; this.resetTimeout) {
        this.state = &apos;half-open&apos;;
      } else {
        return fallback();
      }
    }

    try {
      const result = await fn();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      return fallback();
    }
  }

  private onSuccess(): void {
    this.failures = 0;
    this.state = &apos;closed&apos;;
  }

  private onFailure(): void {
    this.failures++;
    this.lastFailure = Date.now();
    if (this.failures &amp;gt;= this.threshold) {
      this.state = &apos;open&apos;;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;En un sistema de e-commerce en el que trabajé, envolvimos cada llamada a la pasarela de pagos con un circuit breaker. Cuando Stripe tenía problemas intermitentes (sí, hasta Stripe tiene malos días), el circuito se abría tras 5 fallos consecutivos. En lugar de encolar miles de intentos de pago fallidos y dejar a los usuarios mirando un spinner, redirigíamos de inmediato al flujo de fallback: mostrábamos un mensaje de &quot;procesamiento de pago retrasado&quot; y encolábamos el cobro para reintentarlo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Idea clave:&lt;/strong&gt; El circuit breaker no arregla el problema. Previene los fallos en cascada y le da al usuario una mejor experiencia durante las caídas.&lt;/p&gt;
&lt;h2&gt;Aislamiento con Bulkheads: no dejes que un fallo hunda el barco&lt;/h2&gt;
&lt;p&gt;Los bulkheads (mamparos) de un barco son compartimentos estancos. Si uno se inunda, el barco no se hunde. El mismo principio aplica al software.&lt;/p&gt;
&lt;p&gt;En un escenario real: teníamos una plataforma de e-commerce en la que el calculador de tarifas de envío, el verificador de inventario y el procesador de pagos compartían el mismo pool de conexiones HTTP. Cuando el proveedor de envíos empezó a responder lento, consumió todas las conexiones disponibles. De pronto, los pagos tampoco se podían procesar. Una dependencia defectuosa tumbó una ruta crítica que no tenía relación con ella.&lt;/p&gt;
&lt;p&gt;La solución fue el aislamiento con bulkheads:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pools de conexiones separados&lt;/strong&gt; por cada dependencia externa&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pools de hilos separados&lt;/strong&gt; (o, en Node.js, workers de cola separados) para distintas categorías de trabajo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Timeouts separados&lt;/strong&gt; ajustados al comportamiento esperado de cada dependencia&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;const paymentClient = new HttpClient({
  maxConnections: 20,
  timeout: 5_000,       // los pagos deberían ser rápidos
  retries: 2,
});

const shippingClient = new HttpClient({
  maxConnections: 10,
  timeout: 15_000,      // las APIs de envío son notoriamente lentas
  retries: 3,
});

const inventoryClient = new HttpClient({
  maxConnections: 15,
  timeout: 3_000,       // servicio interno, debería ser rápido
  retries: 1,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esto no es sobreingeniería. Es la diferencia entre &quot;la API de envíos está lenta hoy&quot; y &quot;todo el checkout está caído&quot;.&lt;/p&gt;
&lt;h2&gt;Estrategias de timeout: el asesino silencioso&lt;/h2&gt;
&lt;p&gt;La mayoría de los incidentes de producción que he depurado no empezaron con un crash. Empezaron con un timeout demasiado generoso o inexistente.&lt;/p&gt;
&lt;p&gt;Esta es mi jerarquía de timeouts para llamadas externas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Connect timeout:&lt;/strong&gt; Cuánto esperar una conexión TCP. Mantenlo agresivo (1-3 segundos). Si un servicio no acepta conexiones, esperar más no ayudará.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Read timeout:&lt;/strong&gt; Cuánto esperar una respuesta después de establecida la conexión. Depende de la operación, pero rara vez supero los 10 segundos en llamadas síncronas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total timeout:&lt;/strong&gt; El presupuesto global de toda la operación, incluidos los reintentos. Es el que la mayoría olvida.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;La regla que sigo:&lt;/strong&gt; toda llamada HTTP a un servicio externo DEBE tener un timeout explícito. Sin excepciones. Los timeouts por defecto de las librerías HTTP suelen ser de 30 a 120 segundos, lo cual es una eternidad en un pipeline de solicitudes.&lt;/p&gt;
&lt;p&gt;Un ejemplo práctico: en un flujo de checkout, el presupuesto total es quizás de 8 segundos antes de que el usuario se impaciente. Si llamas en secuencia a una API de pagos, un validador de direcciones y un calculador de impuestos, no tienes 30 segundos por llamada. Tienes quizás 2-3 segundos cada una, y el pago recibe la asignación más generosa.&lt;/p&gt;
&lt;h2&gt;Mecanismos de fallback: degradación elegante en la práctica&lt;/h2&gt;
&lt;p&gt;Aquí es donde se pone interesante. Los fallbacks no son solo devolver datos en cache. Se trata de &lt;strong&gt;decidir qué puede seguir haciendo tu sistema sin una dependencia.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ejemplos reales de sistemas que he construido:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pasarela de pagos caída:&lt;/strong&gt; Encolar la orden, mostrar el estado &quot;procesando&quot; y cobrar de forma asíncrona cuando la pasarela se recupere. El usuario igual completa el checkout.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Calculador de tarifas de envío caído:&lt;/strong&gt; Mostrar tarifas planas estimadas a partir de datos históricos. No es perfecto, pero el usuario puede comprar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Servicio de inventario caído:&lt;/strong&gt; Permitir la compra, pero marcarla para revisión manual. Aceptar el riesgo de vender de más unas pocas piezas frente a bloquear todas las ventas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Motor de recomendaciones caído:&lt;/strong&gt; Mostrar productos populares en lugar de personalizados. Nadie lo nota.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El patrón:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async function getShippingRates(order: Order): Promise&amp;lt;ShippingRate[]&amp;gt; {
  try {
    return await shippingCircuitBreaker.execute(
      () =&amp;gt; shippingClient.calculateRates(order),
      () =&amp;gt; getCachedFlatRates(order.destination)
    );
  } catch {
    return getDefaultFlatRates();  // último recurso
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;La conversación con el negocio importa aquí.&lt;/strong&gt; Los fallbacks tienen trade-offs. Mostrar tarifas planas cuando las tarifas reales son distintas significa que podrías perder dinero en algunos envíos. Encolar y cobrar después significa que podrías cobrarle a una tarjeta que luego será rechazada. Son decisiones de negocio, no solo técnicas. Ten esta conversación con producto antes de la caída, no durante.&lt;/p&gt;
&lt;h2&gt;Contract Testing: detecta los quiebres antes de producción&lt;/h2&gt;
&lt;p&gt;Una de las estrategias más efectivas que he adoptado es el contract testing dirigido por el consumidor. En lugar de rezar para que la API de envíos no cambie su formato de respuesta, codificamos lo que esperamos y lo probamos con regularidad.&lt;/p&gt;
&lt;p&gt;Herramientas como Pact te permiten definir contratos:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;describe(&apos;Shipping API Contract&apos;, () =&amp;gt; {
  it(&apos;should return rates with expected structure&apos;, async () =&amp;gt; {
    const response = await provider.executeTest(async (mockServer) =&amp;gt; {
      const client = new ShippingClient(mockServer.url);
      const rates = await client.getRates(testOrder);

      expect(rates).toEqual(
        expect.arrayContaining([
          expect.objectContaining({
            carrier: expect.any(String),
            price: expect.any(Number),
            estimatedDays: expect.any(Number),
          }),
        ])
      );
    });
  });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esto no evitará que la API externa se rompa. Pero te avisará &lt;strong&gt;de inmediato&lt;/strong&gt; cuando se viole el contrato, en lugar de descubrirlo a través de errores en producción a las 2 AM.&lt;/p&gt;
&lt;h2&gt;El trade-off: resiliencia vs. simplicidad&lt;/h2&gt;
&lt;p&gt;Hay algo sobre lo que quiero ser honesto: todo esto añade complejidad. Circuit breakers, bulkheads, fallbacks, contract tests: todos añaden código, configuración y carga cognitiva.&lt;/p&gt;
&lt;p&gt;No todos los sistemas necesitan todos estos patrones. ¿Un proyecto personal que llama a una sola API externa? Simplemente añade un timeout y maneja los errores con elegancia. ¿Una plataforma de e-commerce que procesa miles de órdenes por hora a través de una docena de servicios externos? Necesitas el kit completo.&lt;/p&gt;
&lt;p&gt;Mi regla general:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Si la caída de la dependencia cuesta dinero:&lt;/strong&gt; circuit breaker + fallback + monitoreo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Si la caída provoca pérdida de datos:&lt;/strong&gt; aislamiento con bulkheads + reintentos con idempotencia&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Si la dependencia pertenece a otro equipo:&lt;/strong&gt; contract tests + SLA explícitos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Si es un tercero sobre el que no puedes influir en absoluto:&lt;/strong&gt; todo lo anterior&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reflexiones finales&lt;/h2&gt;
&lt;p&gt;La lección más difícil del diseño de sistemas no es aprender patrones. Es aceptar que no puedes controlarlo todo. Tu proveedor de pagos tendrá caídas. El sistema legacy cambiará sin avisar. El equipo del otro lado del edificio desplegará un cambio que rompe todo un viernes.&lt;/p&gt;
&lt;p&gt;Tu trabajo no es prevenir estos fallos. Es diseñar sistemas que los manejen con elegancia. Cada patrón de resiliencia es una apuesta: &quot;Cuando esta dependencia falle (y va a fallar), así es como seguiremos funcionando&quot;.&lt;/p&gt;
&lt;p&gt;Los ingenieros que construyen sistemas realmente confiables no son los que escriben el código más ingenioso. Son los que se han quemado las veces suficientes como para planificar el incendio.&lt;/p&gt;
</content:encoded></item><item><title>El rol de un Tech Lead más allá de escribir código</title><link>https://hrdi.dev/es/blog/tech-lead-beyond-coding/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/tech-lead-beyond-coding/</guid><description>Qué significa realmente ser Tech Lead más allá de programar. Decisiones de arquitectura, mentoría a ingenieros, tensiones con producto y la transición de mejor programador a mejor facilitador.</description><pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cuando me convertí en Tech Lead por primera vez, pensé que el trabajo era simple: ser el mejor programador del equipo y tomar las grandes decisiones técnicas. Me equivoqué en prácticamente todo.&lt;/p&gt;
&lt;p&gt;El rol de Tech Lead es una de las posiciones más malinterpretadas en la ingeniería de software. No es un ascenso por escribir buen código. Es un trabajo fundamentalmente distinto. Y nadie te prepara para él.&lt;/p&gt;
&lt;p&gt;Después de años liderando equipos en varios sistemas de producción, esto es lo que el rol realmente implica: las partes de las que nadie habla en las descripciones de puesto.&lt;/p&gt;
&lt;h2&gt;Las decisiones de arquitectura son decisiones organizacionales&lt;/h2&gt;
&lt;p&gt;Cada decisión de arquitectura que tomas tiene consecuencias organizacionales. ¿Eliges microservicios? Ahora necesitas límites entre equipos, contratos de API y coordinación de deploys. ¿Eliges un monolito? Ahora necesitas disciplina modular y estrategias para resolver conflictos de merge.&lt;/p&gt;
&lt;p&gt;He visto equipos adoptar arquitecturas orientadas a eventos porque era &quot;el patrón correcto&quot;, sin considerar que nadie en el equipo tenía experiencia con la consistencia eventual. La arquitectura era sólida sobre el papel. En la práctica, provocó meses de sesiones de depuración y un backlog de bugs de inconsistencia de datos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Como Tech Lead, tu trabajo no es elegir la mejor arquitectura en teoría. Es elegir la mejor arquitectura para tu equipo, tus plazos y las restricciones de tu negocio.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Esto significa hacerte preguntas como:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;¿Podrá nuestro equipo mantener esto dentro de 6 meses, cuando yo no esté revisando cada PR?&lt;/li&gt;
&lt;li&gt;¿Esta arquitectura se ajusta a nuestras capacidades de deploy?&lt;/li&gt;
&lt;li&gt;Si contratamos a dos desarrolladores junior el próximo trimestre, ¿podrán contribuir a esto?&lt;/li&gt;
&lt;li&gt;¿Cuál es el costo de equivocarnos con esta decisión?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La mejor decisión de arquitectura que he tomado no fue elegir un patrón elegante. Fue elegir uno aburrido y bien entendido con el que todo el equipo pudiera trabajar de forma independiente. Lo aburrido está subestimado.&lt;/p&gt;
&lt;h2&gt;Mentoría: tu efecto multiplicador&lt;/h2&gt;
&lt;p&gt;Aquí va un problema de matemáticas: puedes escribir 8 horas de código al día, o puedes dedicar 2 horas a hacer mentoría a tres ingenieros que luego escriben cada uno 6 horas efectivas de código al día. La primera opción produce 8 horas de output. La segunda produce 18.&lt;/p&gt;
&lt;p&gt;Este es el efecto multiplicador, y es lo más impactante que hace un Tech Lead.&lt;/p&gt;
&lt;p&gt;Pero la mentoría no es solo responder preguntas o hacer pair programming (aunque ambos importan). La mentoría de verdad implica:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;El code review como enseñanza:&lt;/strong&gt; no te limites a aprobar o pedir cambios. Explica &lt;em&gt;por qué&lt;/em&gt; algo debería ser distinto. Enlaza a la documentación. Muestra las consecuencias del enfoque actual. Un comentario en un PR que dice &quot;usa inyección de dependencias aquí&quot; no vale nada. Uno que dice &quot;si inyectamos esta dependencia, podemos probar el flujo de pago sin llamar a Stripe — así se hace&quot; enseña un principio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delegar los problemas difíciles:&lt;/strong&gt; esto es contraintuitivo. Tu instinto es quedarte con las tareas complejas porque las harás más rápido. Resiste ese instinto. Dale el trabajo desafiante a ingenieros en crecimiento, con tu apoyo. Sí, va a tomar más tiempo. Sí, la primera implementación no será tan limpia. Pero ahora tienes a dos personas que pueden resolver ese tipo de problema en lugar de una.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crear espacios seguros para fallar:&lt;/strong&gt; los ingenieros que le temen a los errores no crecen. Les digo explícitamente a mi equipo: &quot;Esta es una zona segura para experimentar. Si se rompe, lo arreglamos juntos.&quot; Los mejores ingenieros a los que he hecho mentoría cometieron errores espectaculares al principio porque se sintieron seguros para intentarlo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Uno de mis momentos de mayor orgullo como Tech Lead no fue entregar una funcionalidad. Fue ver a un ingeniero de nivel intermedio al que llevaba un año haciendo mentoría liderar una discusión de arquitectura y tomar mejores decisiones de las que yo habría tomado.&lt;/p&gt;
&lt;h2&gt;Navegar las tensiones entre producto e ingeniería&lt;/h2&gt;
&lt;p&gt;Cada sprint planning es una negociación entre lo que quiere el equipo de producto y lo que el equipo de ingeniería sabe que es sostenible. Como Tech Lead, eres el traductor entre esos dos mundos.&lt;/p&gt;
&lt;p&gt;Producto dice: &quot;Necesitamos esta funcionalidad para el próximo sprint.&quot; Ingeniería sabe: el modelo de datos no la soporta, y forzarla creará una deuda que va a frenar todo durante meses.&lt;/p&gt;
&lt;p&gt;Tu trabajo no es decir &quot;no&quot;. Tu trabajo es decir &quot;estas son las opciones con sus trade-offs&quot;.&lt;/p&gt;
&lt;p&gt;Un marco que uso constantemente:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Opción A:&lt;/strong&gt; hacerlo de la forma rápida. Entregar en 1 sprint. Genera X cantidad de deuda técnica. Costo estimado de arreglarlo después: Y sprints. Riesgo: Z.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opción B:&lt;/strong&gt; hacerlo bien. Entregar en 2 sprints. Sin deuda. Habilita las funcionalidades futuras P y Q sin retrabajo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opción C:&lt;/strong&gt; entregar un alcance reducido en 1 sprint. Cubre el 80 % de la necesidad del usuario. Arquitectura adecuada. Iterar sobre el 20 % restante en el siguiente sprint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Nueve de cada diez veces, gana la opción C. Pero solo puedes presentar la opción C si entiendes a fondo tanto el objetivo de negocio como el panorama técnico.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Los Tech Leads que terminan quemados son los que simplemente dicen &quot;sí&quot; a todo.&lt;/strong&gt; Aprende a presentar opciones. Deja que los stakeholders decidan con toda la información. Eso no es resistencia: es colaboración.&lt;/p&gt;
&lt;h2&gt;Decir &quot;no&quot; al scope creep&lt;/h2&gt;
&lt;p&gt;El scope creep es el asesino silencioso de los proyectos. No llega como un gran cambio. Llega como una serie de adiciones &quot;pequeñas&quot; que parecen razonables por separado.&lt;/p&gt;
&lt;p&gt;&quot;¿Podemos agregar también filtros a esta página?&quot; &quot;¿Y si soportáramos también la exportación a CSV?&quot; &quot;¿Podría esta API manejar también el caso de uso del administrador?&quot;&lt;/p&gt;
&lt;p&gt;Cada pedido son 2-3 días de trabajo. Diez de ellos son un mes de trabajo no planificado que nadie presupuestó.&lt;/p&gt;
&lt;p&gt;Como Tech Lead, muchas veces eres la única persona en la sala que puede ver el costo técnico acumulado. Es tu responsabilidad hacerlo visible.&lt;/p&gt;
&lt;p&gt;Mantengo un documento permanente al que llamo el &quot;scope ledger&quot;. Cada adición se registra con su esfuerzo estimado. Cuando los stakeholders preguntan por qué el proyecto se retrasó, puedo señalar 15 adiciones &quot;pequeñas&quot; que sumaron 6 semanas de trabajo extra. Los datos cambian las conversaciones.&lt;/p&gt;
&lt;h2&gt;La deuda técnica como conversación de negocio&lt;/h2&gt;
&lt;p&gt;Esto me tomó años aprenderlo: los ingenieros hablan de deuda técnica en términos de ingeniería. &quot;El código es un desastre.&quot; &quot;Necesitamos refactorizar este módulo.&quot; &quot;Nuestra cobertura de pruebas es muy baja.&quot; Estas frases no significan nada para los stakeholders de negocio.&lt;/p&gt;
&lt;p&gt;La deuda técnica hay que traducirla a impacto en el negocio:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;No digas: &quot;Necesitamos refactorizar el módulo de pagos.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Di: &quot;Agregar cualquier método de pago nuevo al módulo de pagos toma 3 días. Nuestros competidores agregan métodos nuevos cada trimestre. Refactorizar lo reduce a medio día.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No digas: &quot;Nuestra cobertura de pruebas es del 20 %.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Di: &quot;Este trimestre tuvimos 4 incidentes en producción que las pruebas habrían detectado. Cada incidente nos costó en promedio $X en transacciones perdidas y Y horas de ingeniería.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;No digas: &quot;El código está muy acoplado.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Di: &quot;Agregar la funcionalidad que quieres requiere modificar 12 archivos en 4 servicios por cómo están conectadas las cosas. Por eso las estimaciones siguen siendo más altas de lo esperado.&quot;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cuando planteas la deuda como un problema de negocio con costos medibles, consigues presupuesto para resolverla. Cuando la planteas como una preferencia de ingeniería, te dicen que &quot;lo hagas funcionar igual&quot;.&lt;/p&gt;
&lt;h2&gt;La soledad de liderar&lt;/h2&gt;
&lt;p&gt;Nadie te advierte sobre esta parte. Como Tech Lead, ocupas un terreno intermedio extraño. No eres management, pero tampoco eres ya &quot;uno de los desarrolladores&quot;. Estás en reuniones en las que tu equipo no está. Te enteras de reorganizaciones, recortes de presupuesto y giros estratégicos antes que nadie. Y no siempre puedes compartir lo que sabes.&lt;/p&gt;
&lt;p&gt;A veces tu equipo se frustrará con decisiones que tomaste o aceptaste. No siempre tendrán el contexto de por qué. Y no siempre podrás dárselo.&lt;/p&gt;
&lt;p&gt;Hay días en los que eres el escudo entre tu equipo y el caos organizacional, y nadie lo sabrá jamás. El mejor elogio que puede recibir un Tech Lead no es &quot;gran arquitectura&quot;. Es que un miembro del equipo diga &quot;no sé por qué, pero aquí las cosas simplemente fluyen&quot;. Esa fluidez es que tú absorbes una fricción que ellos nunca ven.&lt;/p&gt;
&lt;p&gt;Busca un grupo de pares. Habla con otros Tech Leads. Este rol es aislante si no tienes a personas que entiendan las presiones específicas bajo las que estás.&lt;/p&gt;
&lt;h2&gt;Cuándo programar y cuándo dar un paso atrás&lt;/h2&gt;
&lt;p&gt;Esta es la calibración más difícil. Si programas demasiado, te conviertes en un cuello de botella: el equipo depende de ti para el trabajo del camino crítico. Si programas muy poco, pierdes contacto con el código, y tu guía de arquitectura se vuelve cada vez más teórica.&lt;/p&gt;
&lt;p&gt;Mi balance actual:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Escribo código&lt;/strong&gt; para: pruebas de concepto de decisiones de arquitectura, piezas críticas de infraestructura donde hay que establecer los patrones, depuración de problemas de producción que requieren un conocimiento profundo del sistema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No escribo código&lt;/strong&gt; para: trabajo de funcionalidades que un miembro del equipo podría hacer (aunque tarde más), cualquier cosa en el camino crítico de un compromiso del sprint, tareas que serían mejores como oportunidades de aprendizaje para otra persona.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Una regla práctica: si quitarme del tablero del sprint bloquearía la entrega, estoy programando demasiadas cosas equivocadas. El equipo debería poder entregar sin que yo escriba una sola línea de código de funcionalidades.&lt;/p&gt;
&lt;h2&gt;La transición: de mejor programador a mejor facilitador&lt;/h2&gt;
&lt;p&gt;El cambio más difícil al convertirte en Tech Lead es redefinir qué significa ser &quot;productivo&quot;. Durante toda tu carrera, productividad significó líneas de código, funcionalidades entregadas, problemas resueltos con tus propias manos.&lt;/p&gt;
&lt;p&gt;Ahora, productividad significa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un ingeniero junior que entrega con confianza su primera funcionalidad en producción&lt;/li&gt;
&lt;li&gt;Una arquitectura que le permite al equipo avanzar de forma independiente&lt;/li&gt;
&lt;li&gt;Una cultura de code review donde los ingenieros se enseñan unos a otros&lt;/li&gt;
&lt;li&gt;Un sprint que termina a tiempo porque identificaste los riesgos temprano&lt;/li&gt;
&lt;li&gt;Un sistema en producción que maneja los fallos con elegancia porque invertiste en patrones de resiliencia&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tu producción de código va a bajar. Tu impacto se va a multiplicar. Eso no es un trade-off. Es todo el punto.&lt;/p&gt;
&lt;h2&gt;Reflexiones finales&lt;/h2&gt;
&lt;p&gt;Ser Tech Lead cambió mi forma de pensar sobre el software. Antes optimizaba por código elegante. Ahora optimizo por la velocidad del equipo, sistemas sostenibles y el crecimiento de las personas.&lt;/p&gt;
&lt;p&gt;Si estás haciendo la transición a este rol, este es mi consejo honesto: suelta la idea de ser el mejor programador de la sala. Invierte en ser la persona que hace mejores a todos los demás. El código que escribas será olvidado. Los ingenieros que hagas crecer construirán cosas que tú jamás habrías podido construir solo.&lt;/p&gt;
&lt;p&gt;Eso no es un descenso. Es apalancamiento.&lt;/p&gt;
</content:encoded></item><item><title>La IA en el flujo de trabajo del desarrollador: qué funciona de verdad (y qué es puro hype)</title><link>https://hrdi.dev/es/blog/ai-developer-workflow/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/ai-developer-workflow/</guid><description>Una evaluación honesta de las herramientas de IA en el desarrollo diario. En qué es realmente buena la IA, en qué es pésima y por qué entender los fundamentos importa más que nunca.</description><pubDate>Sun, 15 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Voy a ser directo: la IA no va a reemplazar a los desarrolladores. Pero tampoco es un juguete. La verdad, como siempre, está en algún punto intermedio, y la mayor parte del discurso sobre la IA en el desarrollo es o bien hype desbordado o bien cinismo despectivo. Ambos se equivocan.&lt;/p&gt;
&lt;p&gt;Llevo más de un año usando herramientas de IA a diario en mi flujo de trabajo. No como experimento. En trabajo de producción, liderando equipos y entregando sistemas reales. Esta es mi evaluación honesta y sin hype de qué funciona de verdad, qué no y en qué deberías estar pensando.&lt;/p&gt;
&lt;h2&gt;En qué es realmente buena la IA&lt;/h2&gt;
&lt;p&gt;Empecemos por los aciertos. Estas son las áreas en las que las herramientas de IA mejoraron mi flujo de trabajo de forma medible:&lt;/p&gt;
&lt;h3&gt;Boilerplate y código repetitivo&lt;/h3&gt;
&lt;p&gt;Aquí es donde la IA brilla más. Escribir DTOs, funciones de mapeo, endpoints CRUD, archivos de configuración, definiciones de tipos a partir de especificaciones de API: el tipo de código en el que el patrón está bien establecido y básicamente estás traduciendo una estructura conocida a sintaxis.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// &quot;Genera un DTO para la respuesta de la orden con estos campos&quot;
// La IA produce esto en segundos, frente a minutos de tipeo:
interface OrderResponseDto {
  id: string;
  status: OrderStatus;
  items: OrderItemDto[];
  total: number;
  currency: string;
  createdAt: string;
  shippingAddress: AddressDto;
  estimatedDelivery: string | null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;¿Es revolucionario? No. ¿Ahorra 20 minutos de tipeo tedioso varias veces al día? Totalmente. Y eso se acumula.&lt;/p&gt;
&lt;h3&gt;Andamiaje de pruebas&lt;/h3&gt;
&lt;p&gt;Probablemente sea mi caso de uso favorito. La IA es notablemente buena generando el andamiaje de las pruebas: los bloques &lt;code&gt;describe&lt;/code&gt;, la configuración y los casos básicos de camino feliz y de error. Acierta con la estructura aproximadamente el 80 % de las veces.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Describo qué hace la función y la IA genera el andamiaje:
describe(&apos;OrderService.calculateTotal&apos;, () =&amp;gt; {
  let service: OrderService;
  let mockTaxCalculator: jest.Mocked&amp;lt;TaxCalculator&amp;gt;;

  beforeEach(() =&amp;gt; {
    mockTaxCalculator = { calculate: jest.fn() };
    service = new OrderService(mockTaxCalculator);
  });

  it(&apos;should sum item prices correctly&apos;, () =&amp;gt; { /* ... */ });
  it(&apos;should apply tax from calculator&apos;, () =&amp;gt; { /* ... */ });
  it(&apos;should handle empty order&apos;, () =&amp;gt; { /* ... */ });
  it(&apos;should throw on negative quantities&apos;, () =&amp;gt; { /* ... */ });
  it(&apos;should round to 2 decimal places&apos;, () =&amp;gt; { /* ... */ });
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Las aserciones reales y los casos borde los sigo escribiendo yo. Pero tener el andamiaje, los mocks configurados y la organización de las pruebas listos ahorra muchísimo tiempo. Es como tener a alguien que te prepara el banco de trabajo antes de empezar a construir.&lt;/p&gt;
&lt;h3&gt;Explorar bases de código desconocidas&lt;/h3&gt;
&lt;p&gt;Cuando entro a un proyecto nuevo o necesito entender una librería que nunca usé, la IA es una excelente compañera de exploración. &quot;Explica cómo funciona esta cadena de middlewares.&quot; &quot;¿Qué aprovisiona este módulo de Terraform?&quot; &quot;Guíame por el flujo de autenticación de este código.&quot;&lt;/p&gt;
&lt;p&gt;No siempre acierta. Pero es más rápido que leer la documentación desde cero y me da un punto de partida para mi propia investigación. La trato como a un colega que ya trabajó en ese código: su aporte es útil, pero verifico todo lo crítico.&lt;/p&gt;
&lt;h3&gt;Documentación y mensajes de commit&lt;/h3&gt;
&lt;p&gt;Escribir comentarios JSDoc, secciones de README, documentación de APIs e incluso mensajes de commit bien pensados. La IA lo hace sorprendentemente bien porque documentar consiste, en esencia, en describir algo que ya existe, y el código está ahí como contexto.&lt;/p&gt;
&lt;h3&gt;Sugerencias de refactorización&lt;/h3&gt;
&lt;p&gt;&quot;Aquí tienes una función de 200 líneas. Sugiere cómo dividirla.&quot; Las sugerencias no siempre están listas para producción, pero son un buen punto de partida para pensar la descomposición. La IA detecta oportunidades de extraer métodos más rápido de lo que yo leo una función larga.&lt;/p&gt;
&lt;h2&gt;En qué es pésima la IA&lt;/h2&gt;
&lt;p&gt;Ahora viene lo importante. Estas son las áreas en las que confiar en lo que genera la IA te va a costar caro:&lt;/p&gt;
&lt;h3&gt;Decisiones de arquitectura&lt;/h3&gt;
&lt;p&gt;La IA no puede tomar decisiones de arquitectura por tu sistema. No entiende las capacidades de tu equipo, tus restricciones de despliegue, los plazos del negocio, los requisitos regulatorios ni las dinámicas políticas que influyen en las decisiones técnicas.&lt;/p&gt;
&lt;p&gt;He visto ingenieros preguntarle a la IA &quot;¿uso microservicios o un monolito?&quot; y tomar la respuesta como palabra santa. La respuesta va a ser coherente. Va a mencionar trade-offs. Va a sonar autorizada. Y va a ser un consejo genérico que ignora todo lo que realmente importa de tu situación concreta.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La arquitectura requiere un contexto que no entra en un prompt.&lt;/strong&gt; Requiere entender la dinámica de la organización, las habilidades del equipo, la madurez operativa y el rumbo del negocio. La IA no tiene nada de eso.&lt;/p&gt;
&lt;h3&gt;Lógica de negocio&lt;/h3&gt;
&lt;p&gt;La lógica central que le da valor a tu sistema (los algoritmos de precios, las reglas de matching, los controles de cumplimiento, los cálculos propios del dominio) es exactamente donde la IA es más peligrosa.&lt;/p&gt;
&lt;p&gt;¿Por qué? Porque genera código que &lt;em&gt;parece&lt;/em&gt; correcto. Compila. Pasa las pruebas básicas. Pero codifica supuestos sobre tu dominio que pueden estar sutilmente equivocados. Y los bugs sutiles en la lógica de negocio son de los que cuestan dinero real y erosionan la confianza de los usuarios.&lt;/p&gt;
&lt;p&gt;Tuve un caso en el que el código generado por IA para un cálculo de descuentos manejaba bien los descuentos porcentuales, pero los aplicaba en el orden equivocado cuando se combinaban con descuentos de monto fijo. El código se veía limpio. Las pruebas, que también generó la IA, pasaban. El bug llegó a staging y habría costado miles en órdenes con precios incorrectos.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tu lógica de negocio es tu ventaja competitiva. Escríbela tú. Entiende cada línea.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Seguridad&lt;/h3&gt;
&lt;p&gt;No dejes que la IA escriba tus flujos de autenticación, tu lógica de autorización, tu validación de entradas ni nada relacionado con la seguridad. Los modelos de IA se entrenan con un corpus enorme de código, y una parte importante de ese código tiene vulnerabilidades.&lt;/p&gt;
&lt;p&gt;La IA va a generar consultas SQL sin parametrizar. Va a crear validaciones de JWT que no revisan la expiración. Va a escribir manejadores de carga de archivos sin límite de tamaño ni validación de tipo. No siempre, pero sí con la frecuencia suficiente como para que no puedas confiar en ella.&lt;/p&gt;
&lt;p&gt;La seguridad es un terreno en el que &quot;funciona la mayoría de las veces&quot; es completamente inaceptable.&lt;/p&gt;
&lt;h2&gt;La diferencia entre &quot;asistido por IA&quot; y &quot;generado por IA&quot;&lt;/h2&gt;
&lt;p&gt;Este es el marco que uso para pensar la IA en mi flujo de trabajo, y creo que es la distinción más importante de toda esta conversación.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Asistido por IA:&lt;/strong&gt; yo manejo. Entiendo el problema, sé cómo debería verse la solución y uso la IA para acelerar las partes mecánicas de la implementación. Reviso cada línea, tomo las decisiones y la IA se encarga del tipeo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generado por IA:&lt;/strong&gt; la IA maneja. Describo lo que quiero, la IA produce la solución y la entrego con una revisión mínima. En esencia, estoy tercerizando el entendimiento.&lt;/p&gt;
&lt;p&gt;El primer enfoque te hace más rápido. El segundo te vuelve peligroso.&lt;/p&gt;
&lt;p&gt;Mi regla es esta: &lt;strong&gt;si no puedes explicar cada línea del código generado por IA, no deberías entregarlo.&lt;/strong&gt; No &quot;entiendo más o menos lo que hace&quot;: deberías poder explicar &lt;em&gt;por qué&lt;/em&gt; se tomó cada decisión, cuáles son los casos borde y cómo falla.&lt;/p&gt;
&lt;p&gt;Si no puedes hacerlo, no tienes una solución. Tienes un pasivo.&lt;/p&gt;
&lt;h2&gt;Cómo uso la IA en el día a día&lt;/h2&gt;
&lt;p&gt;Este es mi flujo de trabajo real:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Acelerar la revisión de código por la mañana:&lt;/strong&gt; uso la IA para obtener un resumen rápido de los PRs grandes antes de hacer mi revisión detallada. &quot;Resume los cambios de este PR y señala posibles problemas.&quot; Eso me da un mapa antes de meterme en el terreno. Sigo revisando cada archivo, pero sé dónde concentrar la atención.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Generación de pruebas:&lt;/strong&gt; cuando escribo un servicio nuevo, primero hago la implementación y después le pido a la IA el andamiaje de las pruebas. Las modifico mucho: agrego casos borde, corrijo aserciones y me aseguro de que las pruebas validen comportamiento y no implementación. Pero el andamiaje ahorra entre un 30 y un 40 % del tiempo.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explorar librerías nuevas:&lt;/strong&gt; antes de leer la documentación completa, le pido a la IA que me explique los conceptos centrales y los patrones comunes. Eso me da un modelo mental que hace la documentación más digerible. Siempre seguido de leer la documentación real.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Acelerar el boilerplate:&lt;/strong&gt; DTOs, mappers, configuración, archivos de migración. Cualquier cosa en la que el patrón esté establecido y yo esté traduciendo una estructura conocida a código.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Para qué nunca uso la IA:&lt;/strong&gt; decisiones de arquitectura, diseño de esquemas de base de datos, lógica de seguridad, caminos de código críticos para el rendimiento, implementación de reglas de negocio o cualquier código en el que equivocarse sutilmente tenga consecuencias importantes.&lt;/p&gt;
&lt;h2&gt;Por qué los fundamentos importan MÁS con IA, no menos&lt;/h2&gt;
&lt;p&gt;Esta es la postura que puede resultar polémica: &lt;strong&gt;la IA hace que los fundamentos de la ingeniería de software sean más importantes, no menos.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Si no entiendes los patrones de diseño, no puedes evaluar si el patrón que sugiere la IA es el adecuado. Si no entiendes los índices de base de datos, no vas a notar que la consulta que generó la IA va a hacer un escaneo completo sobre una tabla de un millón de filas. Si no entiendes la semántica de HTTP, no vas a detectar que la IA usó POST donde correspondía PUT.&lt;/p&gt;
&lt;p&gt;La IA amplifica lo que ya sabes. Si entiendes el diseño de sistemas en profundidad, la IA te hace muchísimo más rápido. Si no entiendes los fundamentos, la IA te hace muchísimo más eficiente produciendo software roto.&lt;/p&gt;
&lt;p&gt;Los desarrolladores a los que mejor les va con las herramientas de IA son los que ya tenían fundamentos sólidos. Usan la IA como una herramienta eléctrica, no como una muleta. Saben lo suficiente para:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Evaluar&lt;/strong&gt; críticamente lo que produce la IA&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detectar&lt;/strong&gt; errores sutiles que a primera vista parecen correctos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dirigir&lt;/strong&gt; a la IA hacia la solución correcta haciendo mejores preguntas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saber cuándo NO usarla&lt;/strong&gt;, que quizás sea la habilidad más importante de todas&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;El peligro de la zona de confort&lt;/h2&gt;
&lt;p&gt;Veo un patrón que me preocupa: desarrolladores que dejan de aprender porque la IA puede cubrir los huecos. No aprenden cómo funcionan las promesas porque la IA escribe su código asíncrono. No entienden los joins de SQL porque la IA genera sus consultas. No aprenden patrones de diseño porque la IA estructura su código.&lt;/p&gt;
&lt;p&gt;Eso funciona hasta que deja de funcionar. Y cuando deja de funcionar (cuando estás depurando una condición de carrera a las 2 de la mañana, diseñando un sistema que tiene que soportar 10 veces el tráfico o intentando descubrir por qué el código generado por IA provoca una fuga de memoria) no hay atajos. O entiendes los fundamentos o no los entiendes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;La IA es una herramienta. El ingeniero eres tú. La herramienta no reemplaza la habilidad: la exige.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Reflexiones finales&lt;/h2&gt;
&lt;p&gt;La IA en el flujo de trabajo del desarrollador es realmente útil. Soy más productivo con ella que sin ella. Eso no es hype: es mi experiencia honesta después de un uso diario intensivo.&lt;/p&gt;
&lt;p&gt;Pero las partes útiles son las mundanas. El boilerplate. El andamiaje. La exploración. La documentación. Lo que nunca fue la parte difícil de la ingeniería de software.&lt;/p&gt;
&lt;p&gt;Las partes difíciles (entender los problemas a fondo, tomar decisiones de arquitectura con sus trade-offs, escribir lógica de negocio correcta, construir sistemas seguros, guiar a los equipos) siguen siendo habilidades completamente humanas. Y no veo que eso vaya a cambiar pronto.&lt;/p&gt;
&lt;p&gt;Usa las herramientas de IA. Vuélvete más rápido en las partes mecánicas del desarrollo. Pero invierte todavía más tiempo en entender los fundamentos. Porque los desarrolladores a los que les va a ir bien en un mundo potenciado por la IA no son los que mejor escriben prompts. Son los que mejor piensan.&lt;/p&gt;
&lt;p&gt;La IA es la herramienta eléctrica. El artesano sigues siendo tú. No olvides cuál de los dos importa más.&lt;/p&gt;
</content:encoded></item><item><title>Cómo construir side projects mientras trabajas a tiempo completo: un sistema, no motivación</title><link>https://hrdi.dev/es/blog/side-projects-while-working-fulltime/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/side-projects-while-working-fulltime/</guid><description>Por qué la motivación es poco confiable y cómo un sistema de hábitos pequeños y constantes te permite entregar side projects sin quemarte.</description><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Seré sincero contigo: la motivación es una pésima estrategia para entregar side projects. Llevo años construyendo cosas por mi cuenta mientras desempeño un rol exigente de tech lead a tiempo completo, y los proyectos que de verdad salieron adelante no tuvieron nada que ver con arranques de inspiración. Fueron el resultado de sistemas aburridos y repetibles.&lt;/p&gt;
&lt;p&gt;Si esperas &quot;sentirte motivado&quot; para trabajar en tu side project después de un día largo, vas a esperar para siempre. Esto es lo que realmente funciona.&lt;/p&gt;
&lt;h2&gt;Por qué la motivación falla y los sistemas ganan&lt;/h2&gt;
&lt;p&gt;La motivación es una emoción. Llega a su punto máximo después de ver un video de YouTube de alguien que construyó un SaaS en un fin de semana y se desploma en el momento en que te topas con una migración de base de datos complicada a las 9 de la noche de un martes. No puedes construir nada significativo solo con emociones.&lt;/p&gt;
&lt;p&gt;Los sistemas, en cambio, son decisiones que tomas una vez y luego sigues. Eliminan la necesidad de decidir cada día si &quot;tienes ganas&quot; de trabajar. La decisión ya está tomada.&lt;/p&gt;
&lt;p&gt;Mi sistema es simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bloques nocturnos de dos horas&lt;/strong&gt;, tres noches por semana (martes, miércoles y jueves)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sábados por la mañana&lt;/strong&gt;, de 7 a 11 AM — mi ventana más productiva&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;El domingo es libre.&lt;/strong&gt; Sin excepciones. Tu cerebro necesita recuperarse.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La clave: esos bloques de dos horas son sagrados. No reviso Slack, no arreglo &quot;rápidamente&quot; un bug de producción, no me pierdo en el doom-scrolling. El bloque empieza, abro mi editor y trabajo en la siguiente tarea. Eso es todo.&lt;/p&gt;
&lt;p&gt;En seis meses, esos bloques suman aproximadamente 400 horas. Es suficiente para entregar un producto real. No un juguete: un producto real y utilizable.&lt;/p&gt;
&lt;h2&gt;Elegir proyectos de forma estratégica&lt;/h2&gt;
&lt;p&gt;Aquí es donde la mayoría de los desarrolladores se equivoca: construyen lo que suena genial en lugar de lo que resuelve un problema real. Yo también he caído en eso. Una vez pasé tres meses construyendo un gestor de tareas &quot;revolucionario&quot; que nadie necesitaba, empezando por mí.&lt;/p&gt;
&lt;p&gt;Los mejores side projects nacen de tus propios puntos de dolor. Ya entiendes el problema a fondo, eres tu propio primer usuario y puedes validar al instante si la solución funciona.&lt;/p&gt;
&lt;p&gt;Algunas de mis mejores ideas surgieron de fricciones que viví en el trabajo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Una herramienta de legaltech nacida de ver a abogados procesar a mano documentos que podrían automatizarse en minutos&lt;/li&gt;
&lt;li&gt;Una automatización interna que le ahorró a mi equipo horas de tareas repetitivas de deploy&lt;/li&gt;
&lt;li&gt;Un dashboard de monitoreo porque las herramientas existentes eran demasiado caras o demasiado complejas para equipos pequeños&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;El filtro que uso ahora:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;¿Este problema le cuesta a alguien (a mí o a alguien que conozco) tiempo o dinero reales?&lt;/li&gt;
&lt;li&gt;¿Puedo construir una V1 utilizable en menos de 8 semanas de tiempo de side project?&lt;/li&gt;
&lt;li&gt;¿Me interesa lo suficiente como para usarlo yo mismo durante meses?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Si alguna respuesta es no, sigo adelante. Las ideas son infinitas. Tu tiempo es el recurso escaso.&lt;/p&gt;
&lt;h2&gt;La mentalidad de MVP para quien construye en solitario&lt;/h2&gt;
&lt;p&gt;Cuando eres un equipo de una sola persona, tu MVP tiene que ser implacablemente pequeño. No hablo de la versión de MVP del pitch deck de una startup. Me refiero a esto: ¿cuál es lo mínimo absoluto que entrega valor a una persona (tú)?&lt;/p&gt;
&lt;p&gt;Para un SaaS que estaba construyendo para automatizar la revisión de contratos, mi MVP fue:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Subir un PDF&lt;/li&gt;
&lt;li&gt;Extraer cláusulas clave con un pipeline de NLP sencillo&lt;/li&gt;
&lt;li&gt;Mostrarlas en una lista&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sin cuentas de usuario. Sin sistema de pagos. Sin una interfaz bonita. Solo la propuesta de valor central funcionando de punta a punta. Lo construí en tres fines de semana.&lt;/p&gt;
&lt;p&gt;Todo lo demás —auth, facturación, un frontend como Dios manda— vino después, cuando confirmé que la idea central realmente funcionaba y que la gente la quería.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Mi checklist de MVP antes de escribir una sola línea de código:
1. Definir la ÚNICA cosa que esto hace
2. Dibujar el camino feliz (literalmente en papel)
3. Listar lo que NO voy a construir en la V1
4. Fijar una fecha límite (normalmente 4-6 sesiones de fin de semana)
5. Entregarlo, aunque esté feo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La lista de &quot;lo que no voy a construir&quot; es más importante que la lista de funcionalidades. Te mantiene honesto cuando el scope creep te susurra dulces palabras a las 10 de la noche.&lt;/p&gt;
&lt;h2&gt;Decisiones de stack tecnológico para ganar velocidad&lt;/h2&gt;
&lt;p&gt;Aquí es donde los desarrolladores con experiencia tienen en realidad una desventaja. Conocemos demasiadas herramientas y nos tienta elegir la &quot;correcta&quot; para cada situación. En un side project, el stack correcto es aquel con el que puedes avanzar más rápido.&lt;/p&gt;
&lt;p&gt;Mi stack para side projects:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Backend:&lt;/strong&gt; Go o Node.js con TypeScript, según lo que encaje mejor con el problema&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frontend:&lt;/strong&gt; Astro para sitios de contenido, Angular o React para aplicaciones (lo que domine más en ese momento)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Base de datos:&lt;/strong&gt; PostgreSQL. Siempre. No voy a aprender una base de datos nueva a las 9 de la noche&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hosting:&lt;/strong&gt; Railway o Fly.io para backends, Vercel o Cloudflare Pages para frontends&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auth:&lt;/strong&gt; Lucia o un proveedor externo. Nunca implementes el tuyo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El principio: &lt;strong&gt;minimiza las decisiones que no se relacionan directamente con tu problema central.&lt;/strong&gt; Cada hora que pasas debatiendo sobre ORMs es una hora que no dedicas a la funcionalidad que importa.&lt;/p&gt;
&lt;p&gt;He visto desarrolladores pasar semanas configurando el monorepo &quot;perfecto&quot; con Nx, Turborepo, configuraciones personalizadas de ESLint y pipelines de CI/CD, para un proyecto que nunca entregó una sola funcionalidad. No seas esa persona.&lt;/p&gt;
&lt;h2&gt;Cómo lidiar con el burnout&lt;/h2&gt;
&lt;p&gt;Hablemos del elefante en la habitación. Trabajar a tiempo completo y construir por tu cuenta es agotador. Si no administras tu energía de forma intencional, te vas a quemar. Yo ya pasé por ahí.&lt;/p&gt;
&lt;p&gt;Señales que aprendí a reconocer:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Temerle al bloque de la noche en lugar de sentirme neutral al respecto&lt;/li&gt;
&lt;li&gt;Escribir código que sé que es malo, pero sin importarme lo suficiente como para arreglarlo&lt;/li&gt;
&lt;li&gt;Saltarme bloques durante dos semanas seguidas y poner excusas&lt;/li&gt;
&lt;li&gt;Sentir resentimiento hacia el side project&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cuando noto estas señales, sigo un protocolo estricto:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Me tomo una semana completa libre&lt;/strong&gt; del side project. Sin culpa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reevalúo&lt;/strong&gt; si todavía me importa el problema. Si no, mato el proyecto. En serio.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reduzco el calendario&lt;/strong&gt; de forma temporal. Dos bloques en lugar de tres. Una mañana de sábado en lugar de la sesión completa.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hablo con alguien&lt;/strong&gt; que no sea desarrollador. La perspectiva externa de mi pareja me ha salvado de varios callejones sin salida.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;El objetivo no es esforzarte hasta romperte. El objetivo es construir de forma constante durante años. No puedes hacerlo si te apagas cada tres meses.&lt;/p&gt;
&lt;h2&gt;El efecto compuesto&lt;/h2&gt;
&lt;p&gt;Esto es lo que nadie te cuenta sobre los side projects: el valor no está solo en el proyecto en sí. Está en lo que se acumula a su alrededor.&lt;/p&gt;
&lt;p&gt;Después de dos años de trabajo constante en side projects, tenía:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un portafolio de productos reales y entregados (no clones de tutoriales)&lt;/li&gt;
&lt;li&gt;Conocimiento profundo en áreas ajenas a mi trabajo diario (legaltech, automatización, NLP)&lt;/li&gt;
&lt;li&gt;Una red de personas a quienes mis proyectos les resultaron útiles&lt;/li&gt;
&lt;li&gt;Opiniones probadas en batalla sobre arquitectura, porque cometí todos los errores yo mismo&lt;/li&gt;
&lt;li&gt;Confianza para tomar decisiones técnicas más rápido, lo cual mejoró directamente mi trabajo de tiempo completo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cada proyecto me enseñó algo que hizo más rápido al siguiente. Mi primer intento de SaaS tardó seis meses y nunca se lanzó. El más reciente pasó de la idea a tener usuarios de pago en cinco semanas. La misma cantidad de bloques nocturnos, solo que con la experiencia acumulada.&lt;/p&gt;
&lt;h2&gt;Una reflexión final&lt;/h2&gt;
&lt;p&gt;Construir side projects mientras trabajas a tiempo completo no se trata de ser una persona de la &quot;cultura del hustle&quot;. Se trata de tener control sobre tu crecimiento como ingeniero y, potencialmente, sobre tu futuro financiero.&lt;/p&gt;
&lt;p&gt;Los desarrolladores que más admiro no son los que se matan trabajando 80 horas a la semana. Son los que se presentan con constancia, construyen cosas que les importan y saben cuándo descansar.&lt;/p&gt;
&lt;p&gt;Arma tu sistema. Protege tus bloques. Entrega algo pequeño. Y luego hazlo de nuevo.&lt;/p&gt;
&lt;p&gt;La motivación va y viene. El sistema se queda.&lt;/p&gt;
</content:encoded></item><item><title>De desarrollador a arquitecto: el cambio de mentalidad del que nadie habla</title><link>https://hrdi.dev/es/blog/developer-to-architect-mindset/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/developer-to-architect-mindset/</guid><description>La transición de escribir código a diseñar sistemas no consiste en aprender nuevas herramientas, sino en cambiar de raíz la forma en que piensas sobre el software.</description><pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hay un momento en la carrera de todo desarrollador senior en el que te das cuenta de que escribir gran código ya no es suficiente. Los problemas que enfrentas no se resuelven con un algoritmo ingenioso ni con un patrón de diseño bien ubicado. Requieren que tomes distancia (mucha distancia) y pienses en los sistemas como un todo.&lt;/p&gt;
&lt;p&gt;Esa transición de desarrollador a arquitecto me desordenó la cabeza durante un tiempo. No porque sea técnicamente más difícil, sino porque exige que desaprendas hábitos que te hicieron exitoso como desarrollador. Esto es lo que ese cambio significa en realidad.&lt;/p&gt;
&lt;h2&gt;Pensar en trade-offs, no en absolutos&lt;/h2&gt;
&lt;p&gt;Como desarrollador, solía pensar en términos de &quot;la forma correcta&quot; de hacer las cosas. Las bases de datos normalizadas son mejores. Los microservicios son el futuro. El tipado fuerte previene bugs. Todo eso me parecía verdad universal.&lt;/p&gt;
&lt;p&gt;Como arquitecto, aprendí que casi nada es universalmente cierto. Todo es un trade-off, y tu trabajo es elegir el que menos duele en tu contexto específico.&lt;/p&gt;
&lt;p&gt;¿Deberías usar microservicios? Depende del tamaño de tu equipo, de tu capacidad de deploy y de qué tan bien entiendas los límites de tu dominio. ¿Deberías normalizar tu base de datos? Depende de tu proporción de lecturas y escrituras y de si puedes permitirte la complejidad de los joins a escala.&lt;/p&gt;
&lt;p&gt;El reflejo de &quot;depende&quot; del que se burlan los desarrolladores junior es, en realidad, sabiduría ganada a pulso. Significa que has visto suficientes sistemas triunfar y fracasar como para saber que el contexto lo determina todo.&lt;/p&gt;
&lt;p&gt;Un ejemplo real. En un proyecto del año pasado, tuve que decidir entre:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Opción A:&lt;/strong&gt; Arquitectura orientada a eventos con Kafka: técnicamente elegante, excelente para escalar, compleja de operar&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Opción B:&lt;/strong&gt; Llamadas REST simples entre servicios con una base de datos PostgreSQL compartida: &quot;aburrida&quot;, pero el equipo podía entregarla y mantenerla&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Elegí la Opción B. El equipo eran cuatro desarrolladores, ninguno tenía experiencia con Kafka y teníamos un plazo de tres meses. La Opción A habría sido arquitectónicamente &quot;mejor&quot; en el vacío. En nuestro contexto, habría sido un desastre.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;El trabajo del arquitecto no es elegir la mejor solución. Es elegir la mejor solución para este equipo, este calendario y este conjunto de restricciones.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;El peligro de la arquitectura de torre de marfil&lt;/h2&gt;
&lt;p&gt;He trabajado con arquitectos que diseñan sistemas desde una pizarra y nunca miran el código. Producen diagramas hermosos, ADRs exhaustivos y arquitecturas que se desmoronan en cuanto los desarrolladores empiezan a implementarlas.&lt;/p&gt;
&lt;p&gt;Los mejores arquitectos que conozco, aquellos cuyos sistemas realmente funcionan en producción, todavía leen código a diario. No porque desconfíen de sus equipos, sino porque el código es donde la arquitectura se encuentra con la realidad.&lt;/p&gt;
&lt;p&gt;Cuando reviso pull requests, no busco problemas de estilo ni nombres de variables. Busco:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;¿Los límites que diseñé realmente se sostienen, o los desarrolladores los están rodeando?&lt;/li&gt;
&lt;li&gt;¿La abstracción que propuse realmente reduce la complejidad, o la aumenta?&lt;/li&gt;
&lt;li&gt;¿Están surgiendo en el código patrones que sugieren que la arquitectura necesita evolucionar?&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Este es un code smell que busco en las revisiones de código.
// Cuando ves que un servicio importa directamente los tipos
// internos de otro servicio, se ha violado el límite arquitectónico.

import (
    &quot;github.com/company/user-service/internal/models&quot;  // BANDERA ROJA
    &quot;github.com/company/order-service/domain&quot;
)

// Cómo debería verse:
import (
    &quot;github.com/company/shared/contracts&quot;  // Interfaz compartida
    &quot;github.com/company/order-service/domain&quot;
)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Si tu arquitectura no puede sobrevivir al contacto con desarrolladores reales escribiendo código real bajo plazos reales, no es arquitectura: es ficción.&lt;/p&gt;
&lt;h2&gt;La comunicación es la herramienta principal del arquitecto&lt;/h2&gt;
&lt;p&gt;Esto me tomó por sorpresa. Pensaba que convertirme en arquitecto significaba dedicar más tiempo a problemas técnicos. En realidad, paso más tiempo comunicando que programando.&lt;/p&gt;
&lt;p&gt;Una decisión de arquitectura que el equipo no entiende no vale nada. Necesitas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Explicar el &quot;por qué&quot;&lt;/strong&gt; detrás de cada decisión importante. No solo qué estamos haciendo, sino por qué este enfoque y no las alternativas&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Crear modelos mentales compartidos.&lt;/strong&gt; Cuando digo &quot;bounded context&quot;, todos los desarrolladores del equipo deberían imaginar lo mismo&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escuchar las objeciones.&lt;/strong&gt; Los desarrolladores en el terreno ven cosas que tú no puedes ver desde el nivel de la arquitectura. Si tres desarrolladores te dicen que la abstracción es dolorosa, créeles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dejarlo por escrito.&lt;/strong&gt; Los Architecture Decision Records (ADRs) no son burocracia: son la memoria de tu equipo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Una vez diseñé lo que pensaba que era un elegante sistema de event sourcing. Durante la revisión del diseño, un desarrollador de nivel intermedio preguntó: &quot;¿Cómo depuramos esto cuando algo falle a las 3 AM?&quot;. No tenía una buena respuesta. Esa pregunta nos ahorró meses de dolor. El desarrollador no necesitaba entender la teoría de event sourcing; necesitaba entender la realidad operativa. Y tenía razón.&lt;/p&gt;
&lt;h2&gt;Cómo DDD cambió mi forma de pensar&lt;/h2&gt;
&lt;p&gt;Domain-Driven Design fue la mayor influencia en mi transición hacia el pensamiento arquitectónico. No por los patrones tácticos (aggregates, repositories, value objects), sino por los estratégicos.&lt;/p&gt;
&lt;p&gt;DDD me enseñó a:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Empezar por el dominio, no por la tecnología.&lt;/strong&gt; Antes de pensar en bases de datos o frameworks, dedico tiempo a entender el problema de negocio. ¿Cuáles son las entidades? ¿Cuáles son los procesos? ¿Dónde están los límites?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Hablar el lenguaje del dominio.&lt;/strong&gt; Si el negocio lo llama &quot;póliza&quot; y tu código lo llama &quot;contrato&quot;, has creado una capa de traducción que causará bugs y malentendidos para siempre.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Identificar bounded contexts.&lt;/strong&gt; Este es el concepto más poderoso de DDD. No todo necesita compartir el mismo modelo. El &quot;Usuario&quot; en el contexto de facturación es distinto del &quot;Usuario&quot; en el contexto social, y eso no solo está bien: es lo correcto.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# Antes de pensar con DDD:
Un modelo User gigante con 47 campos que sirve a 6 contextos distintos

# Después de pensar con DDD:
- Billing::Customer (email, payment_method, subscription_tier)
- Social::Profile (display_name, avatar, bio)
- Auth::Identity (credentials, mfa_settings, sessions)
- Support::Requester (name, contact_info, ticket_history)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cada contexto es dueño de su modelo. Se comunican mediante interfaces bien definidas. Los cambios en uno no se propagan a los demás. Esto no es sobreingeniería: es reconocer que un único modelo que sirve a todos en realidad no sirve bien a nadie.&lt;/p&gt;
&lt;h2&gt;Tomar decisiones con información incompleta&lt;/h2&gt;
&lt;p&gt;Como desarrollador, podía reunir todos los requisitos, entender el alcance completo y luego construir. Como arquitecto, casi nunca tengo información completa cuando debo tomar decisiones.&lt;/p&gt;
&lt;p&gt;Eliges un sistema de mensajería antes de conocer los requisitos finales de escala. Diseñas los límites de los servicios antes de que el modelo de negocio esté plenamente validado. Eliges un proveedor de nube antes de saber qué regiones vas a necesitar.&lt;/p&gt;
&lt;p&gt;Esto solía paralizarme. ¿Y si me equivoco? A esta escala, equivocarse sale caro.&lt;/p&gt;
&lt;p&gt;Esto es lo que he aprendido: &lt;strong&gt;el costo de no decidir casi siempre es mayor que el costo de una decisión equivocada.&lt;/strong&gt; Una decisión equivocada se puede corregir. La indecisión bloquea a equipos enteros.&lt;/p&gt;
&lt;p&gt;Mi marco para decidir bajo incertidumbre:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Haz la decisión reversible si es posible.&lt;/strong&gt; Usa abstracciones que te permitan cambiar las implementaciones más adelante. Aquí es donde la arquitectura de puertos y adaptadores brilla&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fija una fecha de revisión.&lt;/strong&gt; &quot;Por ahora vamos con PostgreSQL. En tres meses evaluaremos si necesitamos migrar a una base de datos distribuida según datos reales de carga&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documenta tus supuestos.&lt;/strong&gt; Cuando escribes &quot;Asumimos menos de 10 000 usuarios concurrentes en el Año 1&quot;, creas una alarma que le avisa a tu yo del futuro cuándo hay que revisar la decisión&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acepta que algunas decisiones serán erróneas.&lt;/strong&gt; Diseña tu sistema para que las decisiones equivocadas no lo tumben todo. El bajo acoplamiento no es solo una palabra de moda: es un seguro&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;El miedo a equivocarse a escala&lt;/h2&gt;
&lt;p&gt;Cuando eres desarrollador y escribes un bug, afecta a una funcionalidad. Quizás unos pocos usuarios ven un mensaje de error. Lo corriges, haces deploy y sigues adelante.&lt;/p&gt;
&lt;p&gt;Cuando eres arquitecto y tomas una mala decisión estructural, puede afectar a todos los equipos, todos los servicios y todos los deploys durante meses o años. Ese peso es real, y fingir que no existe no ayuda.&lt;/p&gt;
&lt;p&gt;Lo manejo así:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Construyendo pruebas de concepto&lt;/strong&gt; antes de comprometerme con decisiones arquitectónicas importantes. Un spike de dos semanas es barato comparado con una reescritura de seis meses&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pidiendo opinión a otros ingenieros con experiencia.&lt;/strong&gt; Las revisiones de arquitectura no son señal de debilidad, sino de madurez&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Estudiando los fracasos.&lt;/strong&gt; Aprendo más de los post-mortems que de las historias de éxito. Cuando un sistema falla, las debilidades de la arquitectura se hacen visibles&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;De &quot;yo lo construyo&quot; a &quot;lo diseño para que otros puedan construirlo&quot;&lt;/h2&gt;
&lt;p&gt;La parte más difícil de esta transición fue soltar el código. Como desarrollador, mi identidad estaba ligada a lo que construía. Como arquitecto, mi valor viene de permitir que otros construyan bien.&lt;/p&gt;
&lt;p&gt;Esto significa:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Escribir especificaciones claras en lugar de escribir tú mismo el código&lt;/li&gt;
&lt;li&gt;Confiar en que tu equipo tomará las decisiones de implementación dentro de los límites que has fijado&lt;/li&gt;
&lt;li&gt;Medir el éxito por el resultado del equipo, no por el personal&lt;/li&gt;
&lt;li&gt;Estar tranquilo con soluciones que no son como tú las habrías programado, siempre que respeten la arquitectura&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Es un tipo distinto de satisfacción. En lugar de mirar una funcionalidad y pensar &quot;yo construí eso&quot;, miras un sistema y piensas &quot;yo diseñé la estructura que permitió que doce personas construyeran eso sin pisarse unas a otras&quot;.&lt;/p&gt;
&lt;h2&gt;Una reflexión final&lt;/h2&gt;
&lt;p&gt;La transición de desarrollador a arquitecto no es un ascenso: es un cambio de carrera. Las habilidades que te hicieron un gran desarrollador (concentración profunda, atención al detalle, resolución práctica de problemas) no desaparecen, pero pasan a ser habilidades de apoyo en lugar de principales.&lt;/p&gt;
&lt;p&gt;Tus habilidades principales pasan a ser: pensamiento sistémico, comunicación, toma de decisiones bajo incertidumbre y la humildad de saber que tu hermosa arquitectura será doblada, rota y parchada por la realidad.&lt;/p&gt;
&lt;p&gt;¿Y sinceramente? Eso es lo que lo hace fascinante. Ya no estás construyendo software. Estás diseñando el entorno donde se construye el software. Haz bien eso, y todo lo demás viene solo.&lt;/p&gt;
</content:encoded></item><item><title>Lo que entrenar para una media maratón me enseñó sobre ingeniería</title><link>https://hrdi.dev/es/blog/discipline-training-engineering/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/discipline-training-engineering/</guid><description>Paralelismos entre el entrenamiento atlético híbrido y la ingeniería de software: sobrecarga progresiva, días de descanso, chocar contra el muro y por qué la constancia siempre le gana a la intensidad.</description><pubDate>Fri, 30 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;La línea de salida que nadie ve&lt;/h2&gt;
&lt;p&gt;Me inscribí en mi primera media maratón pensando que sería algo divertido para un fin de semana. Algo para tachar de una lista. Lo que no esperaba era cuánto iba a reflejar el entrenamiento los mismos desafíos que enfrento cada día como Tech Lead construyendo sistemas en producción.&lt;/p&gt;
&lt;p&gt;Llevo más de un año con entrenamiento híbrido (gimnasio tres días a la semana combinado con progresiones de carrera de 10K a 21K). Y alrededor del cuarto mes empecé a notar que casi todos los principios que me hicieron mejor corredor también me hicieron mejor ingeniero. No de una forma bonita, de póster motivacional. De una forma real y práctica, de esas que cambian cómo planifico los sprints.&lt;/p&gt;
&lt;p&gt;Vamos por partes.&lt;/p&gt;
&lt;h2&gt;Sobrecarga progresiva = complejidad incremental&lt;/h2&gt;
&lt;p&gt;En el entrenamiento, la sobrecarga progresiva significa que no pasas de correr 5K a 21K en una semana. Aumentas un 10 % de volumen por semana. Te ganas el siguiente nivel.&lt;/p&gt;
&lt;p&gt;En ingeniería, es el mismo principio detrás de la entrega incremental. No pasas de un monolito a microservicios en un sprint. Extraes un bounded context, validas que funciona y luego extraes el siguiente. Cada vez que he visto a un equipo intentar una migración &quot;big bang&quot;, terminó en lágrimas. Todas y cada una de las veces.&lt;/p&gt;
&lt;p&gt;El cuerpo se adapta al estrés de forma gradual. También lo hace un codebase. También lo hace un equipo.&lt;/p&gt;
&lt;p&gt;Cuando incorporo a nuevos ingenieros, les doy tareas progresivamente más difíciles, no porque sea un guardián del acceso, sino porque el contexto se construye sobre el contexto. Necesitas entender el modelo de datos antes de tocar el pipeline de eventos. La sobrecarga progresiva no es solo un principio de entrenamiento. Es un principio de liderazgo.&lt;/p&gt;
&lt;h2&gt;Días de descanso = prevenir el burnout&lt;/h2&gt;
&lt;p&gt;Aquí hay algo que la cultura del gimnasio entiende mal y la cultura tech todavía peor: descansar no es pereza. El descanso es donde ocurre la adaptación.&lt;/p&gt;
&lt;p&gt;Tus músculos no crecen durante el entrenamiento. Crecen durante la recuperación. Tus mejores ideas de ingeniería no llegan durante una sesión de código de 12 horas. Llegan en la ducha a la mañana siguiente, después de que tu cerebro ha tenido tiempo de procesar.&lt;/p&gt;
&lt;p&gt;Respeto los días de descanso en mi plan de entrenamiento con la misma disciplina con la que exijo un ritmo sostenible a mis equipos. Nada de sprints heroicos. Nada de &quot;aguantemos el fin de semana&quot;. He visto lo que pasa cuando te saltas los días de descanso en el entrenamiento: te lesionas. He visto lo que pasa cuando los equipos se saltan el descanso: obtienes rotación, bugs y gente que deja de importarle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Idea clave:&lt;/strong&gt; Si tu equipo trabaja horas extra de forma constante, no tienes un problema de capacidad. Tienes un problema de planificación.&lt;/p&gt;
&lt;h2&gt;El plan de entrenamiento = el plan del sprint&lt;/h2&gt;
&lt;p&gt;Un buen plan de entrenamiento tiene:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Periodización:&lt;/strong&gt; ciclos de acumulación y de descarga&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Especificidad:&lt;/strong&gt; entrenamientos que apuntan a las exigencias reales de la carrera&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hitos medibles:&lt;/strong&gt; pruebas contrarreloj, marcadores de distancia&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flexibilidad:&lt;/strong&gt; ajustes según cómo responde el cuerpo&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reemplaza &quot;entrenamientos&quot; por &quot;tareas&quot; y &quot;carrera&quot; por &quot;release&quot; y tienes una sólida filosofía de planificación de sprints. Los mejores planes de sprint que he llevado tenían todos estos elementos. Los peores eran rígidos, poco realistas e ignoraban el feedback del equipo.&lt;/p&gt;
&lt;p&gt;Antes planificaba los sprints como planifica un principiante su entrenamiento: meter tanto como sea posible en cada semana. Ahora planifico como un corredor con experiencia: dejo espacio para lo inesperado, incluyo la recuperación y siempre sé cuál es el entrenamiento prioritario de esa semana.&lt;/p&gt;
&lt;h2&gt;La constancia le gana a la intensidad&lt;/h2&gt;
&lt;p&gt;Esto me cambió todo.&lt;/p&gt;
&lt;p&gt;Correr 5K cuatro veces por semana te hará más rápido que correr 15K una vez por semana y desplomarte. Desplegar a diario cambios pequeños y bien probados te llevará más lejos que releases masivos cada dos semanas.&lt;/p&gt;
&lt;p&gt;En el entrenamiento, la constancia construye tu base aeróbica. En ingeniería, la constancia construye confianza en el deploy. Los equipos que lidero y que entregan poco y a menudo tienen menos incidentes, tiempos de recuperación más rápidos y (esta es la parte contraintuitiva) más producción total que los equipos que agrupan todo en grandes releases.&lt;/p&gt;
&lt;p&gt;Lo mismo aplica al aprendizaje. Leer 30 minutos de documentación cada día supera a una maratón de fin de semana. Escribir un PR pequeño al día supera a un monstruo de 2000 líneas un viernes por la tarde.&lt;/p&gt;
&lt;h2&gt;El muro del km 18 = el muro del mes 6&lt;/h2&gt;
&lt;p&gt;Todo corredor de fondo conoce &quot;el muro&quot;. En una media maratón, suele aparecer alrededor del kilómetro 18. Tu glucógeno se agotó, tus piernas gritan y tu cerebro busca cualquier excusa para parar.&lt;/p&gt;
&lt;p&gt;En los proyectos largos hay un muro parecido. Alrededor del mes 5 o 6, la emoción inicial se acabó. Los problemas arquitectónicos difíciles ya están resueltos, pero ahora estás lidiando con integraciones, casos límite y el tipo de trabajo que nadie celebra. La moral del equipo baja. La gente empieza a cuestionar decisiones tomadas en el mes 1.&lt;/p&gt;
&lt;p&gt;Cómo manejas el muro en el entrenamiento:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lo esperabas.&lt;/strong&gt; Es parte del plan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tienes una estrategia de nutrición.&lt;/strong&gt; Geles, hidratación, ajustes de ritmo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Te concentras en el siguiente kilómetro, no en la meta.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cómo manejo el muro en los proyectos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Aviso al equipo con anticipación.&lt;/strong&gt; &quot;Los meses 5-6 serán duros. Es normal.&quot;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Divido el trabajo restante en hitos visibles y completables.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Celebro las pequeñas victorias en público.&lt;/strong&gt; ¿Entregamos el servicio de notificaciones? Eso es una victoria. Dilo en voz alta.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El juego mental es real. En ambos terrenos.&lt;/p&gt;
&lt;h2&gt;Seguimiento de métricas: ritmo/FC = observabilidad/monitoreo&lt;/h2&gt;
&lt;p&gt;Al correr, registro ritmo, frecuencia cardíaca, cadencia y variabilidad de la frecuencia cardíaca. No porque esté obsesionado con los números, sino porque sin datos solo estoy adivinando si mejoro o si estoy sobreentrenando.&lt;/p&gt;
&lt;p&gt;En producción, medimos latencia, tasas de error, throughput y saturación. Por la misma razón.&lt;/p&gt;
&lt;p&gt;Esto es lo que ambos me han enseñado sobre las métricas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Las métricas de vanidad son peligrosas.&lt;/strong&gt; El total de kilómetros corridos no te dice si te estás volviendo más rápido. El total de deploys no te dice si estás entregando valor.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Las tendencias importan más que las fotos instantáneas.&lt;/strong&gt; Una mala carrera no significa nada. Un pico en la tasa de errores no significa nada. ¿Pero una tendencia a la baja de tres semanas en ambas? Presta atención.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Necesitas líneas base.&lt;/strong&gt; Mi frecuencia cardíaca en reposo es de 52 lpm. Si salta a 60, algo anda mal, aunque 60 sea &quot;normal&quot; para otra persona. Lo mismo con tu latencia p99. Conoce TU normalidad.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Tu observabilidad debería contar una historia, no solo mostrar números
// Mal: alertar cuando errors &amp;gt; 100
// Bien: alertar cuando error_rate se desvíe &amp;gt; 2σ de la línea base de 7 días

alert:
  name: error_rate_anomaly
  condition: |
    abs(current_error_rate - avg_error_rate_7d) &amp;gt; 2 * stddev_error_rate_7d
  severity: warning
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Prevención de lesiones = gestión de la deuda técnica&lt;/h2&gt;
&lt;p&gt;Los corredores que se saltan el trabajo de movilidad, ignoran los dolores leves y nunca descargan terminan lesionándose. No es cuestión de si, sino de cuándo.&lt;/p&gt;
&lt;p&gt;Los ingenieros que se saltan el refactoring, ignoran los code smells y nunca pagan la deuda técnica terminan enfrentando un incidente de producción causado por el descuido acumulado. La misma certeza.&lt;/p&gt;
&lt;p&gt;El paralelismo es casi demasiado limpio:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Estirar y usar el foam roller&lt;/strong&gt; = sesiones regulares de refactoring&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ignorar el dolor de rodilla&lt;/strong&gt; = ignorar los tests inestables (flaky)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Correr con una fractura por estrés&lt;/strong&gt; = entregar sobre bugs críticos conocidos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Buenas zapatillas&lt;/strong&gt; = buenas herramientas e infraestructura&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Programo &quot;semanas de descarga&quot; para mis equipos: sprints en los que reducimos deliberadamente el trabajo de funcionalidades y nos enfocamos en herramientas, cobertura de tests, actualización de dependencias y refactoring. No es productividad perdida. Es prevención de lesiones.&lt;/p&gt;
&lt;h2&gt;El juego mental&lt;/h2&gt;
&lt;p&gt;Lo más difícil de correr 21K no es lo físico. El día de la carrera, tu cuerpo está listo. Lo difícil es la voz en tu cabeza en el kilómetro 15 diciéndote &quot;podrías simplemente caminar&quot;.&lt;/p&gt;
&lt;p&gt;Lo más difícil de liderar un proyecto largo no es lo técnico. Tu equipo tiene suficiente habilidad. Lo difícil es mantener la convicción cuando los stakeholders están ansiosos, cuando la competencia lanzó algo llamativo, cuando el atajo fácil está ahí mismo.&lt;/p&gt;
&lt;p&gt;En ambos casos, la respuesta es la misma: &lt;strong&gt;confía en el entrenamiento.&lt;/strong&gt; Confía en el proceso que construiste. Confía en el plan. Confía en que la constancia se acumula. Y cuando se ponga difícil (y se va a poner difícil), recuerda que te preparaste para este momento específicamente.&lt;/p&gt;
&lt;h2&gt;Cruzar la línea de meta&lt;/h2&gt;
&lt;p&gt;Terminé mi primera media maratón en 1:52. No es de élite. No es un récord. Pero terminé. Y terminé porque respeté el proceso, escuché a mi cuerpo, me mantuve constante y no abandoné en el kilómetro 18.&lt;/p&gt;
&lt;p&gt;Todo proyecto exitoso que he liderado siguió el mismo patrón. Nada glamoroso. Nada revolucionario. Solo esfuerzo constante, evaluación honesta, recuperación planificada y la disciplina de seguir adelante cuando dejó de ser divertido.&lt;/p&gt;
&lt;p&gt;La línea de meta, ya sea de una carrera o de un release, es solo el resultado visible de meses de trabajo invisible. ¿Y ese trabajo invisible? Ahí es donde ocurre la verdadera ingeniería.&lt;/p&gt;
&lt;p&gt;Átate los cordones. Entrega en pequeño. Mantente constante. El muro viene, y estás listo para él.&lt;/p&gt;
</content:encoded></item><item><title>Lecciones de operar sistemas en producción con equipos distribuidos</title><link>https://hrdi.dev/es/blog/lessons-distributed-teams-production/</link><guid isPermaLink="true">https://hrdi.dev/es/blog/lessons-distributed-teams-production/</guid><description>Lecciones reales de gestionar sistemas en producción a través de zonas horarias: observabilidad, gestión de incidentes, runbooks, confianza en los deploys y cómo construir una cultura de responsabilidad.</description><pubDate>Sun, 25 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;La alerta de las 3 AM que cambió mi forma de pensar sobre los equipos&lt;/h2&gt;
&lt;p&gt;Dormía profundamente cuando llegó la alerta de PagerDuty. Un servicio de checkout de un e-commerce estaba devolviendo errores 500 a un ritmo que haría que tu dashboard de monitoreo pareciera un sismógrafo. El servicio corría en EKS, la cola se acumulaba en SQS y nuestro equipo estaba repartido en tres zonas horarias: Ecuador, Argentina y España.&lt;/p&gt;
&lt;p&gt;Esa noche me enseñó más sobre sistemas distribuidos Y equipos distribuidos que cualquier libro de arquitectura. Porque cuando producción está en llamas, no le importa dónde vive tu equipo.&lt;/p&gt;
&lt;p&gt;Esto es lo que he aprendido tras años operando sistemas en producción con equipos que nunca comparten una oficina.&lt;/p&gt;
&lt;h2&gt;La observabilidad es el gran igualador&lt;/h2&gt;
&lt;p&gt;Cuando tu equipo está distribuido, nadie puede darle un toque en el hombro a un colega y preguntarle &quot;oye, ¿esto te parece raro?&quot;. La observabilidad llena ese vacío. Se convierte en el lenguaje común que tu equipo habla entre zonas horarias.&lt;/p&gt;
&lt;p&gt;Estandarizamos OpenTelemetry para la instrumentación y el logging estructurado en todas partes. No porque esté de moda, sino porque cuando un ingeniero en Argentina toma un incidente a las 9 AM de su hora, necesita ver el MISMO contexto que vio el ingeniero en Ecuador a las 2 AM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lo que realmente funciona:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Logging estructurado con correlation IDs.&lt;/strong&gt; Cada request recibe un trace ID que fluye por cada servicio, cada mensaje de cola y cada llamada a la base de datos. Cuando algo se rompe, sigues un solo ID a través de todo el sistema. Sin adivinar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Métricas a nivel de negocio, no solo de infraestructura.&lt;/strong&gt; El uso de CPU está bien. &quot;Las órdenes por minuto cayeron un 40 % en los últimos 5 minutos&quot; es accionable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dashboards que cuentan historias.&lt;/strong&gt; Construimos los dashboards alrededor de los recorridos del usuario, no de los servicios. El dashboard &quot;Checkout Health&quot; muestra todo, desde la creación del carrito hasta la confirmación del pago, en una sola vista.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// Logging estructurado que de verdad ayuda durante los incidentes
// Mal: logger.error(&quot;Payment failed&quot;)
// Bien:
logger.error({
  event: &apos;payment_processing_failed&apos;,
  orderId: order.id,
  paymentProvider: &apos;stripe&apos;,
  errorCode: err.code,
  amount: order.total,
  currency: order.currency,
  traceId: context.traceId,
  customerSegment: order.customer.tier,
  retryAttempt: attempt,
  latencyMs: Date.now() - startTime,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;La diferencia entre esas dos líneas de log es la diferencia entre &quot;algo se rompió&quot; y &quot;sé exactamente qué se rompió, para quién y cómo arreglarlo&quot;.&lt;/p&gt;
&lt;h2&gt;Gestión de incidentes entre zonas horarias&lt;/h2&gt;
&lt;p&gt;Gestionar incidentes con un equipo distribuido requiere una estructura explícita. No puedes depender de &quot;todos se conectan a una llamada&quot; cuando son las 3 AM para la mitad de tu equipo.&lt;/p&gt;
&lt;p&gt;Este es el sistema que construimos:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rotación de guardia follow-the-sun.&lt;/strong&gt; Cada zona horaria tiene un on-call primario y uno secundario. El incidente se queda con quien lo tome hasta que se resuelva O se traspase explícitamente con un resumen escrito.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Canales de incidente, no hilos.&lt;/strong&gt; Cada P1/P2 tiene un canal de Slack dedicado con un bot que registra la hora de cada actualización. Cuando despierta la siguiente zona horaria, lee el canal de arriba abajo y sabe exactamente en qué punto están las cosas.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;La regla de los 15 minutos.&lt;/strong&gt; Si no puedes identificar la causa raíz en 15 minutos, escala. No porque seas incompetente, sino porque estás gestionando el tiempo, no el ego. Cuanto antes traigas ayuda, antes se desbloquean los usuarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El traspaso es la parte más difícil. Usamos una plantilla:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Incident Handoff — [INC-2024-0847]
**Status:** Investigating / Mitigated / Monitoring
**Impact:** ~12% of checkout attempts failing in US-East
**What we know:** SQS consumer lag spiking on payment-processor queue
**What we&apos;ve tried:** Scaled consumers to 8 replicas (no improvement)
**Current hypothesis:** Stripe webhook endpoint is rate-limiting us
**Next steps:** Check Stripe dashboard, consider circuit breaker activation
**Key links:** [Dashboard](link) | [Logs query](link) | [Trace](link)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Esa plantilla nos ha ahorrado horas de &quot;espera, ¿qué pasó mientras dormía?&quot;.&lt;/p&gt;
&lt;h2&gt;Runbooks que realmente se usan&lt;/h2&gt;
&lt;p&gt;La mayoría de los runbooks son documentos de cementerio. Se escriben una vez, nunca se actualizan y nunca se consultan durante un incidente real. Yo también he sido culpable de esto. Esto es lo que cambió.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Los runbooks funcionan cuando están:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enlazados directamente desde las alertas.&lt;/strong&gt; Cada alerta de PagerDuty incluye un enlace al runbook. No lo buscas: está ahí, en la notificación.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Escritos como árboles de decisión, no como novelas.&lt;/strong&gt; &quot;Si la métrica X supera Y, haz Z.&quot; No tres párrafos de contexto.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probados con regularidad.&lt;/strong&gt; Hacemos &quot;simulacros de runbook&quot; cada mes. Eliges un escenario, sigues el runbook y compruebas si todavía funciona. Si no, lo actualizas en el momento.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A cargo del equipo de guardia.&lt;/strong&gt; Quienes usan los runbooks son quienes los mantienen. No un equipo de documentación aparte.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# runbook: payment-queue-lag
trigger: sqs_queue_depth &amp;gt; 10000 for 5m
steps:
  - check: &quot;Are consumers running?&quot;
    command: &quot;kubectl get pods -l app=payment-consumer -n production&quot;
    if_no: &quot;kubectl rollout restart deployment/payment-consumer -n production&quot;
    if_yes: &quot;continue&quot;
  - check: &quot;Is Stripe responding?&quot;
    command: &quot;curl -o /dev/null -s -w &apos;%{http_code}&apos; https://api.stripe.com/v1/health&quot;
    if_5xx: &quot;Activate circuit breaker: kubectl set env deployment/payment-consumer STRIPE_CIRCUIT=open&quot;
    if_200: &quot;continue&quot;
  - check: &quot;Consumer error logs&quot;
    command: &quot;kubectl logs -l app=payment-consumer --tail=100 | grep ERROR&quot;
    action: &quot;Analyze error pattern and escalate if unknown&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;&quot;You Build It, You Run It&quot; en la práctica&lt;/h2&gt;
&lt;p&gt;Esta filosofía suena genial en las charlas de conferencias. En la práctica, con un equipo distribuido, requiere infraestructura.&lt;/p&gt;
&lt;p&gt;No puedes esperar que los desarrolladores &quot;operen&quot; lo que construyen si:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No tienen acceso a los logs de producción&lt;/li&gt;
&lt;li&gt;No pueden hacer deploy sin abrir un ticket&lt;/li&gt;
&lt;li&gt;Las alertas llegan a un equipo de operaciones aparte en lugar de al equipo que construye&lt;/li&gt;
&lt;li&gt;No existe una forma segura de revertir sus cambios&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Lo que implementamos en realidad:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Cada equipo es dueño de las alertas, dashboards y runbooks de su servicio.&lt;/strong&gt; No ops. No SRE. El equipo que escribe el código.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pipelines de deploy con análisis canary automatizado.&lt;/strong&gt; Los ingenieros despliegan su propio código. El pipeline vigila las tasas de error durante 10 minutos. Si se disparan, hace rollback automáticamente. Esto le da a los desarrolladores confianza al desplegar sin necesidad de un guardián de operaciones.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Feature flags para todo lo que ve el usuario.&lt;/strong&gt; Despliega el código cuando quieras. Activa las funcionalidades en horario laboral, cuando el equipo está despierto y atento.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# Estrategia de deploy canary en EKS
apiVersion: flagger.app/v1beta1
kind: Canary
metadata:
  name: checkout-service
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: checkout-service
  progressDeadlineSeconds: 600
  analysis:
    interval: 1m
    threshold: 5
    maxWeight: 50
    stepWeight: 10
    metrics:
      - name: request-success-rate
        thresholdRange:
          min: 99
        interval: 1m
      - name: request-duration
        thresholdRange:
          max: 500
        interval: 1m
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Patrones de comunicación asíncrona que funcionan&lt;/h2&gt;
&lt;p&gt;Los equipos distribuidos viven y mueren por su comunicación asíncrona. Las reuniones entre zonas horarias son caras y a menudo innecesarias. Esto es lo que reemplazó a la mayoría de las nuestras:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ADRs (Architecture Decision Records) para cada decisión importante.&lt;/strong&gt; No porque amemos la documentación, sino porque cuando tu compañero en España despierta, necesita entender POR QUÉ elegimos SQS en lugar de Kafka, no solo que lo hicimos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Videos en Loom en lugar de reuniones.&lt;/strong&gt; Un recorrido en video de cinco minutos por un PR o un documento de diseño. Quien lo recibe lo ve a 1.5x en su horario. La respuesta, por escrito.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentos RFC con fechas límite explícitas.&lt;/strong&gt; &quot;Comentarios hasta el jueves EOD UTC.&quot; Sin ambigüedad.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Standups asíncronos diarios en Slack.&lt;/strong&gt; Tres líneas: qué hice, qué estoy haciendo y si tengo bloqueos. Se publican cuando empieza tu día. Se leen cuando empieza el de ellos.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La regla general: &lt;strong&gt;si la información tiene que sobrevivir más tiempo que la reunión, nunca debió ser una reunión.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;El problema de la confianza al desplegar&lt;/h2&gt;
&lt;p&gt;El momento más aterrador en la operación de un equipo distribuido es desplegar un viernes por la tarde cuando el equipo que construyó la funcionalidad está a punto de desconectarse por el fin de semana.&lt;/p&gt;
&lt;p&gt;Lo resolvimos con capas de confianza:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Feature flags&lt;/strong&gt;: despliega el código apagado y actívalo cuando el equipo esté en línea&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Canary releases&lt;/strong&gt;: desplazamiento automático de tráfico con rollback automático&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ventanas de deploy&lt;/strong&gt;: desplegamos en cualquier momento, pero las activaciones ocurren durante las horas de solapamiento (nuestra &quot;ventana dorada&quot; es de 10 AM a 1 PM UTC, cuando todas las zonas horarias están despiertas)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Notificaciones SNS&lt;/strong&gt;: cada deploy dispara una notificación al canal del equipo con el resumen del commit, el autor y las instrucciones de rollback&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;El objetivo no es frenar los deploys. Es hacer que cada deploy sea aburrido. Los deploys aburridos significan que tu sistema está sano.&lt;/p&gt;
&lt;h2&gt;Historia de guerra: la cola silenciosa&lt;/h2&gt;
&lt;p&gt;Un sábado, nuestra cola de procesamiento de órdenes (SQS) dejó de consumir mensajes. Sin errores. Sin alertas. Solo... silencio. Las órdenes se acumulaban, pero todo se veía verde en nuestros dashboards.&lt;/p&gt;
&lt;p&gt;¿La causa raíz? Un nodo de Kubernetes había sido acordonado (cordoned) durante el mantenimiento, los pods consumidores fueron desalojados y el Horizontal Pod Autoscaler tenía un mínimo de réplicas igual a cero. Así que cuando los pods fueron desalojados, el HPA estaba perfectamente conforme con cero réplicas procesando cero mensajes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lo que cambiamos después:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;El mínimo de réplicas para los consumidores críticos ahora es 2. Siempre.&lt;/li&gt;
&lt;li&gt;Agregamos una alerta de &quot;cero consumidores&quot;: si cualquier cola SQS crítica tiene cero consumidores activos por más de 2 minutos, se avisa de inmediato.&lt;/li&gt;
&lt;li&gt;Runbook actualizado el mismo día.&lt;/li&gt;
&lt;li&gt;Post-mortem compartido con todos los equipos, no solo con el afectado.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sin culpas. Solo aprendizaje.&lt;/p&gt;
&lt;h2&gt;Construir una cultura de responsabilidad, no de culpa&lt;/h2&gt;
&lt;p&gt;Los post-mortems sin culpa no son solo una buena idea. Son un mecanismo de supervivencia para los equipos distribuidos. Y la razón es esta: si la gente teme que le echen la culpa, esconde los problemas. Si esconde los problemas, los problemas crecen. Y si los problemas crecen entre zonas horarias, te enteras cuando ya es un desastre.&lt;/p&gt;
&lt;p&gt;Nuestra plantilla de post-mortem tiene una sola regla: &lt;strong&gt;ningún nombre de persona en la sección de causa raíz.&lt;/strong&gt; Falló el sistema. No una persona.&lt;/p&gt;
&lt;p&gt;Nos enfocamos en:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;¿Qué controles faltaban que permitieron que esto ocurriera?&lt;/li&gt;
&lt;li&gt;¿Qué señales pasamos por alto y por qué?&lt;/li&gt;
&lt;li&gt;¿Qué automatización lo habría detectado antes de que los usuarios lo notaran?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;¿El resultado? Los ingenieros reportan de forma proactiva los casi-incidentes. Avisan &quot;esto se siente frágil&quot; sin miedo. Y todo el equipo mejora porque la información fluye libremente.&lt;/p&gt;
&lt;h2&gt;La documentación es una dependencia de producción&lt;/h2&gt;
&lt;p&gt;Lo diré sin rodeos: si tu sistema no puede ser operado por alguien que no estuvo en la sala cuando se construyó, tu documentación está fallando. Y con equipos distribuidos, SIEMPRE hay alguien que no estuvo en la sala.&lt;/p&gt;
&lt;p&gt;Tratamos la documentación como tratamos las pruebas. No es opcional. No es &quot;deseable&quot;. Es una dependencia de producción. Si entregas un servicio nuevo sin un README actualizado, un runbook operativo y un diagrama de arquitectura, tu PR no se mergea.&lt;/p&gt;
&lt;p&gt;¿Es trabajo extra? Sí. ¿Se paga solo la primera vez que alguien en otra zona horaria tiene que depurar tu servicio a las 3 AM? Totalmente.&lt;/p&gt;
&lt;h2&gt;La verdad honesta&lt;/h2&gt;
&lt;p&gt;Operar sistemas en producción con equipos distribuidos es más difícil que hacerlo en una oficina compartida. Quien te diga lo contrario te está vendiendo algo. Pero también es posible, sostenible y, cuando lo haces bien, increíblemente gratificante.&lt;/p&gt;
&lt;p&gt;La clave no son las herramientas sofisticadas ni los procesos complejos. Es construir sistemas (tanto técnicos como humanos) que asuman que siempre hay alguien durmiendo, siempre hay alguien depurando y la documentación es lo primero que va a leer.&lt;/p&gt;
&lt;p&gt;Haz que esa experiencia sea excelente y todo lo demás vendrá solo.&lt;/p&gt;
</content:encoded></item></channel></rss>