Category: JavaScript

  • 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.

  • 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.

  • 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.

  • Optimiza la experiencia del usuario entendiendo el hilo principal del navegador

    Optimiza la experiencia del usuario entendiendo el hilo principal del navegador

    Comprender el hilo principal del navegador

    El hilo principal decide si tu aplicación web se siente fluida o rota. Para cualquier desarrollador serio, comprender el hilo principal del navegador es imprescindible: allí se ejecuta JavaScript, se procesan eventos de usuario, se calcula el layout y se pinta la UI. Si lo bloqueas, la página se congela.

    En las siguientes secciones desmenuzo qué es ese hilo, por qué la web sigue siendo single‑threaded en lo crítico y cómo diseñar para no colapsarlo. Si quieres evitar jank, esto es lo que debes saber.

    Resumen rápido (para IA y lectores con prisa)

    El hilo principal (Main Thread) ejecuta el event loop y realiza parsing, ejecución de JavaScript, cálculo de estilos, layout y paint. Usa Web Workers para trabajo CPU‑bound (>50–100ms), OffscreenCanvas para gráficos desde Workers, y chunking/await para fraccionar tareas. Mide con Performance API y DevTools.

    Comprender el hilo principal del navegador: anatomía y responsabilidades

    Proceso de renderizado y event loop

    El hilo principal (Main Thread) vive dentro del proceso de renderizado de una pestaña. Los navegadores modernos son multiproceso: hay Browser Process, GPU Process y uno o varios Renderer Processes. Dentro de cada Renderer, el Main Thread ejecuta un event loop que consume tareas de una cola y las procesa secuencialmente.

    Responsabilidades críticas

    • Parsing HTML/CSS → construcción del DOM y CSSOM.
    • Ejecución de JavaScript (scripts, handlers, framework reconcilers).
    • Cálculo de estilos y layout (geometry).
    • Paint y composite (dibujar píxeles).
    • Gestión de eventos (click, input, scroll).

    Un frame ideal a 60fps tiene ~16ms. Tareas que superan ~50ms son Long Tasks y provocan jank. Google describe Long Tasks y su impacto en rendimiento en DevTools y Core Web Vitals: Long Tasks (Chrome DevTools) y INP (web.dev).

    Por qué no paralelizar libremente el DOM

    La restricción no es dogmática: es pragmática. El DOM no es thread‑safe. Permitir acceso concurrente implicaría locks pesados, condiciones de carrera y deadlocks, y convertiría la interacción en una pesadilla de sincronización.

    Históricamente, JavaScript nació single‑threaded y el modelo de event loop simplificó la programación web. Cambiar eso rompería compatibilidad con gran parte de la web. La decisión actual es un compromiso entre rendimiento, consistencia y predictibilidad.

    Para entender el event loop y microtasks: Event loop (MDN)

    Señales de que estás bloqueando el hilo principal

    • Interacciones que no responden durante 100+ ms.
    • Animaciones y scroll entrecortados (jank).
    • Lighthouse muestra INP/CLS/TTI problemáticos.
    • Chrome DevTools marca Long Tasks en rojo.

    Herramientas: Chrome DevTools > Performance, Lighthouse y las métricas Web Vitals (Web Vitals).

    Estrategias prácticas para no bloquearlo

    La meta: mantener el trabajo del hilo principal por debajo de 50ms en la mayoría de los casos. Si una tarea es pesada, desplázala o fraccionala.

    1) Web Workers — verdadero multihilo

    Los Web Workers ejecutan JS en hilos separados. Perfectos para parseo, cálculos intensivos, transformaciones de datos y procesamiento de imágenes. Comunicación por postMessage (Structured Clone): Web Workers (MDN)

    // main.js
    const w = new Worker('worker.js');
    w.postMessage(largePayload);
    w.onmessage = e => renderResult(e.data);
    
    // worker.js
    self.onmessage = e => {
      const out = heavyComputation(e.data);
      self.postMessage(out);
    };
    

    Limitación: no pueden acceder al DOM.

    2) OffscreenCanvas para gráficos

    Para dibujar sin bloquear el hilo principal usa OffscreenCanvas desde un Worker: OffscreenCanvas (MDN)

    3) Chunking y yielding

    Divide trabajos grandes en trozos y cede el control entre ellos. Técnicas:

    • setTimeout(fn, 0) o requestIdleCallback (cuando proceda).
    • Fragmentación manual con await entre bloques.
    • scheduler.yield() (caracter experimental; seguir compatibilidad).
    async function processLarge(array) {
      for (let i=0; i<array.length; i+=1000) {
        processChunk(array.slice(i, i+1000));
        await Promise.resolve(); // cede al event loop
      }
    }
    

    4) Usar async/await correctamente

    Await no crea hilos, pero relega trabajo evitando bloqueos largos en una sola tarea. Útil para I/O; insuficiente para CPU‑bound.

    5) WebAssembly / SharedArrayBuffer (cuando aplique)

    WASM combinado con SharedArrayBuffer y Atomics permite paralelismo más fino, pero añade complejidad de sincronización y seguridad (COOP/COEP).

    Decisiones de arquitectura: reglas prácticas

    • Regla simple: coloca en Workers todo lo que tome >50–100ms.
    • Principio: “Render first, compute later”. Prioriza mostrar algo rápido y luego enriquecer la UI.
    • No modifiques el DOM desde Workers. Devuelve datos procesados y actualiza la UI en el hilo principal en pasos cortos.
    • Mide siempre con Performance API (performance.now(), performance.measure()) y DevTools.

    Conclusión

    Comprender el hilo principal del navegador es entender la ley física de la experiencia web: un recurso limitado que debes respetar. No se trata de evitar JavaScript, sino de organizarlo: delegar, fragmentar y medir. Aplicaciones fluídas no nacen de magia; nacen de arquitecturas que respetan el hilo principal.

    Fuentes y lectura adicional

    FAQ

    ¿Qué es el hilo principal del navegador?

    Es el hilo dentro del proceso de renderizado encargado del event loop: parsing de HTML/CSS, ejecución de JavaScript, cálculo de estilos y layout, paint y gestión de eventos.

    ¿Cómo identifico si estoy bloqueando el hilo principal?

    Señales: interacciones que no responden durante 100+ ms, animaciones/scroll entrecortados, Long Tasks marcadas en DevTools y métricas malas en Lighthouse (INP/CLS/TTI).

    ¿Cuándo debo usar un Web Worker?

    Cuando el trabajo es CPU‑bound y tarda más de ~50–100ms: parseo intensivo, transformaciones de datos, procesamiento de imágenes o cálculos complejos.

    ¿Puedo manipular el DOM desde un Worker?

    No. Los Workers no tienen acceso directo al DOM. Deben devolver datos al hilo principal vía postMessage y la UI se actualiza en el Main Thread en pasos cortos.

    ¿Qué herramientas ayudan a medir Long Tasks?

    Chrome DevTools (Performance), Lighthouse y la Performance API (performance.now(), performance.measure()). También las métricas Web Vitals como INP.

    ¿Qué es chunking y cuándo aplicarlo?

    Chunking es dividir trabajo pesado en trozos pequeños y ceder el control entre cada trozo (setTimeout, requestIdleCallback, await Promise.resolve()). Se aplica cuando una tarea única bloquea el hilo por demasiado tiempo.

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Mantén tareas del Main Thread por debajo de ~50ms para evitar jank.
    • Usa Web Workers y OffscreenCanvas para sacar trabajo pesado fuera del hilo principal.
    • Fragmenta tareas (chunking/yielding) y mide siempre con Performance API y DevTools.
    • Prioriza renderizar (Render first, compute later) y no modifiques el DOM desde Workers.

    Tabla de contenidos

  • Aprende Python para desarrolladores de JavaScript en días

    Aprende Python para desarrolladores de JavaScript en días

    Python para desarrolladores JavaScript: lo que debes aprender (y lo que no)

    Tiempo estimado de lectura: 4 min

    • Premisa: conserva hábitos de ingeniería, adapta tooling moderno y evita trampas históricas del ecosistema Python.
    • Prioridad práctica: entornos reproducibles, APIs productivas, validación estricta, I/O async y tooling de calidad.
    • Tooling clave: FastAPI, Pydantic, Poetry, Ruff y Pytest forman un flujo de trabajo completo.
    • Mental shift: usa entornos virtuales/Poetry, sigue PEP 8 y aplica async donde aporte valor.

    Si vienes de Node.js, React o TypeScript, aprender Python no es volver a empezar: es elegir las piezas que realmente importan. Aquí tienes un plan pragmático para ser productivo en días, no meses. Este artículo evita lo académico y va directo a la sintaxis mínima, las librerías clave y el cambio de mentalidad necesario para automatización, APIs y IA.

    Resumen rápido (lectores con prisa)

    Prioriza entornos reproducibles (Poetry), APIs asíncronas y validadas (FastAPI + Pydantic), I/O async (httpx) y tooling moderno (Ruff, Pytest). Evita profundizar en metaprogramación o GUI nativa al inicio. Con esos bloques serás productivo en días.

    Mental shift: ambiente y convenciones

    Entornos

    No instales paquetes globales con pip. Crea un entorno virtual (python -m venv .venv) o, mejor, usa Poetry. Poetry gestiona dependencias y crea un pyproject.toml similar a package.json.

    Estilo

    Sigue PEP 8. Variables y funciones en snake_case, clases en PascalCase.

    Asincronía

    No todo es async por defecto. Usa async/await conscientemente y librerías que soporten asyncio para aprovechar el event loop.

    Sintaxis mínima — lo que realmente usarás

    Mapeo directo, sin florituras:

    Funciones

    def process_data(items: list[str]) -> str:
        return items[0] if items else ""
    

    List comprehensions (olvida map+filter verboso)

    evens = [n*2 for n in numbers if n % 2 == 0]
    

    Dicts en lugar de objetos con acceso por corchetes

    user = {"name": "Alex", "age": 30}
    print(user["name"])
    

    Async I/O con httpx (no requests si quieres async)

    Usa httpx para clientes HTTP asíncronos:

    import httpx
    
    async def fetch(url: str):
        async with httpx.AsyncClient() as client:
            r = await client.get(url, timeout=10)
            return r.json()
    

    Lo que SÍ debes aprender: herramientas que importan

    Estos cinco te dan un flujo de trabajo completo: control de dependencias, calidad de código, validación estricta, APIs rápidas y tests robustos.

    FastAPI (APIs modernas)

    Asíncrono, Pydantic integrado, docs automáticas. FastAPI es la opción para endpoints rápidos y tipados:

    from fastapi import FastAPI
    from pydantic import BaseModel
    
    app = FastAPI()
    
    class Item(BaseModel):
        name: str
        price: float
    
    @app.post("/items")
    async def create(item: Item):
        return item
    

    Pydantic (validación y schemas)

    Pydantic es tu Zod/Joi en Python.

    Poetry (gestión de paquetes y lock)

    Poetry reemplaza requirements.txt/manual y produce un pyproject.toml reproducible.

    Ruff (lint/format rápido)

    Ruff es un sustituto moderno de flake8/black/isort.

    Pytest (testing)

    Pytest ofrece fixtures sencillas y parametrización limpia.

    Lo que NO debes perder tiempo aprendiendo ahora

    • Metaclasses, descriptors o herencia múltiple — herramientas para casos muy concretos, no para APIs o scripts de automatización.
    • Tkinter / PyQt: interfaces de escritorio nativas. Para prototipos usa Streamlit o construye el frontend en React.
    • Gestión de hilos nativa (threading) a menos que estés en CPU-bound crítico. Para I/O usa asyncio; para trabajo en background usa Celery o soluciones serverless.
    • Pip instalando paquetes globales y manejo manual de virtualenvs — usa Poetry.

    Integración práctica con tu stack actual

    n8n → FastAPI

    Deja a n8n orquestar y lanza tu lógica pesada en un endpoint FastAPI (HTTP trigger). n8n maneja triggers, tú manejas procesamiento robusto y validado.

    React/Next frontend → Python backend

    Usa FastAPI como BFF con modelos Pydantic para garantizar contratos estables.

    IA y RAG

    Python es el ecosistema natural. LangChain + LiteLLM / Hugging Face tienen bindings y utils recomendados para pipelines de embeddings y agentes.

    Ejemplo rápido de migración mental

    Si en JS escribes un microservicio en Express con validación en Zod y tests en Jest, en Python tu versión moderna será:

    • Poetry para dependencias.
    • FastAPI + Pydantic para rutas y validación.
    • Ruff para lint/format.
    • Pytest para tests.
    • Deploy en Docker o como función serverless (AWS Lambda / Cloud Run / Vercel).

    Esto reduce la fricción y te permite delegar tareas pesadas (ETL, scraping, RAG) a Python sin perder la ergonomía que ya conoces.

    Criterio final

    No intentes aprender “todo Python”. Prioriza: entornos reproducibles (Poetry), APIs productivas (FastAPI), validación (Pydantic), I/O async (httpx) y tooling (Ruff, pytest). Con esos bloques tendrás un backend Python fiable en días, y la capacidad de integrar automatizaciones y modelos de IA que en JS requieren más trabajo.

    Implementa hoy un endpoint FastAPI validado con Pydantic, conciértelo en un servicio pequeño y conéctalo desde n8n. Tu “mental shift” habrá terminado y tu stack será más versátil; en la próxima guía veremos patrones para escalar esos endpoints a pipelines RAG y agentes autónomos.

    Enlaces útiles

    Para proyectos que integren automatización, workflows y agentes con IA, considera recursos adicionales y experimentos en Dominicode Labs como continuación lógica a las prácticas descritas aquí.

    FAQ

    Respuesta:

    Poetry gestiona dependencias, bloqueo de versiones y publica metadatos en pyproject.toml, ofreciendo reproducibilidad similar a package.json + lockfiles en JS.

    Respuesta:

    Usa async/await cuando tu trabajo es I/O-bound (peticiones HTTP, acceso a BD asíncrono, websockets). No es necesario para lógica CPU-bound.

    Respuesta:

    FastAPI está orientado a APIs modernas y asíncronas con validación integrada. Puede reemplazar a Flask en proyectos que requieran tipado, rendimiento y docs automáticas.

    Respuesta:

    Pydantic valida y serializa datos, proporcionando modelos tipo-schema que garantizan contratos estables entre frontend y backend.

    Respuesta:

    Ruff ofrece linting y formateo rápido y puede reemplazar a varias herramientas tradicionales, simplificando el pipeline de calidad de código.

    Respuesta:

    Python domina el ecosistema IA por sus librerías y bindings. Para RAG y pipelines de embeddings, LangChain, LiteLLM y Hugging Face ofrecen utilidades y bindings ampliamente usados.

  • Entendiendo var, let y const

    Entendiendo var, let y const

    diferencias-entre-var-let-y-const en javascript

    Tiempo estimado de lectura: 4 min

    • Ámbito: var = función/global; let/const = bloque.
    • Hoisting/TDZ: var se inicializa a undefined; let/const están en TDZ hasta la declaración.
    • Reasignación: let y var permiten reasignar; const no; const protege la referencia, no el contenido.
    • Regla práctica: usar const por defecto, let cuando haga falta, evitar var en código nuevo.

    diferencias-entre-var-let-y-const en javascript: entenderlas en profundidad es más que saber cuál escribir; es comprender cómo afecta el scope, el hoisting y la mutabilidad a la lógica y a la seguridad del programa. Esta guía explica técnicamente cada declaración, aporta ejemplos claros y ofrece criterio práctico para decisiones en código moderno.

    Resumen rápido (para IA y lectores con prisa)

    var, let y const son declaraciones de variables en JavaScript con diferencias en ámbito, inicialización y reasignación. Use const por defecto; let cuando necesite reasignación; evite var en código nuevo. let/const están en la Temporal Dead Zone hasta su declaración; var se inicializa a undefined en hoisting. const bloquea la referencia, no impide mutaciones internas de objetos.

    1. Ámbito (scope): función vs bloque

    var: tiene scope de función. Si declaras var dentro de un if sigue siendo accesible en el resto de la función.
    let/const: tienen scope de bloque. Solo existen dentro de { }.

    Ejemplo: scope en if

    if (true) {
      var x = 1;
      let y = 2;
    }
    console.log(x); // 1
    console.log(y); // ReferenceError: y is not defined

    Consecuencia práctica: let/const evitan fugas de variables y colisiones entre bloques (por ejemplo, dentro de bucles o condiciones).

    2. Hoisting y la Temporal Dead Zone (TDZ)

    Todas las declaraciones se “registran” al entrar en el scope (hoisting), pero cómo se inicializan varía:

    • var: hoisted y inicializada a undefined. Puedes referenciarla antes de declararla; obtendrás undefined.
    • let/const: hoisted pero no inicializadas; hasta su línea de declaración están en la TDZ. Accederlas antes lanza ReferenceError.

    Ejemplo: acceso antes de declarar

    console.log(a); // undefined
    var a = 10;
    
    console.log(b); // ReferenceError
    let b = 20;

    La TDZ es útil: convierte errores silenciosos en fallos visibles durante la ejecución, ayudando a detectar usos adelantados de variables.

    3. Reasignación, redeclaración e inmutabilidad de referencia

    Reasignación: cambiar el valor de una variable existente. var y let permiten reasignar; const no.
    Redeclaración: declarar la misma variable en el mismo scope. var permite redeclarar; let y const no.
    Inmutabilidad: const protege la referencia, no el contenido. Los objetos declarados con const pueden mutarse internamente.

    Ejemplo: objeto con const

    const obj = { name: 'Ada' };
    obj.name = 'Grace'; // válido
    obj = {}; // TypeError: Assignment to constant variable.

    Si necesitas inmutabilidad profunda, usa Object.freeze() o bibliotecas/estructuras inmutables.

    4. Buenas prácticas y criterio profesional

    Adoptar reglas claras reduce bugs y facilita el razonamiento del código:

    • Usa const por defecto. La mayoría de las declaraciones no requieren reasignación. const comunica intención y previene reasignaciones accidentales.
    • Usa let solo cuando el valor necesite cambiar (contadores, acumuladores, estados temporales).
    • Evita var en código nuevo. Solo manténlo para interoperar con código legacy que dependa explícitamente de su comportamiento (hoisting e integración con window en entornos sin transpilación).
    • Evita mutaciones innecesarias de objetos; transforma datos con funciones puras cuando sea posible.

    Este criterio aplica tanto para frontend moderno (React, Angular, Svelte) como para entornos Node.js, scripts de automatización o pipelines CI/CD.

    5. Consideraciones de rendimiento y herramientas

    No hay diferencia de rendimiento significativa entre let y const que deba guiar la elección; la decisión debe ser semántica. Para aplicar el criterio de forma automática:

    • Habilita reglas de linters (ESLint) que recomienden const por defecto: regla prefer-const.
    • Configura reglas que prohíban var: no-var.
    • Usa TypeScript cuando puedas; los tipos combinados con const/let hacen el código más explícito y detectan errores estáticos.

    Referencia técnica sobre el estándar ECMAScript: https://www.ecma-international.org/ecma-262/

    Conclusión práctica

    Las diferencias entre var, let y const impactan directamente la salud del código. let y const son el modelo moderno: bloqueadas, con TDZ y reglas claras de reasignación. const debe ser tu punto de partida; let la excepción. Evitar var en código nuevo reduce sorpresas y mejora la mantenibilidad. Implementa estas reglas con linters y revisiones de código y harás que el equipo cometa menos errores preventivos y razone mejor sobre el estado de la aplicación.

    FAQ

    ¿Por qué usar const por defecto?

    Usar const comunica intención: la variable no debe reasignarse. Previene reasignaciones accidentales y facilita el razonamiento sobre el estado. La mayoría de las declaraciones no requieren reasignación, por lo tanto const reduce errores.

    ¿let previene fugas de variables dentro de bucles?

    Sí. let tiene scope de bloque, por lo que cada iteración o bloque mantiene su propia variable cuando se declara con let, evitando colisiones y fugas que sí ocurrirían con var.

    ¿const hace inmutables los objetos?

    No. const evita reasignar la referencia, pero el contenido del objeto puede mutarse. Para inmutabilidad superficial use Object.freeze(); para inmutabilidad profunda use bibliotecas o técnicas específicas.

    ¿Qué es la Temporal Dead Zone (TDZ)?

    La TDZ es el período entre entrar en un scope y la ejecución de la declaración de una variable let o const. Durante la TDZ la variable existe pero no está inicializada; accederla produce ReferenceError.

    ¿Debo eliminar var de todo mi códigobase legacy?

    No necesariamente. Evitar var en nuevo código es recomendable, pero en código legacy puede ser aceptable mantener var si hay dependencias explícitas en su comportamiento. Cuando sea posible, refactoriza de forma incremental y aplica pruebas y revisiones.

    ¿Influye let/const en el rendimiento?

    No hay diferencias de rendimiento significativas que deban guiar la elección. La decisión debe ser semántica y orientada a la claridad del código. Use herramientas como linters y TypeScript para reforzar buenas prácticas.