Category: JavaScript

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

  • Server Actions en Next.js y su Impacto en el Reclutamiento

    Server Actions en Next.js y su Impacto en el Reclutamiento

    Server Actions en Next.js: ¿El fin de las APIs REST tradicionales?

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Server Actions son ideales para mutaciones originadas en la UI de Next.js y mejoran la DX reduciendo boilerplate.
    • No reemplazan REST para webhooks, clientes externos o arquitecturas desacopladas.
    • Trata cada Server Action como un endpoint público: valida, autentica y aplica rate limits.
    • Usa Route Handlers (APIs REST) para interoperabilidad, streaming binario y contratos estables entre servicios.

    Introducción

    Server Actions en Next.js permiten ejecutar funciones del servidor invocadas desde el cliente. Next.js hace la fontanería (serialización, endpoint POST, transporte). Documentación oficial: Documentación oficial y análisis en Vercel: análisis en Vercel.

    Lo digo rápido y con claridad: no son el fin de las APIs REST tradicionales. Pero cambian radicalmente cómo gestionas mutaciones internas. Si entiendes cuándo usar cada patrón, ahorras horas de debugging y deuda técnica.

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

    Qué es: Server Actions son funciones marcadas con 'use server' que Next.js ejecuta en el servidor cuando se invocan desde el cliente.

    Cuándo usarlo: Mutaciones originadas en la UI de Next.js (formularios, botones, CRUD pequeño).

    Por qué importa: Reduce boilerplate, facilita revalidación y mejora la DX compartiendo tipos entre cliente y servidor.

    Cómo funciona (en una línea): Next.js convierte la llamada en una petición POST y ejecuta la función en el servidor.

    Server Actions vs APIs REST — visión general

    Sí aparecen como sustituto natural dentro del dominio de la UI. No sustituyen REST fuera del dominio de la aplicación. Dicho de otra forma: son fantásticos para mutaciones internas; son inútiles para webhooks, clientes externos y servicios desacoplados.

    A continuación comparo ambos enfoques con ejemplos y criterio práctico.

    Cómo funcionan, en dos líneas

    Server Action

    Función marcada con 'use server' que Next.js ejecuta en el servidor cuando la invocas desde un formulario o handler.

    Route Handler (API REST)

    Endpoint explícito en app/api/.../route.ts que responde a cualquier cliente HTTP.

    Bajo el capó, una Server Action es una petición HTTP POST generada por Next.js, pero con menos boilerplate para ti.

    Ejemplo: crear un post (Route Handler)

    Backend (app/api/posts/route.ts):

    import { NextResponse } from 'next/server';
    import { db } from '@/lib/db';
    
    export async function POST(request: Request) {
      const body = await request.json();
      // validar con Zod aquí
      const post = await db.post.create({ data: body });
      return NextResponse.json(post, { status: 201 });
    }

    Frontend (cliente):

    'use client';
    async function handleSubmit(e: React.FormEvent) {
      e.preventDefault();
      const data = Object.fromEntries(new FormData(e.currentTarget));
      await fetch('/api/posts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(data),
      });
    }

    Control total sobre headers, status y streaming. Compatible con cualquier cliente (mobile, cron jobs, n8n).

    Ejemplo: crear un post (Server Action)

    Acción (app/actions.ts):

    'use server';
    import { db } from '@/lib/db';
    import { revalidatePath } from 'next/cache';
    
    export async function createPost(formData: FormData) {
      const title = String(formData.get('title') ?? '');
      const content = String(formData.get('content') ?? '');
      // validar y auth aquí
      await db.post.create({ data: { title, content } });
      revalidatePath('/posts');
    }

    Frontend:

    import { createPost } from '@/app/actions';
    
    export default function Form() {
      return (