Category: Blog

Your blog category

  • Patrones de Diseño en React

    Patrones de Diseño en React

    Patrones de Diseño en React: Cómo escribir componentes limpios y reutilizables

    Tiempo estimado de lectura: 4 min

    • Ideas clave:
    • Extrae lógica de datos a Custom Hooks y mantén componentes como responsables solo de UI.
    • Usa composición y useMemo para optimizar y evitar God Components.
    • Evita prop‑drilling; adopta Context + custom hooks cuando el estado cruza >3 niveles.
    • La IA puede acelerar producción de código, no la arquitectura: automatiza políticas en CI.

    Introducción

    Patrones de Diseño en React: Cómo escribir componentes limpios y reutilizables — esto es lo que necesitas dominar ahora que la IA puede generar código por ti. En las primeras líneas: Patrones de Diseño en React: Cómo escribir componentes limpios y reutilizables no es teoría; es la defensa contra la deuda técnica que las herramientas generativas amplifican.

    Aquí tienes una guía práctica, con ejemplos reales de “spaghetti code” vs “clean code”, y las reglas claras para aplicar composición y Custom Hooks sin inventar moda.

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

    Qué es: Patrones y prácticas para organizar lógica y presentación en React.

    Cuándo usarlo: Cuando el componente mezcla fetch, transformación y UI.

    Por qué importa: Reduce deuda técnica y mejora testabilidad y reutilización.

    Cómo funciona: Extrae fetch/efectos a hooks, componentes puros para UI, y usa composición/context según necesidad.

    Cuando usar Patrones de Diseño en React: composición y Custom Hooks

    React promueve la composición y la reutilización, pero el mal uso de useState y useEffect conduce a God Components. La documentación oficial sobre reusar lógica con hooks es una referencia básica React Docs — Custom Hooks. Usa esa base y añade criterio: separa responsabilidades, encapsula efectos y haz componentes que describan UI, no procesos.

    El problema en 3 líneas

    • Un componente que hace fetch, filtra datos, maneja errores y renderiza una UI compleja es un antipatrón.
    • Resultado: difícil de testear, reutilizar y mantener.
    • La IA amplifica esto porque produce más código, no mejor arquitectura.

    Spaghetti Code vs Clean Code: ejemplo práctico

    A continuación, un ejemplo condensado para comparar y no marearte.

    Spaghetti (anti-patrón):

    // ❌ UserList.jsx
    import { useState, useEffect } from 'react';
    
    export default function UserList() {
      const [users, setUsers] = useState([]);
      const [loading, setLoading] = useState(true);
      const [search, setSearch] = useState('');
    
      useEffect(() => {
        fetch('/api/users')
          .then(r => r.json())
          .then(d => setUsers(d))
          .finally(() => setLoading(false));
      }, []);
    
      const filtered = users.filter(u => u.name.toLowerCase().includes(search.toLowerCase()));
    
      if (loading) return <p>Loading...</p>;
    
      return (
        <div>
          <input value={search} onChange={e => setSearch(e.target.value)} />
          <ul>{filtered.map(u => <li key={u.id}>{u.name} — {u.email}</li>)}</ul>
        </div>
      );
    }
    

    Clean (composición + hook):

    // hooks/useUsers.js
    import { useState, useEffect } from 'react';
    export function useUsers() {
      const [users, setUsers] = useState([]);
      const [loading, setLoading] = useState(true);
      useEffect(() => {
        fetch('/api/users').then(r => r.json()).then(setUsers).finally(() => setLoading(false));
      }, []);
      return { users, loading };
    }
    
    // components/UserCard.jsx
    export function UserCard({ user }) {
      return <div className="card">{user.name}<br/><small>{user.email}</small></div>;
    }
    
    // pages/UserDirectory.jsx
    import { useState, useMemo } from 'react';
    import { useUsers } from '@/hooks/useUsers';
    import { UserCard } from '@/components/UserCard';
    import { SearchInput } from '@/components/SearchInput';
    
    export default function UserDirectory() {
      const { users, loading } = useUsers();
      const [q, setQ] = useState('');
      const visible = useMemo(() => users.filter(u => u.name.toLowerCase().includes(q.toLowerCase())), [users, q]);
      if (loading) return <p>Loading...</p>;
      return (
        <section>
          <SearchInput value={q} onChange={setQ} />
          <div>{visible.map(u => <UserCard key={u.id} user={u} />)}</div>
        </section>
      );
    }
    

    ¿Qué cambió? SRP (Single Responsibility Principle). Hooks para datos, componentes para presentación, useMemo para evitar cálculos innecesarios.

    Reglas claras para escribir componentes reutilizables

    1. Extrae efectos y fetches a Custom Hooks. Si ves más de un useEffect, considera agruparlos. (Referencia: React Docs — Custom Hooks: React Docs — Custom Hooks)
    2. Mantén componentes visuales puros. Un UserCard no debería conocer fetch ni routing.
    3. Usa composición en lugar de herencia. Componer es construir UIs como funciones matemáticas: predecible y testeable. (ver: render-and-commit)
    4. Evita prop-drilling: si pasas props más de 3 niveles, introduce Context + custom hook para leer/escribir estado (passing-data-deeply-with-context).
    5. Encapsula validaciones y transformaciones en funciones puras o selectors fuera del render.
    6. Escribe tests para hooks (React Testing Library + msw para mocks). Hooks testeables = menos regresiones.

    Señales de que debes refactorizar ahora

    • Componentes de más de 200 líneas o con 3+ responsabilidades.
    • Repetición de fetch o lógica de transformación en varios componentes.
    • Tests frágiles donde montar el componente exige mockear red completa.
    • Cambios frecuentes en UI que requieren tocar la lógica de datos.

    Clean Code y la IA: cómo colaborar sin perder control

    • Dale a la IA unidades pequeñas: un hook, un componente, un test. Prompts que piden “refactoriza este hook para manejar errores y retries” funcionan mejor que “arregla todo el archivo”.
    • Acepta la generación, pero revisa arquitectura: tipos, boundaries, efectos secundarios.
    • Automate linters y reglas de arquitectura en CI (ESLint, TypeScript strict, tests de integración). La IA puede cumplir reglas; no improvises.

    Conclusión práctica

    Patrones de Diseño en React: Cómo escribir componentes limpios y reutilizables no es un lujo: es la medida que decide si tu proyecto sobrevive a la velocidad que impone la IA. Extrae lógica a hooks, compón componentes y escribe contratos claros entre capas. Si lo haces, cada línea generada por una IA será una mejora, no una bomba de tiempo.

    Sigue la documentación oficial y conviértelo en hábito: React Docs — Custom Hooks — esto no termina aquí; la próxima capa es cómo testear y versionar hooks críticos en equipos grandes. Te lo dejo para el siguiente artículo.

    Dominicode Labs

    Si buscas aplicar estas prácticas en flujos de trabajo o agentes que generan código, considera explorar recursos y experimentos en Dominicode Labs. Es una continuación lógica para llevar patrones y automatización a pipelines de desarrollo.

    FAQ

    ¿Qué es un Custom Hook y cuándo debo crear uno?Un Custom Hook es una función que reutiliza lógica relacionada con estado o efectos en React. Crea uno cuando detectes la misma lógica de estado/efecto en más de un componente o cuando quieras encapsular fetches, retries o manejo de errores para pruebas aisladas.

    ¿Cómo evitar prop‑drilling sin complicar la arquitectura?Introduce Context con un custom hook consumidor cuando pases props más de tres niveles; eso mantiene la API clara y evita acoplamientos rígidos entre componentes intermedios.

    ¿Cuándo es razonable usar useMemo?Usa useMemo para evitar recomputaciones costosas cuando el cálculo depende de entradas estables (arrays, listas) y cuando el coste de la memoización es menor que el cálculo en renders frecuentes.

    ¿Qué señales indican que un componente es un God Component?Más de tres responsabilidades, >200 líneas, mezcla de fetchs, transformaciones y renderizado complejo, y tests frágiles que requieren mocks extensos son señales claras.

    ¿Cómo testeo hooks que hacen fetch?Utiliza React Testing Library junto a msw para mockear solicitudes HTTP y pruebas aisladas del hook sin montar la UI completa. Eso reduce fricción y mejora fiabilidad.

    ¿La IA puede reemplazar la revisión arquitectónica?No. La IA puede generar código rápido, pero no garantiza decisiones arquitectónicas. Automatiza reglas en CI y revisa boundaries, tipos y efectos secundarios antes de aceptar cambios significativos.

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

  • Guía para la Puesta en Producción de Aplicaciones Angular 2026

    Guía para la Puesta en Producción de Aplicaciones Angular 2026

    Arquitectura para la Puesta en Producción de Aplicaciones Angular: Estándares 2026

    Tiempo estimado de lectura: 4 min

    • Enfoque: SemVer automatizado, builds reproducibles y despliegues inmutables.
    • Rendimiento: minimizar bundle inicial (budgets, lazy loading, zoneless) y optimizar assets (Brotli/AVIF).
    • Infraestructura y despliegue: Docker multi-stage, non-root, Blue/Green o Canary, observabilidad completa.
    • Calidad: pipeline con lint→tests→build→e2e→deploy y gating para visual, performance y accesibilidad.

    Introducción

    La Arquitectura para la Puesta en Producción de Aplicaciones Angular: Estándares 2026 define cómo pasar de “funciona en mi máquina” a un sistema observable, seguro y repetible. Esta guía asume Angular 19+ (Zoneless, Signals) y está orientada a equipos que necesitan SLAs reales, trazabilidad de releases y despliegues reproducibles.

    Resumen rápido (lectores con prisa)

    SemVer automatizado + Conventional Commits para releases fiables. Builds rápidos con Esbuild/Vite y Angular CLI en producción; enforce budgets. Contenedores multi-stage, runtime non-root. CI/CD con jobs que actúan como contrato de calidad y gates de performance/visual/accessibility.

    Estrategias de Versionado y Gestión del Cambio

    Mensajes estandarizados

    Adopta SemVer automatizado y Conventional Commits para producir releases confiables. Mensajes estandarizados: feat:, fix:, perf:. Herramienta recomendada: semantic-release.

    Flujo

    Flujo: Trunk-Based Development. Integraciones frecuentes a main. Pull Requests pequeños y verificados.

    Artefactos inmutables

    Cada despliegue corresponde a un Git tag firmado (vX.Y.Z). Rollbacks = redeploy del tag anterior.

    Generación de CHANGELOG

    Generación automática de CHANGELOG desde commits para auditoría y compliance.

    Referencias: Conventional Commits

    Arquitectura de Construcción y Optimización del Bundle

    Build toolchain

    Objetivo: FCP / LCP bajos, bundle inicial pequeño, tiempo de build corto. Build toolchain: Esbuild/Vite (velocidad) y Angular CLI en modo production. Usa ng build --configuration production.

    Zoneless

    Zoneless: retirar zone.js cuando sea posible (mejor rendimiento y menos re-render).

    Lazy & defer

    Usar rutas lazy y @defer/suspense para componentes pesados.

    Budgets

    Configura budgets en angular.json para hacer fallar el build si el bundle es demasiado grande:

    "budgets": [{
      "type": "initial",
      "maximumWarning": "150kb",
      "maximumError": "200kb"
    }]

    Assets

    Generar Brotli y Gzip durante el build; optimizar imágenes a AVIF/WebP; subset fonts.

    Analítica de bundle

    Analítica de bundle: source-map-explorer o webpack-bundle-analyzer para identificar dependencias pesadas.

    Docs Angular build: angular.io/guide/build

    Estrategia de Contenedorización con Docker

    CSR (static)

    Usa multi-stage builds y ejecuta procesos como non-root. Para CSR: build → sirve con Nginx; imagen final basada en nginx:alpine.

    SSR

    SSR: builder → runtime con node:alpine ejecutando el server bundle.

    Ejemplo multi-stage para SSR

    FROM node:22-alpine AS builder
    WORKDIR /app
    COPY package*.json ./
    RUN npm ci
    COPY . .
    RUN npm run build:ssr
    
    FROM node:22-alpine AS runner
    WORKDIR /app
    COPY --from=builder /app/dist /app/dist
    USER node
    CMD ["node", "dist/server/main.js"]

    Seguridad

    No correr como root; minimizar capas; scan de imágenes (Trivy).

    Docker best practices: docs.docker.com

    Orquestación de CI/CD con GitHub Actions

    Pipeline clave

    Pipeline como contrato de calidad. Ejemplo de jobs clave:

    • Lint & format
    • Unit tests (Vitest/Jest)
    • Build + budgets
    • E2E en entorno efímero (Playwright)
    • Build image & push (GHCR/ECR)
    • Deploy (Terraform/Helm)

    Snippets y prácticas

    Cache de node_modules y de outputs de Esbuild. Concurrency groups para evitar deploys solapados.

    Docs GitHub Actions: docs.github.com/actions

    Estrategias de Despliegue e Infraestructura

    Decisión central: CSR (Edge) vs SSR (Container)

    CSR: S3 + CloudFront, Vercel o Cloudflare Pages. Ventaja: latencia global, coste bajo.

    SSR: Kubernetes / Cloud Run. Usa Horizontal Pod Autoscaler y readiness/liveness checks.

    Blue/Green o Canary

    Blue/Green o Canary deployments obligatorios para producción. Controla tráfico con ingress (Traefik/NGINX) o service mesh.

    Observabilidad

    OpenTelemetry → Prometheus (metrics) + Grafana; Sentry para errores.

    Estrategia de Pruebas y Calidad (QA)

    Unit tests

    Unit tests: cubrir lógica de servicios y signals (Vitest/Jest).

    E2E

    E2E: Playwright ejecutado contra entorno ephemeral por PR; flujos críticos: login, checkout, forms.

    Playwright: playwright.dev

    Visual regression & Performance

    Visual regression: Percy/Chromatic en cada PR. Performance gates: Lighthouse CI en cada PR; falla si Performance < 90. Accessibility: automatizar axe-core en CI.

    Checklist mínimo previo a producción

    • SemVer + semantic-release configurado.
    • Budgets en angular.json que fallan build.
    • Docker multi-stage y non-root runtime.
    • Pipeline GitHub Actions con lint→test→build→e2e→deploy.
    • Blue/Green o Canary configurado en infra.
    • Monitoreo (OpenTelemetry + Sentry) y alertas para p95 latency y error budget.
    • Visual & performance gating en PRs.

    Conclusión

    Poner Angular en producción en 2026 exige disciplina más que herramientas. Implementa SemVer, enforcea budgets, automatiza QA y despliega inmutabilidad. Si tu pipeline falla rápido y tus despliegues son revertibles, has ganado más resiliencia que con cualquier micro-optimización puntual.

    Para equipos interesados en automatización y workflows relacionados con despliegues y pipelines, una continuación lógica es explorar recursos y experimentos en Dominicode Labs.

    FAQ

    Respuesta:

    Usa Conventional Commits para estandarizar mensajes y una herramienta como semantic-release para generar versiones SemVer automáticamente a partir de los commits y publicar tags firmados.

    Respuesta:

    Zoneless implica eliminar zone.js para reducir re-renders y mejorar rendimiento. Quitar zone.js tiene sentido si tu app y librerías son compatibles con el modelo de change detection alternativo (por ejemplo Signals) introducido en Angular 19+.

    Respuesta:

    Define budgets en angular.json con maximumError para que el comando de build falle si el bundle excede el tamaño especificado (ejemplo incluido en la guía).

    Respuesta:

    CSR sirve contenido estático desde CDN/edge (S3 + CloudFront, Vercel, Cloudflare Pages) y es más barato y rápido geográficamente. SSR requiere contenedores/servicios (Kubernetes/Cloud Run) y permite renderizar en servidor para SEO y tiempo a primer render en casos complejos.

    Respuesta:

    Jobs mínimos: lint & format, unit tests, build con budgets, E2E en entorno efímero, build image & push, deploy (Terraform/Helm). Estos jobs actúan como contrato de calidad antes de cualquier despliegue.

    Respuesta:

    Ejecuta E2E en entornos efímeros por PR y ejecuta un subconjunto crítico de pruebas para feedback rápido; pruebas completas pueden correr en pipelines separados o en merge to main según políticas de SLA.

  • Cómo usar el patrón CQRS para mejorar escalabilidad y rendimiento

    Cómo usar el patrón CQRS para mejorar escalabilidad y rendimiento

    Patrón de diseño CQRS: cuándo usarlo y por qué no es para todos

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Separa escritura (Commands) y lectura (Queries) para optimizar modelos y almacenamiento.
    • Beneficia escalado de lecturas y vistas especializadas, pero introduce consistencia eventual y complejidad operativa.
    • Úsalo cuando el ratio lectura/escritura sea muy alto o el dominio requiera vistas múltiples o reglas complejas.

    ¿Quieres escalar lecturas sin romper transacciones? El patrón de diseño CQRS te puede salvar —o hundir— dependiendo de cómo lo uses. Aquí tienes la guía técnica, sin adornos, con ejemplos y criterios claros para decidir.

    En las primeras líneas: el patrón de diseño CQRS separa explícitamente escritura (Commands) y lectura (Queries). Esa separación permite optimizar cada lado con un modelo y almacenamiento distinto, pero introduce consistencia eventual y complejidad operativa.

    Resumen rápido (lectores con prisa)

    CQRS separa operaciones que modifican estado (Commands) de las que leen datos (Queries).

    Úsalo cuando necesites escalado de lecturas, vistas desnormalizadas o múltiples proyecciones por consumidor.

    Introduce consistencia eventual y mayor complejidad operativa (event bus, idempotencia, reintentos).

    Implementación típica: write model ACID + event bus + proyectores + read model optimizado.

    Patrón de diseño CQRS: ¿qué es y cómo funciona?

    CQRS (Command Query Responsibility Segregation) extiende el principio CQS de Bertrand Meyer a nivel arquitectónico. La idea es sencilla:

    • Commands = intenciones que cambian estado (ej. CrearPedido).
    • Queries = lecturas sin efectos secundarios (ej. ObtenerPedidosUsuario).

    Arquitectura típica

    • Lado de escritura: valida reglas de negocio, persiste en DB normalizada (ACID) y publica eventos.
    • Bus de eventos: Event bus recomendado (Kafka, RabbitMQ o SQS transportan los eventos).
    • Proyectores (projectors): consumen eventos y actualizan vistas desnormalizadas.
    • Lado de lectura: lee desde vistas optimizadas (Elasticsearch, MongoDB, Redis).

    Referencia conceptual: Referencia conceptual

    Ejemplo práctico (TypeScript, simplificado)

    Command side

    Command side:

    class CrearPedidoCommand { constructor(public usuarioId:string, public items:any[]){ } }
    
    class PedidoHandler {
      async execute(cmd: CrearPedidoCommand){
        const pedido = PedidoFactory.crear(cmd);
        await writeRepo.save(pedido); // BD transaccional
        eventBus.publish(new PedidoCreadoEvent(pedido.id, pedido.snapshot()));
      }
    }

    Query side (proyector)

    Query side (proyector):

    eventBus.subscribe('PedidoCreado', async (evt) => {
      await readRepo.upsert({ id: evt.pedidoId, usuarioId: evt.usuarioId, total: evt.total });
    });

    La UI consume readRepo: consultas rápidas, sin joins.

    Por qué usar CQRS: beneficios reales

    • Escalado independiente: replicas de lectura que soportan millones de consultas sin tocar la base transaccional.
    • Consultas ultrarrápidas: vistas pre-agg y desnormalizadas adaptadas a cada cliente.
    • Lógica de dominio limpia: los comandos alojan reglas complejas sin ensuciar las consultas.
    • Seguridad y límites: segmentas permisos entre escritura y lectura.

    Stack sugerido

    El coste: por qué no es para todos

    CQRS trae dos impuestos que muchos subestiman:

    1. Consistencia eventual. Tras un comando exitoso, la vista puede tardar en actualizarse. La UI y procesos deben tolerar ese lag (read-your-writes, polling, optimistic updates).
    2. Complejidad operativa. Monitoreo del bus, reintentos, idempotencia, versionado de eventos, proyecciones huérfanas. Debuggear fallos distribuidos se vuelve un arte oscuro.

    Señales de alarma: equipo pequeño, dominio CRUD simple, SLA que exige consistencia inmediata. En esos casos, CQRS es over-engineering.

    Cuándo aplicarlo (criterios claros)

    Usa CQRS solo si encajan al menos dos de estos casos:

    • Ratio lectura/escritura muy alto (p. ej. 100:1 o más).
    • Vistas múltiples y complejas que requieren pre-agrupación o formato distinto por consumidor.
    • Dominio con reglas de negocio complejas que complican las consultas si se mezclan.
    • Ya trabajas con arquitectura basada en eventos o Event Sourcing.

    Evítalo si:

    • Tu app es formularios + listados simples.
    • Tienes requisitos de consistencia inmediata estrictos.
    • No hay capacidad operativa para mantener infra distribuida.

    Migración práctica: cómo empezar sin detonarte

    1. Identifica el “hot path” de lecturas lentas (métricas p95/p99).
    2. Implementa un proyector que consuma eventos y mantenga una vista materializada mínima.
    3. Redirige esas consultas al read model gradualmente.
    4. Añade pruebas de integridad, idempotencia y observabilidad en el event bus.
    5. Solo entonces extiende el patrón a otros contextos.

    Herramientas útiles: Axon, EventStore, Kafka, y librerías CQRS como @nestjs/cqrs.

    Conclusión y siguiente paso

    El patrón de diseño CQRS te da independencia y rendimiento donde el modelo CRUD choca contra límites reales. Pero es cirugía mayor: pagas con latencia, operaciones y debugging distribuido. No lo adoptes por moda; adopta CQRS por necesidad demostrada.

    Empieza pequeño: crea una proyección, mide el lag, valida la experiencia de usuario. En el próximo artículo mostraré una migración paso a paso desde un endpoint CRUD hacia una vista materializada con Kafka y un proyector en Node.js.

    FAQ

    ¿Qué significa CQRS?

    CQRS es la segregación de responsabilidades entre Commands (operaciones que modifican estado) y Queries (operaciones de lectura sin efectos secundarios).

    ¿Cuándo es una buena idea implementarlo?

    Cuando tienes un ratio de lectura/escritura muy alto, múltiples vistas que requieren pre-agrupación o un dominio con reglas complejas que dificultan mezclar lectura y escritura en el mismo modelo.

    ¿Qué impacto tiene en la consistencia de datos?

    Introduce consistencia eventual: tras un comando exitoso las vistas pueden tardar en reflejar el cambio. La UI y procesos deben manejar ese lag explícitamente.

    ¿Qué componentes operativos debo considerar?

    Monitoreo del bus de eventos, manejo de reintentos, idempotencia, versionado de eventos, y la salud de las proyecciones son claves para operar CQRS en producción.

    ¿Puedo aplicar CQRS solo a partes de mi sistema?

    Sí. Es recomendable empezar por un “hot path” de lecturas lentas y migrar gradualmente a una vista materializada antes de ampliar el patrón.

    ¿Qué herramientas ayudan con CQRS y Event Sourcing?

    Herramientas mencionadas incluyen Kafka, Axon, EventStore y librerías como @nestjs/cqrs, así como bases de datos optimizadas para lectura como Elasticsearch o Redis.

  • Scripts en Python para optimizar tu productividad semanal

    Scripts en Python para optimizar tu productividad semanal

    Scripts en Python que te ahorran horas cada semana (Casos reales)

    Tiempo estimado de lectura: 4 min

    • Automatiza lo repetitivo: pequeñas tareas con scripts te devuelven horas cada semana.
    • Enfoque pragmático: ejemplos listos para copiar, adaptar y ejecutar hoy mismo.
    • Integración: combina Python con n8n o FastAPI para orquestación sin perder flexibilidad.
    • Producción segura: logging, retries, timeouts, tests y entornos reproducibles como pasos mínimos.

    Introducción

    Si buscas “Scripts en Python que te ahorran horas cada semana (casos reales)”, estás en el lugar correcto. En las primeras líneas: este artículo muestra scripts prácticos —limpieza de datos, renombrado masivo, scraping controlado y generación de informes— que puedes copiar, adaptar y ejecutar hoy mismo para recuperar horas semanales.

    No es teoría: son patrones productivos con código mínimo, buenas prácticas y enlaces a la documentación oficial para escalar a producción.

    Resumen rápido (lectores con prisa)

    Qué es: Colección de scripts Python prácticos para tareas repetitivas.

    Cuándo usarlo: Cuando procesos manuales consumen tiempo semanalmente.

    Por qué importa: Scripts reproducibles reducen errores humanos y liberan tiempo.

    Cómo funciona: Pequeños scripts con dependencias claras, logging y orquestación via n8n o FastAPI.

    Scripts en Python que te ahorran horas cada semana: cuatro casos reales

    1) Limpieza y normalización de datos (Pandas)

    Problema: CSVs semanales con fechas mixtas, espacios, duplicados. Resultado: 30 minutos manuales por archivo.

    Solución: usar pandas para normalizar columnas, coercionar fechas y eliminar duplicados. Dependencias: pandas.

    import pandas as pd
    
    def clean_sales(input_path, output_path):
        df = pd.read_csv(input_path)
        df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_')
        if 'fecha_venta' in df:
            df['fecha_venta'] = pd.to_datetime(df['fecha_venta'], errors='coerce').dt.date
        df.drop_duplicates(subset=['email'], inplace=True)
        df.dropna(subset=['monto', 'email'], inplace=True)
        df.to_csv(output_path, index=False)
    

    Por qué funciona: debug determinista, reproducible y scriptable desde cron o n8n.

    2) Renombrado y organización masiva de archivos (Pathlib)

    Problema: carpeta Downloads repleta de facturas, imágenes y PDFs sin orden.

    Solución: Pathlib + shutil para mover por fecha de modificación o por patrón. Pathlib docs.

    from pathlib import Path
    import shutil
    from datetime import datetime
    
    src = Path.home() / "Downloads"
    dst = Path.home() / "Documents/Invoices"
    
    for f in src.glob("*.pdf"):
        ts = datetime.fromtimestamp(f.stat().st_mtime)
        target = dst / str(ts.year) / f"{ts.month:02d}"
        target.mkdir(parents=True, exist_ok=True)
        shutil.move(str(f), str(target / f.name))
    

    Impacto: cientos de archivos organizados en segundos, sin errores humanos.

    3) Scraping controlado y monitoreo (Requests + BeautifulSoup)

    Problema: comprobar precios o disponibilidad manualmente cada mañana.

    Solución: Requests + BeautifulSoup para sitios estáticos. Respecta robots.txt y usa headers.

    import requests
    from bs4 import BeautifulSoup
    
    url = "https://ejemplo.com/producto"
    headers = {"User-Agent": "bot@miempresa.com"}
    r = requests.get(url, headers=headers, timeout=10)
    r.raise_for_status()
    soup = BeautifulSoup(r.text, "html.parser")
    price = soup.select_one(".price").get_text(strip=True)

    Cuando la web es dinámica, usa Playwright.

    Buenas prácticas: backoff en reintentos, pausas entre requests y registro estructurado.

    4) Generación automática de informes (Jinja2 → Email/HTML/PDF)

    Problema: compilar métricas de distintas fuentes y formatearlas manualmente cada semana.

    Solución: Jinja2 para plantilla HTML + render + envío por SMTP o vía n8n. Jinja2 docs.

    from jinja2 import Environment, FileSystemLoader
    env = Environment(loader=FileSystemLoader("templates"))
    tpl = env.get_template("weekly.html")
    html = tpl.render(metrics=metrics_dict)

    Patrón:

    • Recoger datos (DB / API)
    • Renderizar plantilla Jinja2
    • Guardar HTML o convertir a PDF (weasyprint / wkhtmltopdf)
    • Enviar por correo o subir a un canal Slack

    Automatización: conviértelo en endpoint FastAPI o ejecútalo desde n8n.

    Cómo convertir un script útil en una automatización confiable

    • Logging estructurado: usa logging/structlog, evita prints.
    • Configuración externa: variables de entorno (.env), no claves hardcodeadas.
    • Retries y backoff: tenacity o patrones de retry para redes inestables.
    • Timeouts y cancelación: define límites en requests/Playwright.
    • Tests y tipo: type hints + pytest para evitar regresiones.
    • Entornos reproducibles: poetry/venv y Dockerfile.
    • Observabilidad: métricas de ejecución y alertas (errores, latencias).

    Integración práctica con n8n y workflow orquestado

    Patrón recomendado: n8n orquesta (triggering, branching, notificaciones) → tu servicio Python ejecuta la tarea pesada → devuelve JSON → n8n sigue con distribución y persistencia. Esto mantiene la UX del no-code y la potencia de Python para lo que importa.

    n8n Execute Command o un endpoint HTTP (FastAPI) son las formas más sencillas de integrar.

    Conclusión: dónde empezar hoy

    Elige la tarea que te roba más tiempo y conviértela en script. Empieza por:

    • limpieza de datos si trabajas con CSVs;
    • organización de archivos si pierdes tiempo buscando;
    • scraping si haces comprobaciones manuales diarias;
    • reportes si dedicas horas a consolidar métricas.

    Implementa logging, pruebas mínimas y despliega como función serverless o servicio ligero. Un script que hoy te ahorre 30 minutos semanales será un activo que multiplica su valor con el tiempo.

    Recursos

    Si trabajas con automatización, n8n o workflows y quieres continuar experimentando con patrones y pruebas, visita Dominicode Labs para recursos adicionales y experimentos prácticos. Es una continuación lógica para quienes combinan no-code y código en producción.

    FAQ

    ¿Qué dependencias necesito?

    Depende de la tarea: para limpieza de datos, pandas. Para scraping estático, Requests y BeautifulSoup. Para render dinámico, Playwright. Para plantillas, Jinja2.

     

    ¿Cómo integrarlo con n8n?

    Usa n8n como orquestador: trigger → ejecutar comando → recibir JSON. Puedes usar Execute Command o un endpoint HTTP (FastAPI) que reciba peticiones desde n8n y devuelva resultados procesables.

     

    ¿Qué pruebas debo escribir?

    Tests unitarios básicos con pytest y pruebas de integración para las interacciones de red o filesystem. Añade type hints y pruebas que cubran casos esperados y errores comunes (fechas inválidas, campos faltantes, timeouts).

     

    ¿Cómo manejar secretos y configuración?

    Usa variables de entorno, managers de secretos o servicios de vault. Nunca hardcodees claves en el repositorio; utiliza .env para desarrollo y soluciones seguras en producción.

     

    ¿Qué hacer si el scraping requiere JS?

    Pasa a herramientas headless como Playwright o Puppeteer. Define timeouts, espera selectores y respetar robots.txt y límites de requests.

     

    ¿Cómo convertir HTML a PDF?

    Renderiza tu plantilla Jinja2 a HTML y usa weasyprint o wkhtmltopdf para la conversión. Alternativamente genera HTML y envíalo por correo si PDF no es estrictamente necesario.

  • Aprende FastAPI: Del Frontend al Backend sin Complicaciones

    Aprende FastAPI: Del Frontend al Backend sin Complicaciones

    FastAPI explicado para devs que vienen del frontend

    Tiempo estimado de lectura: 4 min

    • Pydantic trae tipado y validación automática al backend (como Zod/TypeScript).
    • Depends ofrece inyección de dependencias limpia y reutilizable (como hooks/contexts).
    • /docs genera documentación y clientes a partir de OpenAPI automáticamente.
    • Estructura por feature (routers, servicios, schemas) evita sobre‑arquitectura temprana.
    • Async vs sync: escoge coherentemente según las librerías de I/O que uses.

    Introducción

    FastAPI explicado para devs que vienen del frontend: si trabajas con React, Next.js o Angular, lo vas a entender rápido. En pocas líneas: FastAPI trae al backend la ergonomía que ya conoces del frontend —tipado, validación automática y feedback inmediato— pero con la potencia del ecosistema Python para datos y automatización.

    Esta guía va al grano: qué partes importan de verdad, cómo estructurar proyectos reales y cómo evitar la sobre‑arquitectura que paraliza más que ayuda.

    Resumen rápido (lectores con prisa)

    FastAPI es un framework web Python que ofrece validación automática con Pydantic, inyección de dependencias con Depends y documentación OpenAPI automática. Úsalo cuando quieras productividad, tipado y APIs mantenibles sin complejidad innecesaria.

    FastAPI explicado para devs que vienen del frontend: los conceptos que importan

    Pydantic: tu Zod/TypeScript en el backend

    Pydantic define esquemas (models) que validan entrada y salida, generan JSON y alimentan la documentación OpenAPI automática. Piensa en él como Zod del servidor.

    from pydantic import BaseModel
    
    class UserCreate(BaseModel):
        username: str
        email: str
        is_admin: bool = False
    

    Si el cliente envía un string donde va un número, FastAPI responde 422. Más seguridad y menos if inútiles. Docs: Docs

    Depends: inyección limpia (como hooks)

    Depends es el equivalente a usar un hook o un context provider. Te permite inyectar autenticación, sesiones DB o configuración sin repetir código.

    from fastapi import Depends
    
    async def get_current_user(token: str):
        # decodifica JWT, consulta DB...
        return user
    
    @app.get("/me")
    async def me(user=Depends(get_current_user)):
        return {"username": user.username}
    

    Esto hace tus rutas pequeñas, testables y legibles.

    Docs automáticas: /docs es tu Postman actualizado

    FastAPI genera Swagger UI y ReDoc desde tus modelos Pydantic. Para frontend, eso significa menos sincronización manual y clientes generados a partir del esquema OpenAPI. FastAPI docs: FastAPI docs

    Cómo estructurar un proyecto (sin volverte loco)

    Estructura recomendada

    La regla: agrupa por dominio/feature, no por tipo de archivo. Es más fácil de escalar y de entender cuando el proyecto crece.

    /project
    ├─ main.py           # instancia FastAPI y monta routers
    ├─ core/
    │  ├─ config.py      # Pydantic Settings
    │  └─ db.py          # sesión/engine DB
    └─ modules/
       └─ users/
          ├─ router.py   # endpoints
          ├─ schemas.py  # Pydantic models
          ├─ service.py  # lógica de negocio
          └─ models.py   # tablas (SQLModel/SQLAlchemy)
    

    router.py solo maneja HTTP.
    service.py contiene lógica pura (sin dependencias HTTP).
    schemas.py es tu contrato con el frontend.

    Usa SQLModel si quieres combinar Pydantic + SQLAlchemy con menor fricción: SQLModel

    Async vs sync: cuándo usar async def

    Eres de frontend, ya conoces async/await. En Python:

    • Usa async def si tus librerías son asíncronas (httpx async, asyncpg).
    • Usa def si dependes de librerías bloqueantes (requests, muchos ORMs syncronous).

    FastAPI ejecuta funciones sync en un threadpool, pero mezclar async con llamadas bloqueantes puede congelar la app. Elige coherentemente la capa de I/O. httpx (async) es una buena alternativa a requests.

    Cómo no sobre‑arquitectar: reglas prácticas

    1. No crees capas de abstracción antes de necesitarlas. Si no vas a cambiar de DB a corto plazo, no inventes un IRepository.
    2. Evita micro‑paquetes para cada función. Agrupa por feature y refactoriza cuando el módulo crezca.
    3. No conviertas cada operación en un task queue desde el día uno. Añade background jobs (Celery, RQ) cuando el throughput lo exija.
    4. Usa Pydantic Settings para configuración validada y .env en desarrollo: Pydantic Settings
    5. Empieza con tests simples en service.py: funciones puras son triviales de testear con pytest.

    Deploy mínimo reproducible

    Dockerfile simple:

    FROM python:3.11-slim
    WORKDIR /app
    COPY pyproject.toml poetry.lock ./
    RUN pip install poetry && poetry install --no-root --only main
    COPY . .
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
    

    Usa Uvicorn para prod. Plataformas como Railway, Render o Fly.io son opciones rápidas con SSL automático.

    Pequeño checklist antes de merge

    • Modelos Pydantic definidos para input/output.
    • Dependencias reutilizables con Depends.
    • Rutas cortas: lógica en service.py, no en router.
    • Logs estructurados y errores manejados correctamente.
    • Tests para la lógica (no solo integración).

    Cierre

    FastAPI te permite pasar de frontend a backend sin perder la mentalidad de DX y tipado que ya dominas. Aprende Pydantic, usa Depends con juicio y estructura por feature. No abuses de patrones hasta que el problema los justifique. En 10 minutos tendrás un endpoint funcional; en unas pocas iteraciones, una API mantenible y profesional.

    Si te interesa explorar integraciones, automatizaciones y flujos avanzados relacionados con APIs y agentes, visita Dominicode Labs para recursos y experimentos prácticos.

    FAQ

    ¿Por qué usar Pydantic en FastAPI?

    Pydantic valida entrada y salida, genera JSON y alimenta la documentación OpenAPI. Reduce checks manuales y errores de tipo, mejorando la seguridad y la ergonomía del desarrollo.

    ¿Cuándo debo usar Depends?

    Usa Depends para autenticación, sesiones DB, configuración u otras dependencias reutilizables. Mantiene las rutas limpias y facilita testing.

    ¿Qué diferencia hay entre async y sync en FastAPI?

    Usa async def si tus librerías de I/O son asíncronas; usa def si son bloqueantes. Mezclar ambos sin cuidado puede bloquear la app.

    ¿Debo empezar con SQLModel o SQLAlchemy?

    Si quieres integrar Pydantic y ORM con menor fricción, SQLModel es una opción práctica. Si necesitas control avanzado, SQLAlchemy puro puede ser más adecuado.

    ¿Cómo evito sobre‑arquitectar mi backend?

    No crees abstracciones ni micro‑paquetes antes de necesitarlos. Agrupa por feature y añade capas solo cuando el problema lo justifique.

    ¿Qué pongo en los tests iniciales?

    Empieza por tests unitarios en service.py (funciones puras) y algunos tests de integración básicos para rutas críticas.

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