Author: Dominicode

  • Por qué deberías evitar enums en TypeScript para tus proyectos

    Por qué deberías evitar enums en TypeScript para tus proyectos

    ¿Debemos evitar enums en TypeScript?

    Tiempo estimado de lectura: 3 min

    • Los enums generan código en runtime y rompen la promesa de tipos que desaparecen.
    • Alternativas preferibles: union types para tipos puros; objetos as const para valores en runtime.
    • const enum inlining es frágil y rompe flujos modernos como isolatedModules.
    • Plan práctico: encontrar enums, priorizar UI/API, sustituir y añadir regla ESLint.

    Introducción

    ¿De verdad sigues escribiendo enums en tus proyectos nuevos? Si tu respuesta es sí, hay que hablar en serio.

    En TypeScript los enums son una característica heredada con promesas bonitas y costes escondidos. La pregunta no es moral: es práctica. ¿Te importa el tamaño del bundle, la previsibilidad del runtime y la compatibilidad con herramientas modernas?
    Entonces sí: evita enums por defecto.

    Resumen rápido (lectores con prisa)

    Los enums generan código JavaScript en runtime; prefieres union types si sólo necesitas tipos, y objetos as const si necesitas valores en runtime. const enum hace inlining pero es frágil con herramientas modernas.

    ¿Debemos evitar enums en TypeScript? — la explicación breve

    TypeScript existe para dar tipos que desaparecen al compilar. Interfaces y type unions se evaporan. Los enums no: generan código JavaScript en runtime. Eso rompe la promesa de “tipos que no pesan”.

    Mira esto:

    enum Status { Active, Inactive, Pending }

    Se transforma en una IIFE que crea un objeto y mapas reversos. Resultado: bytes extra, más complejidad para los bundlers y más superficie para errores silenciosos.

    Si quieres leer la doc oficial.

    Por qué los enums son problemáticos hoy

    • Generan código en runtime. No es sólo “feo”: es carga real en producción.
    • Tree-shaking menos efectivo. Esa IIFE puede sobrevivir al proceso de eliminación de código muerto (ver guía de Webpack).
    • Los enums numéricos permiten reverse mapping y asignaciones inesperadas: puedes acabar con números inválidos sin que TypeScript te lo grite.
    • const enum hace inlining, pero rompe flujos modernos con isolatedModules (esbuild, SWC, tsup) — herramientas que usamos para acelerar builds (esbuild, Vite).

    Qué usar en su lugar (patrones que sí funcionan)

    No te mando a la guerra sin armas. Hay alternativas simples, robustas y alineadas con el ecosistema JS.

    1) Union Types (cuando sólo quieres limitar valores)

    type Status = 'active' | 'inactive' | 'pending';
    function setStatus(s: Status) { /* ... */ }
    • Cero runtime.
    • Tipado claro y autocumplido en IDE.
    • Perfecto para props, APIs y funciones.

    2) Objetos as const (cuando necesitas valores en runtime)

    const STATUS = {
      ACTIVE: 'active',
      INACTIVE: 'inactive',
      PENDING: 'pending'
    } as const;
    
    type Status = typeof STATUS[keyof typeof STATUS];
    • Tienes objeto para iterar (Object.values).
    • Tipos literales extraídos automáticamente.
    • JavaScript simple en runtime: nada de IIFEs raras.

    3) Branded Types (si necesitas nominalidad)

    Cuando realmente quieres que dos tipos similares no sean intercambiables, usa branded types. Es más verboso, pero evita los problemas del tipado nominal que los enums intentan resolver con coste.

    `const enum`: la promesa rota

    Sí, const enum elimina el objeto y hace inlining. Suena perfecto. En la práctica es frágil:

    • Rompe con isolatedModules.
    • Empeora sourcemaps y debugging.
    • Depende del flujo de compilación; en monorepos o builds parciales, es una bomba de tiempo.

    Conclusión: no es solución universal. Evitarlo también es sensato.

    Qué hacer en tu codebase (plan práctico)

    1. Busca: grep por enum en tu repo.
    2. Prioriza: enums usados en UI y API surface primero.
    3. Sustituye:
      • Si no necesitas iteración: cambia por union type.
      • Si necesitas runtime: cambia por objeto as const.
    4. Añade regla ESLint para evitar futuros enums:
      {
        "@typescript-eslint/no-restricted-syntax": [
          "error",
          {
            "selector": "TSEnumDeclaration",
            "message": "Usa union types u objetos as const en lugar de enums"
          }
        ]
      }
    5. Prueba, mide bundle y sourcemaps. Verás la diferencia.

    ¿Cuándo sí considerar un enum?

    Muy raras veces. Casos válidos:

    • Integración con código legacy que usa enums.
    • SDKs donde los valores numéricos son parte del contrato público (protocolos).
    • Interoperabilidad con librerías externas que exigen enums.

    Pero si arrancas un proyecto nuevo, el path claro es evitar enums.

    Cierre con criterio

    Los enums ya no son la herramienta “obvia” que eran. Son una reliquia que introduce deuda técnica donde no la necesitas. Prefiere union types o as const. Son más predecibles, más rápidos y más amables con las herramientas modernas.

    Revisa tu código: haz una búsqueda rápida de enum. Si aparecen, planifica una migración por sprint. Tu bundle —y tu tranquilidad— lo agradecerán.

    FAQ

     

    ¿Por qué los enums generan código en runtime?

    Porque TypeScript traduce los enums a objetos JavaScript (a menudo via una IIFE) que existen en el runtime para soportar features como reverse mapping y valores numéricos.

    ¿No puedo usar const enum para evitar el código extra?

    const enum hace inlining eliminando el objeto, pero es frágil: rompe con isolatedModules, complica sourcemaps y depende de flujos de compilación coherentes en monorepos y builds parciales.

    ¿Qué gana mi bundle evitando enums?

    Menos bytes en el runtime, mejor posibilidad de tree-shaking y menos complejidad para los bundlers, lo que suele traducirse en bundles más pequeños y builds más predecibles.

    ¿Los objetos as const permiten iteración?

    Sí. Un objeto declarado con as const se puede iterar con Object.values() o métodos similares y además puedes extraer los tipos literales automáticamente con typeof.

    ¿Cómo detecto enums en un repo grande?

    Haz una búsqueda por la palabra clave enum (por ejemplo con grep) y prioriza los usos en la UI y la surface de la API.

    ¿Cuándo debo mantener un enum existente?

    Cuando hay integración con código legacy que depende de enums, cuando los valores numéricos son parte de un contrato público (SDKs/protocolos) o para interoperabilidad con librerías externas que exigen enums.

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