Category: JavaScript

  • Cómo Angular 21 Optimiza la Asincronía Sin Zone.js

    Cómo Angular 21 Optimiza la Asincronía Sin Zone.js

    Sin Zone.js — Native Async/Await: Angular 21 y la arquitectura Zoneless

    Sin Zone.js — Native Async/Await es el cambio arquitectónico que Angular 21 trae para cerrar años de parches y trampas alrededor de la asincronía. En lugar de confiar en interceptores globales, el framework apuesta por Signals y async/await nativo, lo que redefine cómo se detectan cambios, cómo se escribe lógica asíncrona y cómo se depura una aplicación Angular a escala.

    Resumen rápido (lectores con prisa)

    Angular 21 elimina Zone.js y usa Signals como fuente de verdad. Usa async/await nativo sin envoltorios. Resultado: detección de cambios localizada, trazas de error más limpias y menos bundle inicial.

    Cuándo: migraciones planificadas por fases. Cómo: mover estado a signals y usar OnPush; auditar subscribes y callbacks externos.

    Tiempo estimado de lectura

    Tiempo estimado de lectura: 5 min

    Ideas clave

    • Signals reemplazan a Zone.js: reactividad explícita y renderizado localizado.
    • Async/await nativo: sin transpile ni parches que oculten trazas y comportamiento.
    • Mejoras medibles: bundle inicial más pequeño, trazas limpias y menos comprobaciones globales.
    • Migración requiere auditoría: refactor de suscripciones y adopción de OnPush y signals.

    Sin Zone.js — Native Async/Await: qué cambia y por qué importa

    Zone.js surgió como una solución pragmática: interceptar (monkey‑patch) APIs del navegador —setTimeout, fetch, Promises, addEventListener— para saber cuándo lanzar la detección de cambios. Funcionó, pero con costes claros: comprobaciones globales innecesarias, trazas de pila contaminadas y fricción con APIs modernas. Más aún, Zone.js no intercepta await a nivel de motor V8, lo que obligó a técnicas de transpile para mantener el comportamiento esperado.

    Angular 21 elimina esa capa. En su lugar, Signals actúan como la fuente de verdad: cuando un signal cambia, Angular calcula qué partes de la vista dependen de él y actualiza solo esas piezas. El async/await se usa tal cual lo diseñó ECMAScript —sin envoltorios ni polyfills— y el navegador ejecuta la asincronía de manera óptima.

    Documentación clave:

    Cómo funciona el modelo Zoneless en práctica

    La filosofía es simple: reactividad explícita y renderizado localizado. Un servicio realiza una llamada asíncrona con async/await, actualiza signals y la vista reacciona únicamente a esos cambios.

    Ejemplo: servicio con signals

    @Injectable({ providedIn: 'root' })
    export class UserService {
      readonly users = signal([]);
      readonly loading = signal(false);
    
      async loadUsers(): Promise {
        this.loading.set(true);
        try {
          const response = await fetch('/api/users');
          this.users.set(await response.json());
        } finally {
          this.loading.set(false);
        }
      }
    }

    Ejemplo: template con signals

    En el template, consumir signals es directo y elimina muchos patrones previos (pipes async, subscribes que mutan propiedades de clase):

    <if (loading()) {>
      <app-skeleton />
    <} @else {>
      @for (user of users()) {
        <app-user-card [user]="user" />
      }
    }

    Angular gestiona internamente las dependencias entre signals y templates; no necesitas Zone.js para “avivar” la vista.

    Beneficios técnicos medibles

    • Reducción del bundle: eliminar Zone.js suele suponer ~100 KB minificados menos en el bundle inicial, con impacto directo en LCP.
    • Trazas de error limpias: las stack traces muestran tu código, no los callbacks internos de Zone.js, lo que reduce tiempo de diagnóstico.
    • Rendimiento en runtime: eventos de alta frecuencia (scroll, mousemove) dejan de disparar comprobaciones globales; solo los cambios efectivos actualizan la UI.
    • Compatibilidad natural con APIs modernas: fetch, Web Streams, WebSockets y librerías modernas funcionan sin trampas de detección.

    Riesgos y puntos a auditar antes de migrar

    Eliminar Zone.js no es solo quitar un import. Patrones comunes que romperán incluyen:

    • Mutar propiedades de clase dentro de subscribe() esperando que la vista se refresque automáticamente.
    • Dependencia implícita en NgZone.run() para actualizaciones desde callbacks externos.
    • Componentes sin ChangeDetectionStrategy.OnPush que confían en comprobaciones globales.

    Identifica y refactoriza estos puntos antes de desactivar Zone.js.

    Estrategia práctica de migración

    Pasos prácticos recomendados para una migración segura y por fases.

    Checklist rápida

    1. Establece ChangeDetectionStrategy.OnPush en toda la base de componentes.
    2. Migrar estado de componentes a signal() y computed(). Empieza por componentes hoja.
    3. Reemplaza suscripciones RxJS que mutan estado por toSignal() (@angular/core/rxjs-interop) o por flujos que actualicen signals explícitamente.
    4. Ejecuta pruebas E2E y de accesibilidad; valida rendimiento en escenarios reales.
    5. Activa el modo zoneless (p. ej. provideExperimentalZonelessChangeDetection() o su equivalente estable) cuando el 100% del estado dependiente de la vista esté en signals.

    Checklist rápida para auditoría antes de activar zoneless:

    • Todos los componentes críticos usan OnPush.
    • No existen mutaciones de estado implícitas en subscribes.
    • CI ejecuta pruebas de integración que cubren flujos asíncronos.
    • Observabilidad en producción: trazas y métricas para comparar comportamiento pre/post migración.

    Conclusión: menos magia, más control

    Sin Zone.js — Native Async/Await no es una moda; es la materialización de un principio: reactividad explícita y alineada con las plataformas. Para equipos que priorizan rendimiento, claridad y una depuración más rápida, Angular 21 ofrece un modelo más predecible y eficiente.

    Empieza la migración por componentes críticos, automatiza las pruebas de integración y planifica la adopción en fases: la reducción de complejidad será visible y medible. En la próxima entrega publicaremos una checklist automatizable y scripts de migración para acelerar este proceso en proyectos reales.

    FAQ

    ¿Qué es exactamente lo que reemplaza a Zone.js en Angular 21?

    Signals y el uso de async/await nativo. Signals actúan como la fuente de verdad para dependencias y actualizaciones de vista.

    ¿Por qué las trazas de error mejoran sin Zone.js?

    Porque se elimina el monkey‑patching y las trampas que insertan callbacks intermedios en las stack traces; las trazas muestran código de la aplicación y no callbacks internos.

    ¿Qué patrones rompen al desactivar Zone.js?

    Patrones que mutan estado dentro de subscribe(), dependencia en NgZone.run() y componentes que esperan comprobaciones globales en lugar de OnPush y signals.

    ¿Cuánto se reduce el bundle al quitar Zone.js?

    Eliminar Zone.js suele suponer aproximadamente 100 KB minificados menos en el bundle inicial, según la observación mencionada en el artículo.

    ¿Cuál es el primer paso práctico para migrar?

    Establecer ChangeDetectionStrategy.OnPush en los componentes y mover estado a signal() y computed(), empezando por componentes hoja.

    ¿Debo activar el modo zoneless inmediatamente en producción?

    No. Activarlo cuando el 100% del estado dependiente de la vista esté en signals y después de haber ejecutado pruebas E2E y de integración que validen flujos asíncronos.
  • Soluciones efectivas para props drilling en React

    Soluciones efectivas para props drilling en React

    ¿Qué es el props drilling en React? Guía de arquitectura y soluciones

    Tiempo estimado de lectura: 4 min

    • Props drilling es pasar props a través de varios niveles que no las usan.
    • Opciones: composición, Context API o stores externos según alcance y frecuencia de cambio.
    • Decisión técnica: privilegia composición; Context para valores estables; store para coordinación entre subsistemas.

    Resumen rápido (lectores con prisa)

    Props drilling: pasar datos por componentes intermedios que no los usan. Si atraviesa pocos niveles (<3) suele estar bien; si escala, considerar composición, Context o un store externo. Elige según alcance, frecuencia de cambio y acoplamiento.

    ¿Qué es el props drilling en React?

    El props drilling en React es cuando pasas propiedades a través de múltiples niveles de componentes que no las usan, solo las retransmiten hasta el componente que sí las necesita.

    Ejemplo visual:

    App (tiene user)
    └─ Layout
    └─ Sidebar
    └─ Menu
    └─ UserProfile (usa user)

    Código mínimo:

    function App() {
      const user = { name: "Ana", avatar: "/ana.jpg" };
      return <Layout user={user} />;
    }
    
    function Layout({ user }) { return <Sidebar user={user} />; }
    function Sidebar({ user }) { return <UserProfile user={user} />; }
    function UserProfile({ user }) { return <img src={user.avatar} alt={user.name} />; }

    Layout y Sidebar no necesitan user. Solo lo llevan. Eso genera acoplamiento y ruido en el código.

    ¿Cuándo es un problema real?

    No todo props que viaja es pecado. Pasar props uno o dos niveles es totalmente aceptable. El problema aparece cuando:

    • La propiedad atraviesa tres o más niveles.
    • Múltiples props no relacionadas llenan la firma de componentes intermedios.
    • Mover un componente exige recablear docenas de firmas.
    • Los re-renders se disparan y el rendimiento cae.

    Consecuencias prácticas: acoplamiento innecesario, refactors costosos, más tests y mayor probabilidad de bugs al cambiar la forma del dato.

    Soluciones (con criterio técnico)

    No hay varita mágica. Hay herramientas y criterios para escoger la correcta.

    1) Composición de componentes (cuando aplica)

    Primera regla: intenta composición antes que librerías. Si el componente final vive en un subárbol que puedes construir desde el padre que tiene el dato, inyecta el subárbol.

    function App() {
      const user = { name: "Ana", avatar: "/ana.jpg" };
      return (
        <Layout sidebar=&{``} />
      );
    }

    Ventaja: cero dependencias, cero props intermedios. Referencia: docs de React sobre composición

    Cuándo usar: datos locales a una sección, poco compartidos fuera del subárbol.

    2) Context API (cuando el dato es global y estable)

    Context te permite proveer un valor desde arriba y consumirlo en cualquier punto del árbol, sin pasar props intermedios.

    const UserContext = React.createContext(null);
    
    function App() {
      const user = { name: "Ana" };
      return <UserContext.Provider value={user}><Layout /></UserContext.Provider>;
    }
    
    function UserProfile() {
      const user = useContext(UserContext);
      return <span>{user.name}</span>;
    }

    Docs oficiales: React Context

    Advertencia: Context es ideal para valores que cambian poco (tema, idioma, sesión). Si el valor cambia con alta frecuencia, todos los consumidores se re-renderizan y el rendimiento puede sufrir.

    3) Estado global / stores (cuando la app escala)

    Cuando múltiples partes desconectadas del árbol necesitan leer y escribir el mismo estado, un store fuera del árbol es la opción práctica.

    Opciones razonables hoy:

    • Zustand: simple, sin boilerplate, buen rendimiento.
    • Redux Toolkit: trazabilidad y patterns para apps enterprise.
    • Jotai/Recoil: atom-based state para control fino de re-renders.

    No uses un store global por moda. Úsalo cuando la composición y Context se queden cortos.

    Cómo decidir (lista rápida)

    Hazte estas preguntas antes de refactorizar:

    1. ¿Cuántos niveles atraviesa la prop? (<3 → probablemente ok)
    2. ¿Los componentes intermedios la usan? (si sí, deja el flujo)
    3. ¿El dato cambia con frecuencia? (si sí, evita Context)
    4. ¿Se comparte entre partes no relacionadas de la UI? (si sí, considera un store)

    Si la respuesta apunta a complejidad real, planifica: migración por fases, pruebas y medición de re-renders.

    Buenas prácticas finales

    • Mantén el estado lo más cerca posible del lugar donde se usa.
    • Prefiere composición cuando sea viable.
    • Usa Context para datos estables.
    • Reserva stores externos para coordinación entre subsistemas.
    • Evita micro-optimizaciones prematuras: primero estructura, luego perf.

    Referencias útiles

    Esto no acaba aquí: en el siguiente post veremos cómo migrar un árbol con props drilling a Zustand paso a paso, sin romper la app ni a los desarrolladores. Suscríbete al boletín de Dominicode para recibir la guía y los snippets listos para copiar.

    FAQ

    ¿Qué es exactamente el props drilling?

    Es el patrón donde pasas propiedades desde un componente superior hasta uno profundo, atravesando componentes intermedios que no las usan. Genera firmas de props infladas y acoplamiento innecesario.

    ¿Cuándo puedo ignorarlo?

    Cuando la prop atraviesa uno o dos niveles y no complica el mantenimiento. No todo pasaje de props requiere refactor.

    ¿Cuándo usar Context en lugar de un store?

    Usa Context para valores globales y estables (tema, idioma, sesión). Evita Context para datos que cambian con alta frecuencia o requieren escrituras concurrentes desde múltiples partes.

    ¿La composición siempre es la mejor opción?

    No siempre, pero es la primera estrategia a intentar: sin dependencias y con menor acoplamiento cuando puedes construir el subárbol desde el padre que tiene el dato.

    ¿Qué problemas de rendimiento trae Context?

    Si el valor del Provider cambia con frecuencia, todos los consumidores se re-renderizan, lo que puede impactar el rendimiento. Se puede mitigar con memos, splitting de contexts o stores que controlen re-renders finos.

    ¿Qué store elegir si la app escala?

    Depende: Zustand para simplicidad y rendimiento, Redux Toolkit para trazabilidad en enterprise, y Jotai/Recoil para control fino de re-renders.

  • Implementa autenticación sencilla en Next.js con NextAuth.js

    Implementa autenticación sencilla en Next.js con NextAuth.js

    NextAuth.js / Auth.js: deja de reinventar la autenticación

    Tiempo estimado de lectura: 6 min

    Ideas clave

    • NextAuth.js (y la evolución a Auth.js v5) evita errores comunes en sesiones, cookies y tokens.
    • Dos estrategias de sesiones: JWT (performance) vs sesiones en base de datos (revocación instantánea).
    • Evita gestionar credenciales si no dominas hashing, salting y políticas de seguridad.
    • Si usas Next.js 14+, técnica y pragmáticamente conviene empezar con Auth.js (v5).

    Tabla de contenidos

    ¿Cansado de perder semanas armando autenticación como si fuera magia negra? Eso se acaba aquí.
    NextAuth.js no es solo otra librería bonita. Es el pegamento que evita que tu aplicación se desangre en sesiones, cookies y tokens. Es la forma razonable de decir: “No voy a reinventar esto” y, aun así, tener control total.

    Resumen rápido (lectores con prisa)

    NextAuth.js es una solución de autenticación open source diseñada para Next.js y arquitecturas Serverless. Úsala para gestionar sesiones, providers OAuth y passwordless sin construir todo desde cero. Importa porque abstrae validación en peticiones y reduce errores de cookies/tokens. Funciona con JWT o sesiones en base de datos según tus necesidades de rendimiento y revocación.

    Qué es, en pocas palabras

    NextAuth.js es una solución de autenticación open source creada para Next.js. Funciona bien con Serverless, Edge y App Router. No pretende sustituir a un proveedor de identidad empresarial, pero te da una capa de seguridad sólida, lista para producción, sin pagar licencia.
    Es como poner una cerradura profesional en la puerta de tu app sin contratar a un cerrajero. Es segura por defecto. Y flexible cuando lo necesitas.

    Por qué importa en el mundo moderno (y no es marketing)

    Next.js con App Router difumina la línea entre servidor y cliente. Ahora puedes ejecutar lógica en el servidor sin servidor fijo. Eso complica cookies, tokens y el control de sesiones. Si lo haces mal, expones rutas, filtrás datos y te conviertes en el peor tipo de soporte: el que responde “está en producción, no puedo tocarlo”.
    NextAuth.js abstrae eso. Valida en el momento de la petición, ideal para funciones Serverless o runtimes Edge. No necesitas un proceso que viva todo el tiempo. Ahorras costes. Escalas mejor. Y, sobre todo, reduces la superficie de errores humanos.

    Características que realmente importan

    No voy a pintarte una lista vacía. Esto es lo importante:
    • Soporte multi-proveedor (OAuth): conecta Google, GitHub, Apple, Discord, lo que sea. No reinventes OAuth.
    • Magic Links (passwordless): menos fricción, menos contraseñas que filtrar.
    • Adapters para DB y ORMs: Prisma, Drizzle, Mongo, Supabase. Tú eliges dónde almacenarlos.
    • Seguridad por defecto: CSRF, firma de cookies, tokens cifrados (JWE) —activados sin tener que leer 200 páginas.
    • Edge-ready: puedes validar sesiones casi al instante si lo ejecutas cerca del usuario.
    No es perfecto, pero es un buen comienzo. Y un comienzo con sentido.

    El dilema clásico: manejar credenciales propias

    Puedes usar el proveedor de “credentials” y manejar usuarios/contraseñas tú mismo. Suena bien hasta que lees la letra pequeña: gestionar contraseñas implica responsabilidad. Hashing, salting, políticas, reset de contraseñas, protección frente a ataques por fuerza bruta.
    NextAuth.js permite credenciales, pero la recomendación oficial y práctica es evitarlo si no sabes exactamente lo que haces. Además, si usas ese proveedor, NextAuth.js fuerza sesiones en JWT y desactiva sesiones en BD. No es capricho: es una decisión para reducir malas prácticas.

    Arquitectura de sesiones: dos caminos y una decisión

    Aquí viene la parte que separa proyectos tranquilos de proyectos que arden. Hay dos estrategias principales:

    1) JWT (por defecto)

    La información viaja en la cookie, cifrada. Rápido. No consultas la DB en cada petición. Ideal para apps con mucho tráfico. Problema: invalidar sesiones inmediatamente es más complejo. Si quieres expulsar a alguien ya, el token sigue válido hasta que expire.

    2) Sesiones en base de datos

    La cookie guarda solo un ID. La verificación exige una consulta a la base de datos. Perfecto si necesitas cortar accesos en tiempo real (ej. revocar a un usuario). Coste: latencia adicional por cada validación.
    No hay una respuesta universal. Si tu app es una red social con millones de peticiones, JWT suele ser la opción. Si es un panel de control bancario donde cortar accesos rápido es crítico, la base de datos gana.

    La transición que incomoda: NextAuth.js v4 → Auth.js v5

    Esto tiene que quedar claro: el ecosistema está en movimiento. NextAuth.js está evolucionando hacia Auth.js (v5). ¿Por qué? Porque no quieren quedarse atados solo a Next.js. Quieren que funcione también en otros frameworks: SvelteKit, SolidStart, Express…
    ¿Qué implica esto para ti?
    • Nueva API unificada. Más poderosa, mejor integración con Server Actions y App Router.
    • Documentación en proceso. La v5 ha estado en beta y la documentación todavía se alinea.
    • Si arrancas un proyecto con Next.js 14 o superior: técnica y pragmáticamente, empieza con v5. Evitarás dolores de migración.
    Sí, duelen las beta-docs. Pero es mejor migrar ahora que rehacerlo todo después.

    Un ejemplo real y útil (sin poesía)

    Piensa en un componente servidor que solo debe ver el usuario autenticado. Con Auth.js/NextAuth puedes validar en el servidor antes de renderizar:
    import { auth } from "@/auth"
    import { redirect } from "next/navigation"
    
    export default async function Dashboard() {
      const session = await auth()
      if (!session) redirect("/login")
      return <h1>Panel seguro de {session.user.name}</h1>
    }
    Eso no es ciencia ficción. Es práctica común. Y evita que la UI se entere antes que el servidor. La seguridad no depende del cliente.

    El dev escéptico y su evolución

    Conozco al dev escéptico. Le encanta escribir autenticaciones a mano. Cree que es más “limpio”. Tres meses después, está leyendo logs a las 3 a.m. por un problema de sesiones. Cambió. Aprendió. No renunció a la personalización: NextAuth.js ofrece callbacks, eventos y handlers. Puedes seguir siendo “muy tuyo” sin pagar el precio de hacerlo todo desde cero.

    Cuando no deberías usar NextAuth.js

    No todo es para todos. Elige otra cosa si:
    • Necesitas SAML/SSO empresarial complejo con flujos B2B avanzados.
    • Quieres una consola de usuarios y control RBAC administrada por un tercero (Clerk, Auth0 lo hacen).
    • Tu equipo no quiere tocar la infraestructura de usuarios y prefiere externalizarlo.
    Si la respuesta es “no me complico la vida” y estás dispuesto a pagar por eso, un SaaS puede ser mejor.

    Migración y estrategia práctica

    Si ya tienes autenticación casera, ¿migrar? Hazlo por fases:
    • Analiza qué needs tienes: invalidación en tiempo real, multi-provider, passwordless.
    • Implementa NextAuth.js en modo JWT en staging. Observa.
    • Si necesitas revocar sesiones pronto, añade el adapter y cambia a sesiones en DB.
    • Aprovecha callbacks para mapear datos de usuario sin romper tus modelos.
    No es tan doloroso. Pero exige disciplina.

    Pequeñas trampas que nadie te cuenta

    • No uses credentials si no entiendes hashing y políticas de seguridad.
    • Validar en cliente es mala idea. Siempre valida en servidor.
    • Documentación: si empiezas con v5, sigue la docs oficiales y ejemplos; ignora tutoriales viejos de v4.
    • Cookies seguras: en producción siempre en https y withSameSite apropiado.

    Metáfora útil (porque la mente recuerda imágenes)

    NextAuth.js es el portero de tu club. No decide a quién gustas. Solo asegura que el que entra esté autorizado. El portero no es el dueño del club, pero conoce la lista, sabe cuándo pedir identificación y tiene la potestad de sacar a cualquiera que cause problemas.

    Urgencia realista

    Si estás empezando un proyecto con Next.js 14+:
    • Empieza con Auth.js (v5).
    • Si pospones, la migración será más cara dentro de 6-12 meses.
    • No por FOMO, sino por coherencia técnica.

    CTA simple y sin vueltas

    ¿Quieres un setup funcional en 10 minutos? Haz esto: instala next-auth o auth y sigue la guía oficial para tu provider preferido. ¿Quieres que te lo arme paso a paso? Respóndeme este mensaje y te doy una checklist práctica y un ejemplo listo para copiar y pegar.

    Esto no acaba aquí

    Autenticación es una zona de fricción constante. Cambian las APIs, aparecen runtimes Edge, el usuario espera menos fricción y más seguridad. NextAuth.js te da una base sólida. Pero ninguna herramienta es el final de tu trabajo. Es la primera línea del plan.
    Si quieres, la próxima entrega la dedicamos a:
    • Migración completa v4 → v5.
    • Ejemplos avanzados con adapters (Prisma, Supabase).
    • Estrategias para invalidación instantánea y refresh tokens.
    ¿Quieres que sigamos? Respóndeme. No es una promesa vacía: es la continuación que necesitas para no aprender las cosas a las malas.

    FAQ

    ¿Qué es NextAuth.js / Auth.js?

    NextAuth.js es una solución de autenticación open source creada para Next.js; Auth.js (v5) es la evolución que amplía soporte a otros frameworks. Proporcionan manejo de sesiones, providers OAuth, magic links y adapters para DB/ORMs.

    ¿Cuándo usar JWT vs sesiones en BD?

    Usa JWT si priorizas performance y quieres evitar consultas a la DB por petición. Usa sesiones en BD si necesitas revocar accesos en tiempo real y tener control inmediato sobre sesiones.

    ¿Puedo manejar credenciales con NextAuth.js?

    Sí, existe el proveedor de “credentials”, pero no es recomendado si no controlas hashing, salting, políticas y protección frente a fuerza bruta. Además, usar credentials suele forzar sesiones en JWT y desactivar sesiones en BD.

    ¿Auth.js v5 es estable para nuevos proyectos?

    La v5 ha estado en beta y la documentación aún se alinea. Sin embargo, si arrancas con Next.js 14+, técnica y pragmáticamente conviene empezar con v5 para evitar migraciones futuras.

    ¿Cómo validar sesiones en servidor con App Router?

    Puedes llamar a la utilidad de autenticación en componentes servidor antes de renderizar. Ejemplo: llamar a auth() y redirigir si no hay sesión para evitar exponer rutas desde el cliente.

    ¿Qué proveedores soporta?

    Soporta múltiples providers OAuth como Google, GitHub, Apple, Discord, además de magic links y adapters para Prisma, Drizzle, Mongo, Supabase, entre otros.

    ¿Qué debo evitar al implementar autenticación?

    No uses credenciales si no entiendes seguridad de contraseñas. No confíes en validación en cliente. Asegura cookies en producción (https, SameSite). Sigue la documentación actual de la versión que eliges y evita tutoriales desactualizados.
  • Cómo implementar un Event Bus para arquitecturas con agentes descentralizados

    Cómo implementar un Event Bus para arquitecturas con agentes descentralizados

    ¿Y si tu app dejara de ser un monstruo monolítico con un único “asistente” y se convirtiera en un enjambre de agentes que se pasan la pelota sin romper nada?

    Tiempo estimado de lectura: 6 min

    • Reducir contexto inútil en prompts, aislar fallos y permitir equipos autónomos sin perder UX coherente.
    • Descentraliza la responsabilidad cognitiva; centraliza seguridad, auditoría y orden.
    • Usa un Event Bus global con contratos claros y BFFs que versionen prompts y auditen inferencias.
    • Mide confidence, delegaciones y latencia; trata prompts como código.

    Poca gente lo dice así: dividir la inteligencia en agentes especializados no es exotismo. Es economía de tokens, menos alucinaciones y menos puntos únicos de falla. Y sí: también es más trabajo. Pero si tu producto escala, vale cada minuto invertido.

    Resumen rápido (lectores con prisa)

    Qué es: Un patrón para dividir inteligencia en agentes por dominio y comunicarlos vía un Event Bus global.

    Cuándo usarlo: Cuando la complejidad de dominios y volumen hacen ineficiente un único asistente monolítico.

    Por qué importa: Reduce tokens, mitiga alucinaciones y falla de forma aislada; facilita equipos autónomos.

    Cómo funciona (resumen): Host publica USER_INPUT al bus; agentes calculan confidence; el agente ganador responde y puede delegar sub-problemas a otros agentes vía eventos.

    Introducción

    Te doy el patrón completo. Arquitectura, contratos de evento, anti-patrones, seguridad y el pacto UX que nadie firma hasta que explota.

    Qué estamos resolviendo (en claro)

    Puntos

    • Reducir contexto inútil en prompts.
    • Aislar fallos por dominio.
    • Permitir equipos autónomos (cada uno con su BFF y su agente).
    • Mantener una UX coherente pese a la descentralización.

    Principio arquitectónico imprescindible

    Descentraliza la responsabilidad cognitiva. Centraliza la infraestructura técnica que garantiza seguridad, auditoría y orden. Traducción: agentes por dominio + un Event Bus global para hablarse.

    Componentes esenciales (resumen)

    Host / Shell

    Monta el Event Bus y el UI unificado. No ejecuta prompts.

    Micro-frontends (Remotes)

    UI + agnostic agent client.

    BFF por dominio

    Ejecuta inferencia, almacena prompts versionados, audita.

    Event Bus Global

    Canal estándar para intentos y delegaciones.

    Orquestación concreta

    Coreografía por defecto; orquestador solo si necesitas transacciones distribuidas.

    El contrato del evento (no lo negocies)

    Si no tienes un schema, tendrás ruido. Exige este JSON mínimo:

    {
      "id": "uuid",
      "source": "inventory-agent",
      "intent": "REQUIRE_BILLING_CHECK",
      "context": { "userId": "123", "orderId": "405" },
      "confidence": 0.87,
      "metadata": { "traceId": "abc", "locale":"es-ES" },
      "timestamp": 1700000000
    }
    

    Reglas rápidas

    • confidence < 0.6 => requiere confirmación humana o UI consent.
    • context debe llevar solo IDs y flags. No historial de chat completo.
    • metadata incluye traceId para rastrear en las trazas distribuidas.

    Cómo se mueven los eventos (flujo)

    1. Usuario pregunta algo en el host.
    2. Host publica evento USER_INPUT al bus.
    3. Agentes suscritos calculan su confidence local.
    4. El agente ganador responde y/o emite delegaciones (events) sobre sub-problemas.
    5. Si hay delegación, otros agentes reaccionan y devuelven resoluciones.
    6. Host compone respuestas y muestra coherencia al usuario.

    Ejemplo práctico (en vivo)

    Usuario: “¿Por qué no ha salido el pedido #405? ¿Falló mi tarjeta?”

    • Inventory-agent: detecta pedido retenido → responde estado del envío → emite REQUIRE_BILLING_CHECK.
    • Billing-agent: suscribe y consulta pasarela en su BFF → descubre tarjeta vencida → emite BILLING_ISSUE_FOUND.
    • Host: recibe ambas respuestas y muestra un flujo: “Pedido retenido. Tarjeta expirada. ¿Actualizar ahora?”

    Cómo implementar el Event Bus (dos opciones)

    Opción A — RXJS (más control dentro de Angular)

    // event-bus.service.ts
    import { Subject } from 'rxjs';
    export const globalEventBus = new Subject();
    // publish: globalEventBus.next(event)
    // subscribe: globalEventBus.subscribe(e => ...)
    

    Opción B — CustomEvent (mejor para micro-frontends aislados)

    // emit
    window.dispatchEvent(new CustomEvent('app:event', { detail: event }));
    // listen
    window.addEventListener('app:event', e => handle(e.detail));
    

    Anti-patterns del bus (evítalos)

    • Enviar cada pensamiento del LLM. Solo conclusiones.
    • Compartir historial completo en context.
    • Permitir que cualquier módulo escuche todo sin roles.
    • Esperar sincronía absoluta entre agentes.

    Mecanismo de arbitraje: quién responde primero

    Necesitas un árbitro débil. No un maestro, sino una regla simple:

    1. Cada agente publica su confidence ante USER_INPUT.
    2. Host espera X ms (ej. 200–400ms) o hasta el primer confidence >= threshold.
    3. El agente ganador toma la palabra; los demás quedan “en espera” para delegaciones.

    Seguridad y datos: no es opcional, es ley

    • No inferes en cliente. Nunca. Las keys quedan en los BFF.
    • Filtrado por roles en el Event Bus: cada agente tiene claims y scopes.
    • Sanitización en BFF: elimina PII innecesaria antes de enviar a LLM.
    • Audit log centralizado: guarda hash de mensajes, no el texto completo salvo consentimiento.
    • Rate limiting por agente y por usuario.

    Prompts y versionado: trátalos como código

    Versiona prompts. Haz CI sobre prompts. Un cambio de prompt puede cambiar comportamiento entero. Guarda el promptId en metadata del evento para reproducibilidad.

    Observabilidad: métricas mínimas que necesitas YA

    • Latencia total por intent (ms).
    • Confidence distribution por agente.
    • Delegation rate (cuántas veces un agente pide otro agente).
    • Fallas y errores por BFF.
    • Casos humanos de override (undo/confirm).

    Costes y escalado

    Agentes especializados consumen menos tokens por prompt. Pero multiplicas llamadas si no cacheas. Cachea respuestas frecuentes en BFF. Si el dominio es alto volumen, considera modelos on-prem o inferencia en región cercana.

    UX: cómo evitar que la UX parezca multi-agente

    • Unifica la voz: host normaliza tono y formato.
    • Muestra trazabilidad solo si el usuario la pide (ej. “ver detalle técnico”).
    • Siempre provee undo para acciones críticas.
    • Si confidence baja, pide confirmación editable: muestra la transcripción + intención sugerida.

    Sincronización del historial

    Historial maestro en Host. Los BFFs pueden guardar copias locales por dominio. Para reproducir una conversación: traceId + promptId + promptVersion + snapshot del context.

    Tests y despliegue

    • Contract tests para el Event Schema.
    • E2E con agentes stubs (simula respuestas con confidence).
    • Canary deploy de prompts: prueba nuevos prompts con 1% de tráfico antes de publicar.

    Cuando usar orquestador en vez de coreografía

    Coreografía = menos acoplamiento. Pero si necesitas transacciones distribuidas (ej. reserva + pago atómico), añade un orquestador o un workflow engine (Temporal, Durable Functions). No lo hagas por conveniencia.

    Checklist práctico para arrancar en 2 semanas

    • [ ] Definir dominios y agentes.
    • [ ] Diseñar Event Schema y contract tests.
    • [ ] Implementar Bus (CustomEvent + roles).
    • [ ] BFFs mínimos con secreto seguro y prompt versionado.
    • [ ] UI host de chat, arbiter de confidence y UX confirmaciones.
    • [ ] Observability (traces + metrics).
    • [ ] Políticas de privacidad y retención.

    Cierre directo: lo que debes hacer hoy

    No diseñes agentes porque “está de moda”. Distribuye inteligencia solo donde tenga sentido. Empieza por 2 agentes: uno crítico (ej. facturación) y otro de baja prioridad (ej. FAQ). Lanza el Event Bus y valida la coreografía. Mide confidence y delegations la primera semana. Ajusta prompts con datos reales.

    ¿Quieres el kit para arrancar? Te puedo pasar:

    • Event Bus + arbiter en TypeScript.
    • BFF skeleton que llama a LLM y valida JSON.
    • Prompts versionados y tests de contrato.

    Responde “QUIERO EL KIT” y te lo envío listo para pegar en tu repo.

    Esto no acaba aquí. Si lo haces bien, tu app dejará de ser un oráculo confuso y empezará a funcionar como un equipo de especialistas que no se pisan el uno al otro. ¿Empezamos por el Event Bus o por el BFF? Responde “BUS” o “BFF”.

    Dominicode Labs

    Si quieres recursos prácticos y plantillas para implementar este patrón, revisa Dominicode Labs. Encontrarás ejemplos de Event Bus, skeletons de BFF y tests de contrato que aceleran la puesta en marcha.

    FAQ

    ¿Por qué dividir en agentes en vez de un asistente monolítico?

    Dividir reduce contexto inútil en prompts, aisla fallos por dominio y permite equipos autónomos con BFFs propios. Es coste de ingeniería que compensa cuando el producto escala.

    ¿Qué contiene el contrato mínimo de un evento?

    El JSON mínimo incluye id, source, intent, context (solo IDs/flags), confidence, metadata (traceId, locale) y timestamp. Sin schema tendrás ruido.

    ¿Cómo evito que la UX se perciba como multi-agente?

    Unifica la voz desde el Host, muestra trazabilidad solo si el usuario la solicita y siempre ofrece undo para acciones críticas. Normaliza tono y formato antes de mostrar respuestas.

    ¿Cuándo necesito un orquestador en lugar de coreografía?

    Cuando necesitas transacciones distribuidas atómicas (ej. reserva + pago). Para casos simples, coreografía reduce acoplamiento; usa orquestador solo para transacciones complejas.

    ¿Dónde deben residir las claves y la inferencia?

    Nunca en el cliente. Las keys y la inferencia deben vivir en los BFFs o infraestructura backend segura. El cliente solo publica eventos y muestra resultados.

    ¿Qué métricas son imprescindibles al arrancar?

    Latencia por intent, distribución de confidence por agente, delegation rate, fallas por BFF y casos humanos de override.

  • Cómo manejar closures y memoria en JavaScript para evitar fugas

    Cómo manejar closures y memoria en JavaScript para evitar fugas

    Closures, Scope Chains y Garbage Collection

    Tiempo estimado de lectura: 4 min

    • Closures retienen el Lexical Environment: una función mantiene referencias al entorno donde fue creada.
    • Scope chain y resolución: el motor busca identificadores subiendo por la cadena de entornos hasta global.
    • Hoisting real y TDZ: funciones, var, let/const se registran de forma distinta durante la fase de creación.
    • GC y fugas: closures pueden retener objetos grandes; motores aplican optimizaciones pero no son infalibles.
    • Prácticas: usar WeakMap, nullificar referencias y auditar memoria con DevTools.

    Closures, Scope Chains y Garbage Collection: saber definir un closure es solo el primer paso. Lo que cuenta en producción es entender cómo el Lexical Environment, la resolución de scope y el Hoisting real afectan la retención de memoria y la estabilidad de tus apps. Si no controlas eso, acabarás depurando fugas que solo aparecen tras días de uptime.

    En las siguientes secciones desgloso cómo funciona realmente el motor, por qué las variables “siguen vivas” y qué reglas prácticas aplicar para evitar memory leaks.

    Resumen rápido (lectores con prisa)

    Un closure es una función que conserva una referencia al Lexical Environment donde fue creada. Úsalo para encapsular estado, pero evita mantener dentro datos pesados si la función debe persistir. La resolución de identificadores recorre la scope chain desde el entorno actual hacia afuera; el GC libera lo inaccesible desde las raíces, pero un closure mantiene accesible su entorno. Reglas prácticas: preferir let/const, usar WeakMap para caches recuperables y nullificar referencias grandes cuando ya no hacen falta.

    Cómo interactúan Closures, Scope Chains y Garbage Collection

    Un closure no es magia. Es una función que mantiene una referencia al Lexical Environment donde fue creada. Ese entorno contiene los Environment Records con las variables locales y una referencia al outer environment. Esa cadena de enlaces es la Scope Chain.

    Técnicamente:

    • Cada invocación crea un Execution Context (fase de creación + fase de ejecución). (ECMAScript spec)
    • En la fase de creación el motor reserva espacio para identificadores (hoisting real).
    • El Lexical Environment conserva variables y la referencia al parent; cuando una función exterior retorna pero su función interior sigue referenciable, ese Lexical Environment sigue vivo.

    Resolución de identificadores (algoritmo)

    1. mira el Environment Record del contexto actual;
    2. si no está, sube al outer;
    3. repite hasta el global;
    4. si no lo encuentra, lanza ReferenceError.

    Ese viaje explica por qué let/const (scope de bloque) y var (scope de función) se comportan distinto. Referencias útiles: MDN Event Loop / Scope y ECMAScript Execution Contexts.

    Hoisting real y Temporal Dead Zone (TDZ)

    Olvida la metáfora “el motor mueve las declaraciones arriba”. Durante la fase de creación el motor:

    • registra function declarations completamente;
    • reserva var inicializado a undefined;
    • registra let y const pero las deja en estado uninitialized.

    Intentar acceder a una let antes de su inicialización entra en la TDZ y lanza ReferenceError. Ejemplo:

    console.log(a); // ReferenceError (TDZ)
    let a = 3;
    

    Contrástalo con var:

    console.log(b); // undefined
    var b = 3;
    

    Documentación V8 sobre let/const: Documentación V8 sobre let/const.

    El lado oscuro: closures que retienen más de lo necesario

    El Garbage Collector (Mark-and-Sweep) libera objetos inaccesibles desde las raíces. Un closure mantiene accesible su Lexical Environment, y por tanto todas las variables que contiene pueden quedar retenidas.

    Ejemplo típico de fuga:

    function creaLeak() {
      const datosPesados = new Array(1e6).fill('*'); // memoria grande
      const info = 'ok';
      return function () {
        console.log(info); // solo usamos `info`, pero `datosPesados` puede quedar retenido
      };
    }
    const fn = creaLeak(); // datosPesados sigue referenciado por el ambiente de fn
    

    Motores modernos (p. ej. V8) aplican optimizaciones como Variable Elimination y Escape Analysis para evitar retener variables que no “escapan”. Pero estas optimizaciones pueden fallar con eval, with, o si el entorno está expuesto al inspector/debugger. V8 blog: V8 blog.

    Patrones seguros y antipatrónes a vigilar

    Patrones a promover:

    • Factory functions y módulos para encapsular estado (closures usados con intención).
    • WeakMap para caches donde las claves deben ser recolectables.
    • Nullificar referencias grandes cuando el closure debe persistir pero los datos no: bigObject = null.

    Antipatrónes comunes:

    • Closures en listeners globales sin remover el listener.
    • Uso de var en loops que provoca referencias compartidas (legacy).
    • Retener objetos grandes “por si acaso” dentro de scopes accesibles.

    Auditoría práctica: cómo detectar y reparar leaks

    1. Captura heap snapshots en Chrome DevTools (Memory → Heap snapshot). Busca objetos con alto “retained size”.
    2. Reproduce en staging con carga real y toma snapshots antes/después de operaciones críticas.
    3. Identifica closures que retienen objetos: DevTools muestra paths to GC roots.
    4. Refactoriza: mover datos pesados fuera del closure, usar WeakMap, o eliminar listeners.

    Guía DevTools: Guía DevTools.

    Reglas de oro para equipos técnicos

    • Usa let/const por defecto. var crea más posibilidades de confusión.
    • Revisa closures en code review: pregunta “¿qué datos retiene esto?”.
    • Evita eval y with (bloquean optimizaciones).
    • Para objetos grandes, preferir estructuras weakly-referenced cuando la vida útil debe coincidir con el objeto clave.
    • Automatiza perfiles de memoria en staging y alerta por crecimiento continuo.

    Conclusión

    Closures y scope chains son poderosas herramientas de diseño; su coste real aparece en producción cuando retienen más memoria de la necesaria. Aprende a leer el Lexical Environment, comprende el Hoisting real y la TDZ, y aplica patrones que permitan al GC hacer su trabajo. Tu aplicación será más estable, tus ops menos nocturnas y tu equipo verá menos incendios por memoria.

    Lecturas y referencias

    FAQ

    ¿Qué es exactamente un closure?

    Un closure es una función junto con el Lexical Environment en el que fue creada; mantiene referencias a las variables de ese entorno aunque la función exterior haya retornado.

    ¿Cuándo un closure puede provocar una fuga de memoria?

    Cuando el closure sigue siendo referenciable y su Lexical Environment contiene objetos grandes que ya no se usan, esos objetos permanecen accesibles desde las raíces y no son recolectados.

    ¿Cómo difiere el hoisting entre var, let y const?

    var: se registra durante la fase de creación e inicializa a undefined. let/const: se registran pero quedan en estado no inicializado hasta la ejecución, entrando en TDZ si se accede antes.

    ¿Qué herramientas usar para detectar closures que retienen memoria?

    Chrome DevTools — Heap snapshots y análisis de paths to GC roots. Reproduce en staging y compara snapshots antes/después de operaciones críticas.

    ¿Cuándo debo usar WeakMap?

    Usa WeakMap para caches o asociaciones donde la clave debe ser recolectable cuando no existan otras referencias fuertes; útil para evitar retener objetos por el cache.

    ¿Qué prácticas de equipo reducen riesgos de memory leaks?

    Adoptar let/const, revisar closures en code reviews preguntando qué datos retienen, evitar eval/with, y automatizar perfiles de memoria en staging con alertas por crecimiento continuo.

  • Cómo los microtasks afectan la renderización del navegador en aplicaciones

    Cómo los microtasks afectan la renderización del navegador en aplicaciones

    Cómo afectan los microtasks a la renderización del browser

    Tiempo estimado de lectura: 4 min

    • Los microtasks se ejecutan después de cada macrotask y antes de cualquier repintado, por lo que encadenarlos o hacerlos pesados puede bloquear la renderización.
    • Si la microtask queue no se vacía, el navegador pospone Style → Layout → Paint y la UI puede quedarse congelada.
    • Estrategias prácticas: troceado (chunking), requestAnimationFrame, Web Workers, APIs de scheduling y medición/ profiling.
    • Reglas de equipo: no procesar >10ms en microtasks sin justificar; mover tareas >16ms a chunking o workers.

    ¿Sabes por qué una cadena de Promise.then() puede dejar tu UI congelada? Porque los microtasks afectan la renderización del browser bloqueándola hasta que su cola se vacíe. Esta no es teoría académica: es la raíz de muchos problemas de jank y malas métricas (INP, LCP) en aplicaciones reales.

    En las primeras líneas: cómo afectan los microtasks a la renderización del browser es simple y crítico: los microtasks se ejecutan después de cada macrotask y antes de cualquier repintado, así que si acumulas o encadenas microtasks pesadas, el navegador no tiene oportunidad de renderizar.

    Resumen rápido (lectores con prisa)

    Qué es: Los microtasks son callbacks que se ejecutan entre macrotasks y antes del siguiente paint.

    Cuándo usarlo: Para consistencia inmediata de estado y batching corto.

    Por qué importa: Microtasks largos o recursivos bloquean la fase de render y causan jank y malas métricas.

    Cómo mitigarlo: Chunking, requestAnimationFrame, Web Workers y scheduling.

    Cómo funciona: Event Loop, macrotasks y microtasks

    El Event Loop tiene iteraciones claras. En cada iteración:

    • Ejecuta un macrotask (ej.: callback de evento, setTimeout, postMessage).
    • Vacía la microtask queue por completo (Promise.then, queueMicrotask, MutationObserver).
    • Ejecuta las fases de render (Style → Layout → Paint) si procede.

    Iteración del Event Loop

    Fuente formal: WHATWG Event Loops. Referencia práctica: MDN Event Loop.

    Regla que bloquea la renderización

    La regla que mata interfaces es evidente: el navegador no entra en la fase 3 hasta que la microtask queue está vacía. Si esa cola se repuebla constantemente, la renderización se pospone indefinidamente.

    ¿Qué problemas verás en producción?

    • Jank visible: cualquier bloque que supere ~16ms arruina 60fps. Si tus microtasks suman más de eso, las animaciones y scroll saltan.
    • INP y Core Web Vitals: interacción que no despliega un siguiente paint rápidamente penaliza la UX y el SEO. Verifica en web.dev: INP y web.dev: Vitals.
    • “Starvation”: microtasks programando microtasks (recursividad) congelan la pestaña hasta el crash.

    Ejemplo mínimo que congela

    function loop() {
      queueMicrotask(loop);
    }
    loop();

    No hay render hasta que esto pare.

    Cuándo usar microtasks (y cuándo no)

    Microtasks son útiles y necesarias. No son el enemigo. Úsalas cuando:

    • Necesitas consistencia inmediata del estado antes del siguiente repintado.
    • Quieres agrupar cambios lógicos antes de un único render (batching de estado).
    • Manejas limpieza inmediata tras una operación asíncrona.

    Pero evita microtasks para trabajo CPU-intenso o bucles largos. Para esos casos, usa macrotasks o APIs de scheduling.

    Estrategias para evitar bloquear el render

    Resumen de tácticas prácticas para no secuestrar la renderización.

    1. Chunking (troceado)

    • Divide trabajo pesado en trozos pequeños y programa cada trozo con macrotasks (setTimeout(..., 0)) o postMessage.
    • Ejemplo: procesar arrays grandes en lotes de 100-500 elementos.

    2. requestAnimationFrame para código vinculado a la visual

    Si actualizas animaciones o layout, sincroniza con requestAnimationFrame para respetar vsync.

    3. Web Workers

    Mueve cálculo intensivo fuera del hilo principal. Comunicación vía postMessage (macrotask).

    Guía: Using web workers

    4. APIs de scheduling modernas

    • Evita hacks como setTimeout(fn, 0) a ciegas; usa APIs diseñadas para ceder control: scheduler.yield() y Task Scheduler (aún en evolución).
    • Artículo de referencia: Scheduler (Chrome).
    • requestIdleCallback también ayuda para tareas de baja prioridad (con sus limitaciones).

    5. Medición y profiling

    Usa Chrome DevTools Performance y Lighthouse para identificar long tasks y microtask spikes. El panel de Performance muestra frames dropped y long tasks.

    Casos reales: batching vs starvation

    Frameworks aplican microtasks con criterio. React 18 usa batching y concurrencia para agrupar commits y evitar renders intermedios (React 18 upgrade guide).

    Eso es distinto de encadenar miles de promesas manualmente. Batching intencional reduce trabajo de render; microtasks descontrolados lo empeoran.

    Regla práctica para equipos técnicos

    • Política en code reviews: no procesar >10ms en microtasks sin justificar.
    • Si una operación puede bloquear >16ms, debe ser chunked o movida a worker.
    • Documenta dónde y por qué se usa queueMicrotask o promesas críticas; busca alternativas de scheduling.

    Conclusión

    Los microtasks son herramientas de precisión: mantienen orden y coherencia, pero son capaces de secuestrar el hilo de renderizado si se usan mal. Entender que “microtasks bloquean render hasta vaciar la cola” es suficiente para empezar a evitar errores graves de UX. Mide, trocea y delega: esa tríada separa interfaces fluidas de las que frustran usuarios.

    Lecturas y referencias

    FAQ

    ¿Qué es exactamente una microtask?

    Una microtask es un callback que se encola para ejecutarse inmediatamente después de la ejecución de la macrotask actual y antes del siguiente repintado. Ejemplos: Promise.then, queueMicrotask, MutationObserver.

    ¿Por qué las microtasks se ejecutan antes del paint?

    Por diseño del Event Loop: tras ejecutar una macrotask el navegador vacía la microtask queue para garantizar coherencia de estado antes de recomponer estilos y layout.

    ¿Cómo puedo detectar si mis microtasks bloquean el render?

    Usa Chrome DevTools Performance y Lighthouse. Busca long tasks y picos en la cola de microtasks; si ves trabajos que suman más de ~16ms entre frames, probablemente estés causando jank.

    ¿Es mejor usar setTimeout o queueMicrotask para trocear trabajo?

    Para troceado y ceder control al navegador, las macrotasks (setTimeout, postMessage) son preferibles. queueMicrotask mantiene prioridad y puede seguir bloqueando el render si se abusúa.

    ¿Cuándo usar Web Workers en vez de chunking?

    Usa Web Workers cuando la tarea es CPU-intensiva y no interactúa directamente con el DOM. Chunking es útil para tareas grandes con dependencia de estado en hilo principal.

    ¿Qué herramientas debo usar para medir microtask spikes?

    Chrome DevTools Performance, Lighthouse y panel de Performance para identificar frames dropped, long tasks y microtask activity.

    ¿Qué políticas de equipo son recomendables sobre microtasks?

    Reglas prácticas: no procesar >10ms en microtasks sin justificar; mover operaciones >16ms a chunking o workers; documentar el uso de queueMicrotask y promesas críticas.

  • Enviar correos transaccionales con Resend en React y NestJS

    Enviar correos transaccionales con Resend en React y NestJS

    Cómo usar Resend en React y NestJS

    Tiempo estimado de lectura: 4 min

    • Mantén consistencia visual entre web y correo usando plantillas React Email.
    • Protege claves renderizando en el servidor (NestJS) y guardando API keys en variables de entorno.
    • Escala correctamente con colas (BullMQ/Redis) para evitar bloquear peticiones.

    Cómo usar Resend en React y NestJS para enviar correos transaccionales sin sangrar tiempo en HTML quebrado ni exponer claves. Esta guía práctica muestra plantillas en React, render en servidor (NestJS) y entrega con Resend.

    Resumen rápido (lectores con prisa)

    Qué es: patrón para generar y enviar emails transaccionales usando plantillas React Email, render en NestJS y entrega vía Resend.

    Cuándo usarlo: cuando quieres consistencia visual entre web y email y no exponer claves en frontend.

    Por qué importa: reduce deuda técnica, mejora DX y entregabilidad al separar render y envío.

    Cómo funciona: escribe plantillas React, renderízalas en servidor con @react-email/render y envía con la API de Resend; procesa con colas para escalar.

    Cómo usar Resend en React y NestJS: flujo y por qué importa

    No es solo “mandar un email”. Es:

    • mantener consistencia visual entre web y correo,
    • no exponer claves,
    • evitar render duplicado,
    • y escalar sin convertir cada registro en un bloqueo HTTP.

    La solución: escribir plantillas con React Email, renderizarlas en NestJS usando @react-email/render y llamar a Resend para la entrega. Docs oficiales: Resend, React Email, NestJS.

    1) Plantilla en React (React Email)

    Instala dependencias en tu monorepo o carpeta compartida:

    npm install @react-email/components @react-email/render
    npm install -D react @types/react

    Ejemplo mínimo: src/emails/WelcomeEmail.tsx

    import * as React from 'react';
    import { Html, Body, Container, Text, Button } from '@react-email/components';
    
    export function WelcomeEmail({ name, url }: { name: string; url: string }) {
      return (
          
            
              Hola, {name}
              Verifica tu cuenta para empezar a usar la plataforma.
              
            
          
        
      );
    }

    Ventaja: el componente es testable, reutilizable y legible. React Email genera HTML compatible con clientes antiguos.

    2) Render y envío en NestJS

    Instala el SDK de Resend:

    npm install resend

    email.service.ts (esqueleto)

    import { Injectable, Logger } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';
    import { Resend } from 'resend';
    import { render } from '@react-email/render';
    import { WelcomeEmail } from '../emails/WelcomeEmail';
    
    @Injectable()
    export class EmailService {
      private resend: Resend;
      private logger = new Logger(EmailService.name);
    
      constructor(private config: ConfigService) {
        this.resend = new Resend(this.config.get('RESEND_API_KEY'));
      }
    
      async sendWelcome(to: string, name: string, verificationUrl: string) {
        const html = render(WelcomeEmail({ name, url: verificationUrl }));
        const res = await this.resend.emails.send({
          from: 'TuApp <noreply@tu-dominio.com>',
          to: [to],
          subject: `Bienvenido ${name}`,
          html,
        });
        this.logger.log(`Enviado: ${res.data.id}`);
        return res;
      }
    }

    Puntos clave:

    • La API key vive en variables de entorno. Nunca en frontend.
    • render() convierte JSX a HTML listo para enviar.
    • Usa ConfigService para separar entornos.

    Referencia de la API de envío: Referencia de la API de envío

    3) No bloquees peticiones: usa colas

    Enviar emails sin cola = romper UX y escalar mal. Usa BullMQ/Redis:

    • BullMQ docs
    • Patrón: controlador crea job -> responde 202 -> worker procesa job (llama a EmailService)

    Beneficios:

    • reintentos automáticos,
    • backpressure controlada,
    • workers horizontales.

    4) Producción: dominios, entregabilidad y observabilidad

    Configura DKIM, SPF y DMARC. Resend te da valores concretos durante la verificación. Enlaces útiles:

    Ejemplo mínimo SPF/DKIM

    • TXT @ v=spf1 include:resend.com ~all
    • Registros DKIM proporcionados por Resend
    • TXT _dmarc “v=DMARC1; p=quarantine; rua=mailto:postmaster@tu-dominio.com”

    Añade headers o tags en los envíos para trazar campañas o templates. Resend Dashboard permite ver bounces, opens y eventos.

    5) Buenas prácticas y decisiones técnicas

    • Reutiliza componentes visuales entre web y email cuando tenga sentido. No todo componente de UI es apto para email: usa @react-email/components para compatibilidad.
    • Mantén plantillas en una carpeta compartida o paquete npm interno (monorepo).
    • En entornos dev, whitelistea destinatarios para no spamear usuarios reales.
    • Telemetría: registra message-id, template tag y userId en logs para debug.
    • Si no usas React en tu stack, no añadas React Email solo por moda. El coste de la dependencia debe justificarse.

    Conclusión rápida

    Usar Resend en React y NestJS no es una moda: es un patrón que reduce deuda, mejora DX y facilita la entregabilidad. Resumen práctico:

    1. escribe plantillas con React Email;
    2. renderiza en NestJS con @react-email/render;
    3. envía con Resend y procesa con colas (BullMQ) en producción;
    4. verifica dominio y monitoriza.

    Si quieres, te dejo un ejemplo con BullMQ integrado y un pipeline de observabilidad (logs + Sentry + Resend tags) listo para copiar y pegar. Esto no acaba aquí.

    FAQ

    Respuesta: Renderizar en servidor evita exponer claves en frontend, asegura HTML consistente y permite centralizar lógica de plantillas. Además facilita pruebas y control de versiones.

    Respuesta: En variables de entorno del servidor o servicio de secretos. Nunca en el cliente ni en repositorios públicos.

    Respuesta: Para no bloquear peticiones HTTP, manejar reintentos, control de backpressure y escalar workers horizontalmente.

    Respuesta: Sí. React Email está diseñado para generar HTML compatible con clientes antiguos y simplificar estilos inline.

    Respuesta: Configura SPF, DKIM y DMARC. Ejemplo mínimo: TXT @ v=spf1 include:resend.com ~all, registros DKIM proporcionados por Resend y un registro DMARC como TXT _dmarc "v=DMARC1; p=quarantine; rua=mailto:postmaster@tu-dominio.com".

    Respuesta: Resend Dashboard muestra bounces, opens y eventos. Además, añade tags/headers en los envíos para integrar con logs y sistemas de observabilidad.

  • Ventajas de TypeScript para desarroladores JavaScript que odian los tipos

    Ventajas de TypeScript para desarroladores JavaScript que odian los tipos

    TypeScript para desarrolladores JavaScript que odian los tipos

    Tiempo estimado de lectura: 4 min

    • Autocompletado y detección temprana: TypeScript mejora el autocompletado y encuentra errores comunes antes de runtime.
    • Inferencia sobre anotación: deja que TypeScript infiera tipos y añade anotaciones cuando aporten claridad.
    • Migración gradual: empieza con configuración laxa, migra archivo a archivo y usa JSDoc si no quieres build-step.
    • Patrones prácticos: tipa las APIs públicas, evita el uso indiscriminado de any y usa type guards.
    • Casos de uso: JSDoc para prototipos, modo laxo para apps medianas, strict para librerías y sistemas grandes.

    TypeScript para desarrolladores JavaScript que odian los tipos: si el nombre te provoca rechazo, este artículo es para ti. Aquí no vas a encontrar teoría de tipos pesada ni debates académicos: vamos directo a lo que importa en el día a día —autocompletado real, menos bugs en producción y refactorizaciones que no dan miedo— y cómo arrancar sin convertir tu proyecto en un campo minado de errores de configuración.

    Resumen rápido (lectores con prisa)

    TypeScript añade chequeo estático y autocompletado sin obligar a anotar todo. Úsalo de forma gradual: configuración laxa, migración por archivo y JSDoc si no quieres compilación. Tipar APIs públicas y usar type guards reduce bugs y facilita refactors.

    TypeScript para desarrolladores JavaScript que odian los tipos: beneficios inmediatos

    La promesa práctica de TypeScript no es “más reglas”, sino “menos fricción”. Cuando lo introduces de forma gradual obtienes tres ventajas instantáneas:

    • Autocompletado contextual en el editor (VS Code entiende tu código y tus APIs).
    • Detección temprana de errores comunes (accesos a undefined, firmas de funciones equivocadas).
    • Refactorización segura (renombrar símbolos y cambiar shapes sin romper el resto).

    Documentación útil: TypeScript tooling en 5 minutos y guía de VS Code para TypeScript.

    Inferencia: la clave para odiar menos los tipos

    No tienes que anotar todo. TypeScript infiere tipos en la mayoría de los casos. Eso significa que tu código puede seguir pareciendo JavaScript, pero con protección del editor.

    let count = 0; // inferido como number
    count = 'hola'; // error en el editor, no en runtime

    Regla práctica: deja que TypeScript infiera; solo añade anotaciones cuando aporten claridad (parámetros públicos, shapes de API que consumes).

    Cómo empezar sin dolor (estrategia pragmática)

    No transformes todo el repo de golpe. Sigue este plan de tres pasos:

    Instala y configura de forma laxa

    • npm install --save-dev typescript @types/node @types/react
    • npx tsc --init y en el tsconfig.json pon allowJs: true y strict: false para comenzar.

    Migra un archivo a la vez

    • Renombra un helper: util.jsutil.ts.
    • Observa las sugerencias del editor; corrige lo esencial.

    Usa JSDoc si quieres cero build-step

    • Añade // @ts-check al inicio de archivos .js y documenta con JSDoc.
    • Ejemplo JSDoc: JSDoc

    Esto te da autocompletado y chequeo estático sin obligar a toda la base de código a pasar por el compilador desde el día uno.

    Patrones prácticos que importan

    – Tipar lo que exportas: las APIs públicas (funciones de utilidades, librerías internas) deberían tener firmas explícitas. El resto puede seguir con inferencia.

    • Evita any como hábito: úsalo solo como parche temporal. Prefiere unknown si necesitas un tiempo para decidir el shape.
    • Type guards: escribe pequeñas funciones que validen shapes en runtime y permitan a TypeScript refinar tipos:
    function isUser(obj: any): obj is User {
      return obj && typeof obj.id === 'number' && typeof obj.name === 'string';
    }

    – Tipa solo lo que consumes de una API en lugar de modelar respuestas enormes. Usa Pick, Partial o Omit para mantener interfaces manejables.

    Casos de uso realistas: cuándo merece la pena

    • Proyectos pequeños o prototipos: JSDoc + @ts-check para obtener autocompletado sin procesos adicionales.
    • Apps medianas (crecen rápido): TypeScript en modo laxo. La inversión paga cuando haces refactors frecuentes.
    • Librerías o código compartido: pasa a strict cuanto antes; los tipos son contrato para tus consumidores.
    • Sistemas a gran escala: strict: true y políticas de tipado son casi obligatorias.

    Problemas comunes y cómo resolverlos rápido

    • “Todo está en rojo”: usa any temporalmente y migra por capas.
    • “Demasiadas anotaciones”: recuerda la inferencia. Limpia anotaciones redundantes.
    • “Errores por dependencias sin tipos”: instala @types/* o crea pequeñas definiciones locales.

    Integración con el ecosistema moderno

    – Next.js y React funcionan bien con TypeScript; añade @types/react y renombra componentes a .tsx.

    – Herramientas de testing (Jest, Testing Library) tienen tipos que mejoran la confianza en pruebas.

    – Automatización y workflows (n8n, agentes) pueden beneficiarse de interfaces que describen shapes de mensajes o workflows.

    Conclusión: comienza pequeño, gana práctico

    Si odias los tipos, no necesitas amar TypeScript para aprovecharlo. Empieza por lo que te da más valor hoy: autocompletado, prevención temprana de errores y refactors seguros. Usa inferencia, JSDoc y una configuración laxa. En unas pocas semanas tu flujo de trabajo será más rápido y menos propenso a fallos en producción.

    Recursos

    Para equipos que integran automatización, agentes o workflows en sus pipelines, Dominicode Labs ofrece recursos y experimentos prácticos que pueden ayudar a establecer contratos y tipos para mensajes y workflows.

    FAQ

    ¿Por qué no debo anotar todo con tipos?

    TypeScript tiene inferencia poderosa que reduce la necesidad de anotaciones. Anotar solo lo que aporta claridad evita ruido y trabajo innecesario.

    ¿Cómo empiezo sin romper mi repo en producción?

    Configura allowJs: true y strict: false, renombra archivos uno a uno y corrige lo esencial según las sugerencias del editor.

    ¿Qué hago si todo aparece en rojo?

    Usa any como parche temporal y migra por capas. Prioriza tipar APIs públicas primero.

    ¿Puedo obtener beneficios sin configurar un build-step?

    Sí. Añade // @ts-check y documentación JSDoc en archivos .js para autocompletado y chequeo estático sin compilación.

    ¿Cuándo debo pasar a strict?

    Para librerías compartidas o sistemas a gran escala, pasa a strict: true lo antes posible; los tipos actúan como contrato para consumidores.

    ¿Cómo manejo dependencias sin tipos?

    Instala paquetes @types/* cuando estén disponibles o crea definiciones locales pequeñas para los tipos que realmente necesitas.

    ¿Debería evitar el uso de any por completo?

    Evítalo como hábito. Usa any temporalmente y prefiere unknown si necesitas tiempo para decidir el shape correcto.
  • Cómo los desarrolladores de JavaScript pueden iniciarse en Ruby

    Cómo los desarrolladores de JavaScript pueden iniciarse en Ruby

    Introduccion a Ruby para Javascript devs

    Tiempo estimado de lectura: 5 min

    • Choque de modelo mental: Ruby es una filosofía orientada a la legibilidad y la productividad, no solo “otro lenguaje”.
    • Todo es objeto y retorno implícito: números, strings y nil exponen métodos; los métodos devuelven la última expresión.
    • Flujo tradicionalmente síncrono: MRI con GIL cambia las decisiones de concurrencia respecto a Node.
    • Ecosistema maduro: Bundler/Gems y Rails favorecen convención sobre configuración para backends monolíticos.

    Introducción

    Una Introduccion a Ruby para Javascript devs debe arrancar por el choque de modelo mental: Ruby no es “otro lenguaje”; es una filosofía que prioriza legibilidad, consistencia y productividad. Si vienes de Node/Browser —event loop, promesas, callbacks— aquí verás un sistema más lineal, orientado a objetos en su núcleo y con convenciones que reducen decisiones repetitivas. Este artículo explica las diferencias prácticas, ejemplos comparativos y criterios para decidir cuándo Ruby aporta valor real.

    Resumen rápido (lectores con prisa)

    Qué es: Un lenguaje orientado a objetos cuya sintaxis y convenciones priorizan legibilidad y productividad.

    Cuándo usarlo: Scripts, automatización, backends monolíticos con reglas de negocio complejas y proyectos donde la claridad importa.

    Por qué importa: Reduce boilerplate, facilita flujos secuenciales y favorece código mantenible.

    Cómo funciona (breve): Todo es objeto, retorno implícito en métodos, bloques nativos y ejecución tradicionalmente síncrona (MRI con GIL).

    ¿Qué cambia para un desarrollador JS en Ruby?

    Ruby fue diseñado por Yukihiro “Matz” Matsumoto con la idea de que el lenguaje se adapte al programador. Eso tiene consecuencias concretas:

    • Todo es objeto. No hay primitivos discontinuos: números, strings y hasta nil son instancias de clases y exponen métodos.
    • Retorno implícito. El valor de la última expresión de un método se devuelve automáticamente.
    • Sintaxis más permisiva. Paréntesis opcionales, bloques nativos (do...end / {}) en lugar de pasar callbacks como en JS.
    • Modelo de ejecución tradicionalmente síncrono. MRI usa GIL; para más contexto, leer sobre GIL, frente al modelo asíncrono y no bloqueante de Node.

    Estos puntos no son ornamentales; cambian cómo estructuras errores, pruebas y scripts.

    Ejemplos prácticos: comparar mentalidades

    Iteración y callbacks

    JavaScript (Node):
    const doubled = [1,2,3].map(n => n * 2);
    
    Ruby:
    doubled = [1,2,3].map { |n| n * 2 }  # Bloque inline
    

    Retorno implícito y string interpolation

    JavaScript:
    const greet = name => {
      return `Hello, ${name}`;
    };
    
    Ruby:
    def greet(name)
      "Hello, #{name}"  # se retorna implícitamente
    end
    

    Hashes y Symbols (clave frecuente en Ruby)

    user = { name: "Alex", role: :admin }
    user[:role] # => :admin
    

    Symbols (:admin) son inmutables y ocupan menos memoria que strings repetidos.

    Ecosistema y herramientas: Bundler, Gems y Rails

    La gestión de dependencias en Ruby se apoya en Bundler y RubyGems: Gemfile y Gemfile.lock garantizan reproducibilidad (bundle install). Documentación: Bundler y RubyGems.

    Rails es el marco de referencia para backends monolíticos en Ruby (Rails). Rails impone convención sobre configuración, patrones MVC claros, generators y un ORM maduro (ActiveRecord). Si vienes de Express —minimalista— Rails te obliga a organizar, lo que es ventaja cuando buscas consistencia en equipos.

    Asincronía y concurrencia: cómo pensar distinto

    Node te enseña a diseñar alrededor de la no-bloqueo. Ruby, especialmente MRI con GIL, tiende a bloquear en operaciones de I/O, aunque existen alternativas: JRuby o runtimes y bibliotecas como async. Para scripts, migraciones o procesos batch, el bloqueo secuencial simplifica el razonamiento y debugging; para sistemas de alta concurrencia I/O-bound, Node/Go siguen siendo mejores elecciones.

    Criterio técnico

    • Usa Ruby para tareas donde la simplicidad del flujo secuencial sea prioridad (scripts, ETL, CLIs).
    • Considera otros runtimes o arquitecturas (workers, colas) si necesitas alta concurrencia.

    Dónde Ruby aporta más valor (y por qué)

    • Scripting y automatización: escribir tareas con menos boilerplate que en Node.
    • Backends monolíticos con reglas complejas: la convención de Rails acelera decisiones arquitectónicas.
    • Ecosistemas específicos: Shopify y muchas apps legacy usan Ruby; entenderlo es estratégico (Shopify).
    • Calidad de código a largo plazo: menos churn en librerías y una comunidad conservadora y estable.

    Riesgos y cuándo evitarlo

    No elijas Ruby solo por moda. Si necesitas máxima concurrencia I/O o latencia ultrabaja, evalúa Node/Go/Rust. Si tu equipo no acepta la convención (opinionated stacks), Rails puede chocar con culturas “libertarias” de micro-librerías.

    Pasos prácticos para empezar (ruta recomendada)

    1. Escribe scripts sencillos: instala Ruby, crea un script.rb que conecte a la DB o lea ficheros.
    2. Aprende bloques y Symbols: son idiomáticos y aparecerán en todas las librerías.
    3. Usa Bundler y Gemfile desde el primer día.
    4. Súbete a Sinatra para entender HTTP mínimo, luego Rails para apps completas.
    5. Integra pruebas (RSpec) y tasks con Rake.

    Recursos

    Ruby oficial: Ruby oficial, Rails: Rails, Bundler: Bundler.

    Dominicode Labs

    Para quienes exploran automatización y workflows en proyectos de ingeniería, continúe con ejercicios prácticos y comparativas de integración. Más recursos y experimentos prácticos están disponibles en Dominicode Labs, donde publicaremos ejemplos: scripts de migración, patrón de servicios en Rails y comparativas de rendimiento real entre stacks.

    FAQ

    Respuesta: El choque es que Ruby favorece un flujo lineal orientado a objetos y convenciones que reducen decisiones repetitivas, mientras que JS/Node tienden a modelos asíncronos basados en event loop, promesas y callbacks.

    Respuesta: Usa Ruby cuando priorices simplicidad del flujo secuencial: scripts, ETL, CLIs y backends monolíticos con reglas de negocio complejas. Para sistemas I/O-bound con alta concurrencia, considera Node/Go.

    Respuesta: Ruby (MRI) tiende a un modelo síncrono y puede bloquear en I/O por el GIL; existen alternativas y bibliotecas que permiten concurrencia, pero la recomendación es diseñar en consecuencia o usar otros runtimes si la concurrencia es crucial.

    Respuesta: Los Symbols (ej. :admin) son valores inmutables usados como claves y etiquetas, ocupan menos memoria que strings repetidos y son idiomáticos en librerías Ruby.

    Respuesta: Empieza por escribir scripts sencillos, aprende bloques y Symbols, usa Bundler y Gemfile, prueba con Sinatra y luego Rails; integra pruebas con RSpec y tareas con Rake.

    Respuesta: No es obligatorio. Rails es la opción estándar para aplicaciones monolíticas por su convención y herramientas, pero puedes usar Sinatra u otros frameworks según la necesidad del proyecto.

  • Mejora tus Core Web Vitals con técnicas prácticas y diagnósticos precisos

    Mejora tus Core Web Vitals con técnicas prácticas y diagnósticos precisos

    Optimización Web Real: Mejorando los Core Web Vitals paso a paso

    Optimización Web Real: Mejorando los Core Web Vitals paso a paso empieza por medir con rigor, identificar los cuellos de botella que afectan a LCP, CLS e INP, y aplicar soluciones concretas —no parches— que reduzcan latencia y estabilicen la experiencia. Este artículo va directo al diagnóstico con PageSpeed Insights y a las correcciones prácticas que realmente mueven la aguja.

    Tiempo estimado de lectura: 6 min
    • Medir antes de tocar código: combina Lab Data (Lighthouse) y Field Data (CrUX) con PageSpeed Insights.
    • Prioriza LCP, CLS e INP: objetivos: LCP < 2.5s, CLS < 0.1, INP < 200ms.
    • Soluciones prácticas: priorizar recursos LCP, reservar espacio para elementos, reducir bloqueo del hilo principal.
    • Automatiza vigilancia: Lighthouse CI en CI, jobs periódicos y alertas (PageSpeed API → Slack/Teams).

    Introducción

    Antes de tocar código, la optimización efectiva empieza por mediciones reproducibles y por priorizar cambios que afecten a la mayoría de usuarios. Este artículo presenta diagnóstico con Lighthouse/PageSpeed Insights, diferencias entre Lab Data y Field Data, y acciones prácticas para LCP, CLS e INP.

    Resumen rápido (lectores con prisa)

    Qué es: Conjunto de métricas (LCP, CLS, INP) que miden la experiencia de carga, estabilidad visual e interactividad.

    Cuándo usarlo: Para priorizar mejoras de rendimiento que impacten a usuarios reales en producción.

    Por qué importa: Afecta percepción de velocidad, retención y conversiones.

    Cómo funciona: Combina Lab Data (Lighthouse) para reproducir problemas y Field Data (CrUX) para validar impacto real.

    Diagnóstico: Lab Data vs Field Data y cómo usarlos (PageSpeed, Lighthouse, CrUX)

    Antes de tocar código, mide. Usa PageSpeed Insights para combinar Lab Data (Lighthouse) y Field Data (Chrome UX Report, CrUX: CrUX). Lighthouse te ayuda a reproducir problemas; CrUX te dice si esos problemas afectan a usuarios reales.

    Reglas claras

    • Ejecuta Lighthouse en modo limpio (sin extensiones, en incognito) o en un entorno CI reproducible. Docs: Lighthouse.
    • Si CrUX muestra malos valores, prioriza arreglos que impacten a la mayoría de usuarios (conexiones lentas, dispositivos móviles).
    • Usa Lighthouse CI en tu pipeline para evitar regresiones.

    Las métricas a mejorar

    • LCP (Largest Contentful Paint) — objetivo < 2.5s.
    • CLS (Cumulative Layout Shift) — objetivo < 0.1.
    • INP (Interaction to Next Paint) — objetivo < 200ms.

    LCP: priorizar lo que el usuario ve primero

    LCP suele ser la hero image o el bloque de texto más grande. Si ese recurso llega tarde, la percepción de velocidad se hunde.

    Acciones prácticas

    1. Identifica el recurso LCP en Lighthouse.
    2. Priorízalo con fetchpriority.
    3. No lo hagas lazy. loading="lazy" está bien para imágenes below-the-fold, no para LCP.
    4. Sirve formatos modernos: WebP/AVIF reduce tamaños significativos. Automatiza en build (Next.js <Image /> o pipeline de imágenes).
    <img src="/hero.avif" alt="Hero" fetchpriority="high" width="1200" height="600">
    import Image from 'next/image';
    <Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />

    priority en Next.js mapea a la idea de fetchpriority y evita lazy-loading.

    Complementos

    • Preconnect al CDN para reducir handshake: <link rel=”preconnect” href=”https://cdn.example.com“>.
    • font-display: swap para evitar bloqueos por fuentes ( MDN ).

    CLS: reserva espacio, evita saltos inesperados

    CLS es casi siempre consecuencia de no reservar espacio para recursos que aparecen después.

    Principios

    • Declara width y height en imágenes y videos. El navegador calcula el aspect-ratio y reserva el espacio.
    • Para contenido dinámico (ads, embeds), usa contenedores con min-height y placeholders visuales.
    • Evita inyectar DOM encima del contenido existente sin un espacio reservado.

    Ejemplo para un iframe de anuncio

    <div style="min-height:250px; width:100%; background:#f5f5f5;">
      <!-- script del anuncio se montará aquí -->
    </div>

    Fonts y CLS: font-display: swap reduce FOIT y, por tanto, desplazamientos cuando la tipografía aparece.

    INP: reducir bloqueo del hilo principal (Main Thread)

    INP mide la latencia percibida en interacciones. Si el hilo principal está ocupado procesando JS, la UI deja de responder.

    Estrategias efectivas

    • Code splitting: no empaquetes todo el JS en la carga inicial. Usa dynamic import() y lazy load para componentes pesados (charts, mapas, editores).
    • Difiere o carga de forma condicional scripts de terceros (async, defer, o carga tras interacción).
    • Identifica tareas largas con Performance Profiler y conviértelas en trabajos más pequeños (chunking) o Web Workers.

    Ejemplo React/Next dinámico

    const Heavy = dynamic(() => import('./Heavy'), { ssr: false });

    Cuidado con SSR: solo carga client-side cuando sea adecuado.

    Scripts de terceros: carga analítica con async/defer o condicionalmente tras interacción. Considera server-side tagging o consentimiento previo para scripts marketing.

    Integración en el workflow: automatizar y alertar

    Rendimiento es continuo, no un ticket que cierras. Integra estas comprobaciones en CI/CD:

    • Lighthouse CI en PRs para bloquear regresiones.
    • Jobs periódicos que consulten PageSpeed Insights API y empujen reportes a Slack/Teams.
    • Workflows automáticos con n8n o herramientas internas para recolectar métricas y alertar cuando CWV bajen.

    Ejemplo conceptual: n8n workflow que llama a PageSpeed API y notifica si LCP > 2.5s.

    Prioridad práctica: checklist para aplicar hoy

    1. Ejecuta PageSpeed Insights y revisa CrUX.
    2. Identifica el LCP y aplica fetchpriority="high"; elimina lazy en ese recurso.
    3. Añade width/height a todas las imágenes y placeholders para embeds.
    4. Cambia imágenes a WebP/AVIF en tu pipeline.
    5. Implementa code splitting y difiere terceros.
    6. Añade Lighthouse CI y un job periódico (API PageSpeed → Slack).

    Recursos y lectura técnica

    Si tu workflow incluye automatización o recopilación de métricas con herramientas como n8n, considera explorar Dominicode Labs como continuación lógica para construir pipelines de monitoreo y experimentación. Dominicode Labs ofrece recursos y plantillas orientadas a integrar PageSpeed y Lighthouse en procesos automatizados.

    FAQ

    ¿Qué diferencia hay entre Lab Data y Field Data?

    Lab Data (Lighthouse) se genera en un entorno controlado y es útil para reproducir y depurar problemas. Field Data (CrUX) refleja métricas recogidas de usuarios reales en producción.

    ¿Cómo identifico el recurso LCP?

    Lighthouse muestra el recurso considerado LCP en su reporte. Revisa la sección correspondiente para saber si es una imagen, un bloque de texto o un video y priorízalo.

    ¿Por qué es importante declarar width/height en imágenes?

    Declarar width y height permite al navegador calcular el aspecto y reservar el espacio, evitando desplazamientos de layout que causan CLS.

    ¿Cuándo debo usar WebP/AVIF?

    Usa WebP/AVIF cuando puedas procesar imágenes en tu pipeline o framework para reducir tamaños sin pérdida notable de calidad. Automatiza la conversión en build para no requerir cambios manuales.

    ¿Cómo reducir INP en aplicaciones con mucho JavaScript?

    Aplica code splitting, difiere carga de scripts no críticos, divide tareas largas en trozos más pequeños y considera Web Workers para trabajo pesado fuera del hilo principal.

    ¿Qué debo automatizar en mi pipeline de CI/CD?

    Automatiza Lighthouse CI en PRs para detectar regresiones, añade jobs periódicos que consulten PageSpeed Insights API y notifiquen a Slack/Teams cuando métricas críticas empeoren.