Category: JavaScript

  • Comparativa de Context API, Zustand y Redux Toolkit para Estado Global

    Comparativa de Context API, Zustand y Redux Toolkit para Estado Global

    Manejo de Estado Global: ¿Context API, Zustand o Redux Toolkit?

    Tiempo estimado de lectura: 4 min

    • Ideas clave:
    • Context API: nativo y sin dependencias; úsalo para valores estáticos o de baja frecuencia.
    • Zustand: minimalista, selectores por suscripción y casi cero boilerplate — opción pragmática para UI global dinámica.
    • Redux Toolkit: disciplina y herramientas para apps enterprise; usa RTK Query para cacheo y sincronización.
    • Separa server state y UI state: usa TanStack Query o SWR para datos remotos.

    Manejo de Estado Global: ¿Context API, Zustand o Redux Toolkit? Si estás diseñando una app React hoy, esta elección define tu ritmo de desarrollo —y tu deuda técnica— durante meses. Aquí tienes una comparativa práctica y juicios arquitectónicos claros para proyectos pequeños y medianos.

    Resumen rápido (lectores con prisa)

    Context API: inyección nativa para datos estáticos o de baja frecuencia. Zustand: store minimalista con suscripción selectiva, ideal para UI dinámica. Redux Toolkit: disciplina y herramientas para apps enterprise y flujos complejos.

    Resumen ejecutivo (lectura rápida)

    Context API: nativo, sin dependencias. Úsalo para valores estáticos o de baja frecuencia (tema, locale, auth simple). No lo conviertas en un store de alto tráfico.

    Zustand: minimalista, selectores por suscripción, casi cero boilerplate. Ideal para la mayoría de proyectos pequeños/medianos (UI global: modales, carritos, filtros).

    Redux Toolkit: robusto y estandarizado. Úsalo en apps enterprise con equipos grandes, necesidades de auditoría, middlewares complejos o cuando RTK Query sea necesario.

    Fuentes de referencia

    Context API: cuándo y cómo usarlo

    Context es inyección de dependencias, no un gestor optimizado de estado. Su ventaja: cero dependencias y simpleza. Su problema: cuando el valor cambia, todos los consumidores se re-renderizan a menos que controles cuidadosamente la granularidad.

    Ejemplo mínimo (tema)

    const ThemeContext = createContext({ theme: 'light' });
    function ThemeProvider({ children }) {
      const [theme, setTheme] = useState('light');
      return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
    }

    Cuándo aplicarlo: datos globales que cambian raramente (tema, idioma, configuración). Evítalo para datos con muchas actualizaciones por segundo.

    Zustand: por qué es la opción pragmática

    Zustand ofrece un store hook-first con suscripción selectiva: el componente solo se re-renderiza si la porción de estado que ha seleccionado cambia. Casi sin boilerplate y fácil de testear.

    Ejemplo

    import { create } from 'zustand';
    const useStore = create(set => ({
      toasts: [],
      push: (t) => set(s => ({ toasts: [...s.toasts, t] })),
    }));
    const count = useStore(s => s.toasts.length); // suscripción granular

    Cuándo usarlo: estado UI global dinámico (modales, notificaciones, carrito, filtros). Para proyectos de 1–10 devs suele ser la mejor inversión: rápido, claro y con buen rendimiento.

    Redux Toolkit: cuándo merece la pena

    RTK impone disciplina: slices, actions, reducers y middlewares. RTK Query añade cacheo y sincronización de datos del servidor. Eso lo hace imprescindible en aplicaciones con flujos de datos complejos y equipos grandes.

    Cuándo usarlo:

    • Equipos grandes y rotativos que necesitan convenciones estrictas.
    • Depuración avanzada (Redux DevTools, time-travel).
    • Requisitos de auditoría o middlewares personalizados.

    Si tu app no requiere esas garantías, RTK es sobreingeniería.

    Regla de oro: separa estado del servidor y estado de UI

    No metas datos de API directamente en tu store global. Para sincronización y caché de datos remotos, usa herramientas especializadas: TanStack Query o SWR. El estado del servidor y el estado de la UI son problemas distintos; mantén sus responsabilidades separadas.

    Árbol de decisión práctico

    1. ¿Los datos vienen de una API y necesitan cacheo? → TanStack Query / RTK Query.
    2. ¿Estado global que cambia poco (theme/auth)? → Context API.
    3. ¿Estado UI interactivo y frecuente (modales/cart/filtros)? → Zustand.
    4. ¿Aplicación enterprise, equipo grande o requisitos de auditoría? → Redux Toolkit.

    Consideraciones prácticas de producción

    • Persistencia: Zustand tiene middleware para persistir en localStorage; Context y RTK también pueden integrarlo.
    • Testing: Zustand y hooks son fáciles de testear unitariamente; Context requiere providers en tests.
    • Next.js / React Server Components: el estado global orientado a UI sigue en cliente; evita mezclar server state y client state en un único store.
    • Migración: empezar con Zustand + TanStack Query es una estrategia segura; si creces mucho, migrar a RTK es posible pero requiere esfuerzo.

    Conclusión

    Para proyectos pequeños y medianos, el balance es claro: Context para configuración estática, Zustand como store pragmático para UI dinámica y TanStack Query para data fetching. Reserva Redux Toolkit para cuando la complejidad real justifique su coste cognitivo. En Dominicode preferimos herramientas que desaparecen y permiten al equipo avanzar; esa regla suele señalar a Zustand como punto de partida.

    FAQ

    Respuesta: Usa Context API para datos globales que cambian raramente (tema, locale, auth simple). Si el estado es UI dinámico y frecuente, prefiere Zustand; si necesitas convenciones estrictas y herramientas de auditoría, considera RTK.

    Respuesta: Zustand puede contener datos remotos pero no ofrece cacheo ni sincronización avanzada por defecto. Para cache y sincronización de server state es mejor usar TanStack Query o RTK Query.

    Respuesta: RTK Query está integrado con la convención de Redux y facilita middlewares, devtools y normalización dentro del ecosistema RTK. TanStack Query es independiente y muy flexible; la elección depende de si ya usas Redux y requieres integración con su flujo.

    Respuesta: Controla la granularidad del contexto: divide Contexts por responsabilidad, memorización de valores y evita pasar objetos nuevos en cada render. Para carga alta de actualizaciones, Context no es la mejor opción.

    Respuesta: Es factible pero no trivial. Migrar de Zustand a RTK requiere introducir slices, actions y posiblemente adaptar middlewares y patrones de acceso; planifica tiempo para reescribir tests y ajustar la arquitectura.

    Respuesta: Depende: la persistencia en localStorage es útil para preferencia de usuario o carritos, pero ten en cuenta seguridad y consistencia. Zustand y RTK soportan middleware de persistencia; valora qué datos deben persistir.

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

    Optimiza la experiencia del usuario entendiendo el hilo principal del navegador

    Comprender el hilo principal del navegador

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

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

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

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

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

    Proceso de renderizado y event loop

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

    Responsabilidades críticas

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

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

    Por qué no paralelizar libremente el DOM

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

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

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

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

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

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

    Estrategias prácticas para no bloquearlo

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

    1) Web Workers — verdadero multihilo

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

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

    Limitación: no pueden acceder al DOM.

    2) OffscreenCanvas para gráficos

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

    3) Chunking y yielding

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

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

    4) Usar async/await correctamente

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

    5) WebAssembly / SharedArrayBuffer (cuando aplique)

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

    Decisiones de arquitectura: reglas prácticas

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

    Conclusión

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

    Fuentes y lectura adicional

    FAQ

    ¿Qué es el hilo principal del navegador?

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

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

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

    ¿Cuándo debo usar un Web Worker?

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

    ¿Puedo manipular el DOM desde un Worker?

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

    ¿Qué herramientas ayudan a medir Long Tasks?

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

    ¿Qué es chunking y cuándo aplicarlo?

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

    Tiempo estimado de lectura: 4 min

    Ideas clave

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

    Tabla de contenidos

  • Aprende Python para desarrolladores de JavaScript en días

    Aprende Python para desarrolladores de JavaScript en días

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

    Tiempo estimado de lectura: 4 min

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

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

    Resumen rápido (lectores con prisa)

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

    Mental shift: ambiente y convenciones

    Entornos

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

    Estilo

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

    Asincronía

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

    Sintaxis mínima — lo que realmente usarás

    Mapeo directo, sin florituras:

    Funciones

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

    List comprehensions (olvida map+filter verboso)

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

    Dicts en lugar de objetos con acceso por corchetes

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

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

    Usa httpx para clientes HTTP asíncronos:

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

    Lo que SÍ debes aprender: herramientas que importan

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

    FastAPI (APIs modernas)

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

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

    Pydantic (validación y schemas)

    Pydantic es tu Zod/Joi en Python.

    Poetry (gestión de paquetes y lock)

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

    Ruff (lint/format rápido)

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

    Pytest (testing)

    Pytest ofrece fixtures sencillas y parametrización limpia.

    Lo que NO debes perder tiempo aprendiendo ahora

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

    Integración práctica con tu stack actual

    n8n → FastAPI

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

    React/Next frontend → Python backend

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

    IA y RAG

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

    Ejemplo rápido de migración mental

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

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

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

    Criterio final

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

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

    Enlaces útiles

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

    FAQ

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

  • Cómo crear una API de autenticación en Express.js para reclutadores

    Cómo crear una API de autenticación en Express.js para reclutadores

    Cree una API de inicio y cierre de sesión con Express.js (Node.js)

    Tiempo estimado de lectura: 3 min

    • Ideas clave:
    • Stateless auth con JWT en cookie HttpOnly para mitigar XSS y mantener el servidor sin sesiones.
    • Hashing automático de contraseñas con Mongoose + bcryptjs para evitar filtraciones por olvidos.
    • Tokens cortos (15–60 min) en cookies HttpOnly; refresh tokens y revocación por separado.
    • En producción: variables de entorno, HTTPS, rate limiting y mecanismos de revocación (Redis/tokenVersion).

    ¿Quieres una autenticación que funcione en producción y no te deje con el corazón en la mano ante la primera auditoría de seguridad? Cree una API de inicio y cierre de sesión con Express.js (Node.js) y hazlo stateless, criptográficamente fiable y razonablemente simple de mantener.

    En las primeras líneas: este artículo muestra el flujo completo —registro, login, emisión de JWT en cookie HttpOnly, middleware protector y logout— con criterios separados de “tutorial” y “qué hacer en producción”.

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

    Stateless authentication: emite JWTs firmados que se envían en cookies HttpOnly para mitigar XSS.

    Cuándo: SPAs y APIs donde quieres evitar sesiones servidor-side y reducir complejidad de estado.

    Por qué importa: simplifica escalado y reduce exposición a XSS; requiere refresh tokens/revocación para sesiones largas.

    Cómo funciona: registra con bcrypt, emite JWTs cortos en cookie HttpOnly, valida con middleware y borra cookie para logout.

    Cree una API de inicio y cierre de sesión con Express.js (Node.js): arquitectura y dependencias

    Pila mínima recomendada

    Instalación

    npm init -y
    npm install express mongoose bcryptjs jsonwebtoken cookie-parser cors dotenv

    Concepto: stateless

    Concepto: stateless = el servidor no guarda sesiones. Emites un access token (JWT) firmado y lo envías en una cookie HttpOnly. El cliente no puede leerla vía JS, lo que mitiga XSS.

    Modelo de usuario y hashing (criterio práctico)

    Automatiza el hashing con Mongoose para evitar fugas por olvidos:

    // models/User.js
    const mongoose = require('mongoose');
    const bcrypt = require('bcryptjs');
    
    const userSchema = new mongoose.Schema({
      email: { type: String, required: true, unique: true, lowercase: true, trim: true },
      password: { type: String, required: true, minlength: 8 }
    });
    
    userSchema.pre('save', async function(next) {
      if (!this.isModified('password')) return next();
      this.password = await bcrypt.hash(this.password, 12); // cost 12
      next();
    });
    
    userSchema.methods.comparePassword = function(candidate) {
      return bcrypt.compare(candidate, this.password);
    };
    
    module.exports = mongoose.model('User', userSchema);

    ¿Por qué cost 12? Balance entre seguridad y CPU. Ajusta según tu infraestructura.

    Login: validar, firmar y enviar cookie

    Regla: JWT corto (ej. 15–60 min) en cookie HttpOnly; refresh tokens aparte.

    // controllers/auth.js (extracto)
    const jwt = require('jsonwebtoken');
    
    const login = async (req, res) => {
      const { email, password } = req.body;
      const user = await User.findOne({ email });
      if (!user || !(await user.comparePassword(password))) {
        return res.status(401).json({ error: 'Credenciales inválidas' });
      }
    
      const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '15m' });
    
      res.cookie('authToken', token, {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict',
        maxAge: 15 * 60 * 1000
      });
    
      res.json({ success: true });
    };

    No pongas datos sensibles en el payload. Claims mínimos: userId y algún scope si hace falta.

    Middleware protector de rutas

    Intercepta y valida la cookie antes de permitir el acceso:

    // middleware/auth.js
    const jwt = require('jsonwebtoken');
    
    module.exports = (req, res, next) => {
      const token = req.cookies.authToken;
      if (!token) return res.status(401).json({ error: 'No autorizado' });
    
      try {
        req.user = jwt.verify(token, process.env.JWT_SECRET);
        next();
      } catch (e) {
        res.status(401).json({ error: 'Token inválido o expirado' });
      }
    };

    Adjunta req.user.userId para consultas posteriores.

    Logout: simple y efectivo

    En una arquitectura basada en cookies HttpOnly, cerrar sesión es instructivo: borrar la cookie en el navegador.

    // controllers/auth.js
    const logout = (req, res) => {
      res.clearCookie('authToken', {
        httpOnly: true,
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict'
      });
      res.json({ success: true });
    };

    Si necesitas invalidación inmediata de tokens (por ejemplo, forzar logout de todos los dispositivos), añade una lista de revocación en Redis con claves expiradas o guarda un tokenVersion en el usuario y compáralo en el JWT.

    Comparativa: Cookies HttpOnly vs localStorage (resumen técnico)

    • Cookies HttpOnly: automáticas, resistentes a XSS, requieren SameSite y HTTPS.
    • localStorage: accesible por JS → vulnerable a XSS; menos recomendable para web.

    Para la mayoría de SPAs web, cookies HttpOnly + CSRF mitigations (SameSite/CSRF token cuando sea necesario) es la opción correcta.

    Consideraciones de producción (criterio senior)

    • Nunca expongas JWT_SECRET ni URIs en el repo; usa variables de entorno.
    • Usa HTTPS en todas partes; secure cookies dependen de ello.
    • Implementa rate limiting en /login (express-rate-limit).
    • Logging y alertas en intentos fallidos.
    • Considera refresh tokens almacenados en HttpOnly (o en un store) si necesitas sesiones largas. Documentación OWASP sobre autenticación: https://owasp.org.
    • Validación de entrada robusta (Joi o express-validator).

    Conclusión y siguiente paso

    Cree una API de inicio y cierre de sesión con Express.js (Node.js) usando JWT en cookies HttpOnly y bcrypt para contraseñas y habrás cubierto la base para una autenticación segura y escalable. Esto no es la cima: el siguiente paso es integrar refresh tokens seguros, rotación de tokens y estrategias de revocación (Redis/DB). Implementa lo básico bien y estarás listo para esas capas adicionales.

    FAQ

    Respuesta

    Cookies HttpOnly no son accesibles desde JavaScript, lo que reduce la superficie de ataque frente a XSS. localStorage es accesible por JS y por tanto vulnerable a XSS.

    Respuesta

    Usa JWTs cortos (ej. 15–60 minutos) para access tokens. Para sesiones largas, combina con refresh tokens seguros y rotación de tokens.

    Respuesta

    Almacena refresh tokens en cookies HttpOnly o en un store con expiración. Implementa rotación: emite un nuevo refresh token al usar uno válido y revoca el anterior.

    Respuesta

    Para invalidación inmediata, usa una lista de revocación en Redis con claves expiradas o guarda un tokenVersion en el usuario y compáralo contra el JWT.

    Respuesta

    Automatizar hashing evita errores humanos (olvidar hashear antes de guardar) y estandariza el coste. Mongoose pre(‘save’) es una forma práctica de hacerlo.

    Respuesta

    Variables de entorno seguras, HTTPS obligatorio, rate limiting en endpoints críticos, logging/alertas, validación de input y mecanismos de revocación para tokens son esenciales.

  • Cómo usar n8n como backend sin servidor de tu aplicación Next.js?

    Cómo usar n8n como backend sin servidor de tu aplicación Next.js?

     

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Usar n8n como motor de orquestación y computación para reducir lógica en API Routes de Next.js.
    • Patrón: Next.js → webhook n8n → procesamiento (IA, scraping, APIs) → persistencia (Supabase) → frontend en realtime.
    • Ideal para MVPs y pipelines IA/ETL; observar límites de latencia, escalabilidad y complejidad de workflows.

    Tabla de contenidos

    La automatización con IA y n8n te permite convertir los workflows visuales en el “backend” de una aplicación Next.js. En lugar de desplegar lógica compleja en API Routes, orquestas webhooks, llamadas a LLMs y persistencia en Supabase desde n8n. El resultado: iteración rápida, menos infra y más foco en producto.

    Resumen rápido (lectores con prisa)

    Qué es: Uso de n8n como gateway y motor de cómputo para orquestar webhooks, llamadas a LLMs, scraping y persistencia.

    Cuándo usarlo: Orquestación entre APIs, pipelines IA/ETL y MVPs donde la latencia y el throughput no son críticos.

    Por qué importa: Permite iterar sin redeploy, con reintentos nativos, observabilidad visual y menos código inicial.

    Cómo funciona (alto nivel): Next.js → webhook n8n → procesamiento (IA/scraping/APIs) → persistencia en Supabase → frontend en realtime.

    Automatización con IA y n8n: modelo arquitectónico y por qué funciona

    El patrón es simple y poderoso: Next.js → webhook n8n → procesamiento (IA, scraping, APIs) → persistencia (Supabase) → frontend en realtime. n8n actúa como API Gateway y motor de cómputo sin que tengas que escribir ni desplegar microservicios.

    Ventajas prácticas:

    • Cambios en lógica sin redeploy.
    • Reintentos y manejo de errores nativos en los workflows.
    • Observabilidad visual por ejecución.

    Limitaciones obvias: latencia, complejidad de workflows y límites de escala. Documentación útil: n8n, Supabase, OpenAI embeddings y límites de funciones en serverless como referencia a por qué evitar lógica pesada en serverless.

    Playbooks prácticos de Dominicode Labs (de idea a producción)

    A continuación tres playbooks reales, listos para implementar y adaptar.

    Playbook A — Generador de informes (asíncrono, UX responsiva)

    • 1. Next.js POST → webhook n8n con {userId, url, jobMeta}.
    • 2. n8n: valida auth (JWT de Supabase), inserta fila en reports con status=pending.
    • 3. n8n: descarga HTML o usa scraping (Browserless/Playwright), extrae texto.
    • 4. n8n: genera embeddings y contexto; llama a LLM para generar el informe.
    • 5. n8n: guarda PDF/markdown en Supabase Storage y actualiza reports a completed.
    • 6. Frontend: escucha Supabase Realtime y muestra resultado cuando cambia el status.

    Playbook B — Enriquecimiento y scoring de leads (evento-driven)

    • 1. Webhook recibe lead.
    • 2. n8n: Clearbit/Hunter → enriquece.
    • 3. n8n: LLM evalúa fit (prompt estructurado), asigna score.
    • 4. Si score > threshold, n8n: crea entidad en CRM y notifica Slack; siempre actualiza tabla leads.

    Playbook C — Pipeline RAG ligero (knowledge base)

    • 1. Ingesta: usuario sube documento → Supabase insertar fila uploads.
    • 2. Database Webhook de Supabase → n8n: descarga, chunking, embeddings (OpenAI), inserción en tabla documents con pgvector.
    • 3. Consulta: usuario pregunta → webhook → n8n: embed query, RPC a Postgres/pgvector, LLM con contexto, actualizar queries con respuesta.

    Referencias técnicas: pgvector en Supabase y ejemplos de workflows en n8n.

    Límites y señales de que debes refactorizar a código

    n8n no es la solución para todo. Señales de alarma:

    • Workflows con más de ~25–30 nodos: difícil de mantener.
    • Necesidad de latencia sub-500ms o throughput extremo (>10k reqs/min).
    • Estado transaccional complejo o requisitos ACID.
    • Lógica condicional anidada profunda o cálculos intensivos.

    Si observas esas señales, muévete a servicios escritos (Deno/Cloudflare Workers, microservicios en Node/Python) y usa n8n para orquestación superior o tareas periféricas.

    Seguridad y operaciones: checklist mínimo

    • Nunca expongas webhooks sin proteger:
      • Validación de header x-api-key o JWT en el primer nodo.
      • Rotación periódica de claves.
    • Row Level Security (RLS) en Supabase para evitar lecturas no autorizadas.
    • Rate limiting y circuit breaker: implementa checks iniciales (Redis o tablas de rate) para mitigar bursts.
    • Logging centralizado y alertas (Slack/email) en fallos críticos.
    • Dev local: ngrok/localtunnel o host.docker.internal para probar webhooks.

    Guía RLS: Guía RLS. n8n security docs: n8n.

    Cómo decidir en 3 pasos (criterio Dominicode)

    1. ¿La tarea es orquestación entre APIs o requiere IA/ETL? Si sí → n8n.
    2. ¿Requiere latencia ultra-baja o transacciones complejas? Si sí → código.
    3. ¿La complejidad del workflow crecerá con el tiempo? Si sí → diseñar desde el inicio con posibilidad de migración gradual a servicios.

    Cierre: cuándo apostar por n8n (y cómo hacerlo responsablemente)

    Usar n8n como backend serverless para Next.js acelera la entrega de features de IA y reduce boilerplate. Es ideal para MVPs y procesos de integración/orquestación donde latencia y throughput no sean críticos. Implementa límites claros, monitoreo y una ruta de migración a microservicios cuando la complejidad y el tráfico lo exijan. Con esos guardrails, automatización con IA y n8n deja de ser un experimento y se convierte en una herramienta productiva para equipos que quieren mover rápido y mantener criterio técnico.

    Más recursos

    Si buscas playbooks, plantillas y ejemplos prácticos para acelerar pipelines de IA y orquestación, revisa Dominicode Labs. Encontrarás material orientado a producción y guías para migración gradual desde workflows a servicios cuando haga falta.

    FAQ

    ¿Por qué usar n8n como backend sin servidor?

    n8n permite orquestar llamadas a APIs, LLMs, scraping y persistencia sin desplegar microservicios. Esto reduce tiempo de entrega y el esfuerzo operativo en etapas tempranas del producto.

    Además incluye reintentos, manejo de errores y observabilidad visual por ejecución, lo que facilita iterar en lógica de negocio sin redeploys constantes.

    ¿Cuándo n8n no es la mejor opción?

    Cuando necesitas latencia sub-500ms, throughput extremo (>10k reqs/min), transacciones ACID o workflows con más de ~25–30 nodos mantenibles, es preferible migrar a servicios escritos (Deno, Cloudflare Workers, microservicios en Node/Python).

    ¿Cómo proteger los webhooks de n8n?

    Valida un header x-api-key o un JWT en el primer nodo del workflow y rota las claves periódicamente. Complementa con rate limiting y checks previos para mitigar abusos.

    ¿Cómo integrar con Supabase y pgvector?

    Usa webhooks de la base de datos para disparar workflows en n8n que descarguen archivos, realicen chunking y generen embeddings (OpenAI). Inserta vectores en una tabla con pgvector en Supabase y consulta con RPC a Postgres/pgvector desde n8n.

    ¿Qué monitoreo y manejo de errores aplicar?

    Centraliza logs y configura alertas (Slack/email) en fallos críticos. Aprovecha reintentos nativos de n8n y añade circuit breakers o tablas/Redis para rate limiting y protección frente a bursts.

    ¿Qué documentación debo revisar primero?

    Revisa la doc oficial de n8n, las guías de Supabase y la guía de embeddings de OpenAI embeddings.

  • Construye aplicaciones en tiempo real con Socket.IO y React

    Construye aplicaciones en tiempo real con Socket.IO y React

    Cómo hacer app en tiempo real con socket io y react

    Tiempo estimado de lectura: 7 min

    • Arquitectura práctica para aplicaciones en tiempo real.
    • Implementación de Socket.IO y React para notificaciones instantáneas.
    • Patrones de integración y consideraciones de escalado.
    • Mejores prácticas de seguridad y robustez.
    • Uso de herramientas para la automatización y workflows.

    Tabla de contenidos

    Cómo hacer app en tiempo real con socket io y react: visión general

    En pocas líneas: Socket.IO te da transporte bidireccional (WebSocket con fallback), reconexión automática y primitives útiles (rooms, namespaces). React es la capa de UI que debe consumir eventos sin perder rendimiento ni crear conexiones fantasma. Empezaremos por el servidor, pasaremos al cliente con un custom hook y acabaremos con consideraciones de escalado y seguridad.

    1. Servidor (Node.js + Socket.IO): handshake y eventos básicos

    Instala:

    • Server: npm i socket.io express
    • Client: npm i socket.io-client

    Ejemplo mínimo que maneja CORS y eventos:

    const express = require('express');
    const http = require('http');
    const { Server } = require('socket.io');
    
    const app = express();
    const server = http.createServer(app);
    const io = new Server(server, {
      cors: { origin: "http://localhost:3000", methods: ["GET","POST"] }
    });
    
    io.on('connection', (socket) => {
      console.log('conectado', socket.id);
    
      socket.on('join', room => socket.join(room));
      socket.on('message', ({room, text}) => {
        io.to(room).emit('message', { text, from: socket.id });
      });
    
      socket.on('disconnect', reason => console.log('desconectado', socket.id, reason));
    });
    
    server.listen(3001);
    

    Documentación oficial: https://socket.io/docs/v4/

    Puntos clave:

    • Configura CORS y handshake antes de exponer eventos.
    • Usa acknowledgements para garantizar entrega: socket.emit('event', data, (ack) => {}).

    2. Cliente React: conexión controlada y custom hook

    Evita abrir sockets dentro del render del componente. Encapsula la lógica en un hook para controlar mount/unmount y evitar listeners duplicados:

    // hooks/useSocket.js
    import { useEffect, useRef, useCallback } from 'react';
    import { io } from 'socket.io-client';
    
    export function useSocket(url) {
      const ref = useRef(null);
    
      useEffect(() => {
        ref.current = io(url, { auth: { token: localStorage.getItem('token') } });
        return () => ref.current.disconnect();
      }, [url]);
    
      const on = useCallback((event, cb) => {
        ref.current.on(event, cb);
        return () => ref.current.off(event, cb);
      }, []);
    
      const emit = useCallback((event, data, cb) => {
        ref.current.emit(event, data, cb);
      }, []);
    
      return { on, emit, socket: ref.current };
    }
    

    En componente:

    • Suscribe con useEffect y limpia con el retorno.
    • Usa React.memo y fragmenta la UI para minimizar re-renders por eventos frecuentes.

    React hooks reference: https://reactjs.org/docs/hooks-effect.html

    3. Patterns para producción: rooms, namespaces, Redis y sticky sessions

    • Rooms: agrupan sockets (salas por proyecto/cliente). Emitir a una room es eficiente y evita broadcast innecesario.
    • Namespaces: separan lógica (/chat, /admin) cuando quieres middlewares distintos.
    • Escalado: si ejecutas múltiples instancias Node, usa el Redis Adapter para sincronizar eventos entre instancias: https://socket.io/docs/v4/adapter/
    • Load balancers: necesitas sticky sessions si dependes de la conexión TCP del WebSocket; alternativamente, usa Redis adapter para asegurar entrega entre instancias.

    Evita emitir todo a todos; diseña eventos granulares y usa rooms para control de alcance.

    4. Seguridad y robustez

    • Autenticación en handshake: valida tokens en socket.handshake.auth o mediante middleware antes de aceptar conexión.
    • Rate limiting + validation: evita flood de eventos y sanitiza payloads.
    • Reconexión: Socket.IO reintenta con backoff. Diseña idempotencia en el servidor para evitar efectos duplicados si el cliente reemite.
    • Cleanup: en disconnecting revisa socket.rooms para liberar recursos o actualizar presencia.

    5. Observabilidad y testing

    • Loguea eventos críticos y mide latencia de round-trip.
    • Tests end-to-end: usa herramientas que simulen múltiples clientes para validar rooms y reconexión.
    • Instrumenta métricas (connections, disconnects, msgs/sec) y alertas si la latencia sube.

    6. Automatización y workflows (cuando el tiempo real debe actuar)

    El valor real aparece cuando los eventos en tiempo real disparan acciones automatizadas: un webhook de un servicio externo, un agente que procesa un payload y genera notificaciones en la UI. Si quieres convertir eventos en workflows observables y repetibles, integra Socket.IO con orquestadores como n8n.

    En Dominicode Labs trabajamos plantillas prácticas que conectan Socket.IO con automations y agentes: blueprints n8n que consumen eventos, ejecutan lógica y notificar en tiempo real al frontend. Si tu objetivo es que la capa en tiempo real no sea solo UI reactivas sino sistemas productivos que actúen, Dominicode Labs ofrece ejemplos y patrones reutilizables.

    Resumen y checklist rápido

    • No instancies sockets en cada render; usa hooks/context.
    • Emplea rooms/namespaces para segmentar flujo.
    • Asegura handshake (JWT) y valida payloads.
    • Escala con Redis Adapter y considera sticky sessions.
    • Monitorea latencia y reconexiones; diseña idempotencia.
    • Si la app dispara workflows, convierte eventos en pipelines orquestados (n8n) y prueba la integración en un entorno controlado (Dominicode Labs).

    Construir apps en tiempo real es más arquitectura que magia: disciplina en la gestión de conexiones, eventos bien diseñados y observabilidad convierten una demo en un sistema mantenible y escalable.

    FAQ

    ¿Qué es Socket.IO? Socket.IO es una biblioteca que permite la comunicación en tiempo real entre clientes y servidores, utilizando tecnologías como WebSockets y proporcionan funcionalidades como reconexión automática y manejo de eventos.

    ¿Cómo se configura un server con Socket.IO? Se configura creando un servidor HTTP y un servidor Socket.IO, manejando eventos de conexión y desconexión, permitiendo establecer rooms y emitir mensajes entre ellos.

    ¿Qué son rooms y namespaces en Socket.IO? Rooms permiten agrupar sockets para que puedan interactuar entre sí, mientras que namespaces permiten dividir la lógica de la aplicación en diferentes rutas, cada una con su propio conjunto de eventos y middleware.

    ¿Cómo asegurar la comunicación en tiempo real? Implementando autenticación en handshake, validando tokens, evitando el flood de eventos y sanitizando los payloads antes de procesarlos.

    ¿Qué herramientas se pueden usar para testing? Se pueden usar herramientas que simulen múltiples conexiones de cliente y validen la lógica de rooms y reconexiones, asegurando así que la aplicación funciona correctamente bajo carga.

  • Entendiendo var, let y const

    Entendiendo var, let y const

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

    Tiempo estimado de lectura: 4 min

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

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

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

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

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

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

    Ejemplo: scope en if

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

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

    2. Hoisting y la Temporal Dead Zone (TDZ)

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

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

    Ejemplo: acceso antes de declarar

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

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

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

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

    Ejemplo: objeto con const

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

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

    4. Buenas prácticas y criterio profesional

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

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

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

    5. Consideraciones de rendimiento y herramientas

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

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

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

    Conclusión práctica

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

    FAQ

    ¿Por qué usar const por defecto?

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

    ¿let previene fugas de variables dentro de bucles?

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

    ¿const hace inmutables los objetos?

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

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

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

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

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

    ¿Influye let/const en el rendimiento?

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

  • JavaScript Date es un Desastre: Por Qué Temporal es la Solución que Necesitas

    JavaScript Date es un Desastre: Por Qué Temporal es la Solución que Necesitas

    ¿Te ha pasado que trabajas con fechas en JavaScript y sientes que `Date` te está jugando una mala pasada?

    Si eres desarrollador, probablemente ya te topaste con problemas de zonas horarias, formatos de fecha inconsistentes o bugs raros que no tienen explicación.

    Bueno, prepárate porque `Temporal` está aquí para cambiar todo eso.

    El tiempo nos hace tontos a todos, y JavaScript tampoco se queda corto en ese aspecto. Honestamente, nunca me ha molestado mucho lo último — de hecho, si has trabajado con JavaScript, ya sabes que en gran medida _disfruto_ de las pequeñas peculiaridades del lenguaje, créeme o no.

    Me gusta cuando puedes ver las costuras; me gusta cómo, por muy formal e inquebrantable que pueda parecer la especificación ES-262, aún puedes ver todas las decisiones buenas _y_ malas tomadas por las cientos de personas que han estado construyendo el lenguaje en pleno vuelo, si sabes dónde mirar. JavaScript tiene _carácter_.

    Claro, no necesariamente hace todo _exactamente_ de la manera que uno podría esperar, pero ya sabes, si me preguntas, ¡JavaScript tiene un encanto real una vez que lo conoces!

    Sin embargo, hay una parte del lenguaje donde eso inmediatamente se desmorona para mí.

    // Los meses numéricos están indexados desde cero, pero los años y días no:
    console.log( new Date(2026, 1, 1) );
    // Resultado: Date Sun Feb 01 2026 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    El constructor `Date`.
    
    // Una cadena numérica entre 32 y 49 se asume que está en los años 2000:
    console.log( new Date( "49" ) );
    // Resultado: Date Fri Jan 01 2049 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    // Una cadena numérica entre 33 y 99 se asume que está en los años 1900:
    console.log( new Date( "99" ) );
    // Resultado: Date Fri Jan 01 1999 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    // ...Pero 100 y más empiezan desde el año cero:
    console.log( new Date( "100" ) );
    // Resultado: Date Fri Jan 01 0100 00:00:00 GMT-0456 (hora estándar de Colombia)
    
    Detesto `Date` _inmensamente_.
    
    // Una fecha basada en cadena funciona como podrías esperar:
    console.log( new Date( "2026/1/2" ) );
    // Resultado: Date Fri Jan 02 2026 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    // ¿Un cero inicial en el mes? No hay problema; uno es uno, ¿verdad?
    console.log( new Date( "2026/02/2" ) );
    // Resultado: Date Mon Feb 02 2026 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    // ¿Formato ligeramente diferente? ¡Por supuesto!
    console.log( new Date( "2026-02-2" ) );
    // Resultado: Date Mon Feb 02 2026 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    // ¿Un cero inicial en el día? Por supuesto; ¿por qué no funcionaría?
    console.log( new Date('2026/01/02') );
    // Resultado: Date Fri Jan 02 2026 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    // A menos, por supuesto, que separes el año, mes y fecha con guiones.
    // Entonces se equivoca con el _día_ (y además cambia la zona horaria, ¡qué locura!).
    console.log( new Date('2026-01-02') );
    // Resultado: Date Thu Jan 01 2026 19:00:00 GMT-0500 (hora estándar de Colombia)
    


    Date apesta.
    Fue copiado apresurada y descaradamente de Java en el coche de camino a la escuela y obtuvo todas las mismas respuestas incorrectas, hasta el nombre en la parte superior de la página: `Date` no representa una _fecha_, representa un _tiempo_.

    Internamente, las fechas se almacenan como valores numéricos llamados **valores de tiempo**: timestamps de Unix, divididos en 1,000 milisegundos — lo cual, bueno, sí, un tiempo Unix también necesariamente implica una fecha, claro, pero _aún así_: Date representa un tiempo, del cual puedes inferir una fecha.

    Esto se vuelve aún más frustrante cuando trabajas con aplicaciones que necesitan manejar múltiples zonas horarias (México, Colombia, Argentina, Chile, etc.) o cuando intentas parsear fechas en formato DD/MM/YYYY que es el estándar en nuestra región.

    // Timestamp Unix para el lunes, 4 de diciembre de 1995 12:00:00 AM GMT-05 (el día en que se anunció JavaScript):
    const timestamp = 818053200;
    
    console.log( new Date( timestamp * 1000 ) );
    // Resultado: Date Mon Dec 04 1995 00:00:00 GMT-0500 (hora estándar de Colombia)
    

    Palabras como “fecha” y “tiempo” significan cosas, pero, claro — _lo que sea, JavaScript_.

    Java deprecó _su_ `Date` allá por 1997, solo unos años después de que el `Date` de JavaScript fuera liberado en el mundo desprevenido; mientras tanto, hemos estado atados a este desastre desde entonces. Es salvajemente inconsistente cuando se trata de analizar fechas, como has visto hasta ahora aquí.

    No tiene sentido de zonas horarias más allá de la local y GMT, lo cual es un problema enorme para desarrolladores que trabajan con aplicaciones internacionales o que necesitan manejar usuarios en diferentes países (México tiene múltiples zonas horarias, Argentina y Chile tienen horario de verano, etc.).

    Y hablando de eso, `Date` _solo_ respeta el modelo de calendario gregoriano. No entiende en absoluto el concepto de horario de verano de manera consistente, lo cual— quiero decir, bueno, sí, igual, pero yo no estoy _hecho de computadoras_.

    Todas estas deficiencias hacen que sea excepcionalmente común usar bibliotecas de terceros como Moment.js, date-fns o Day.js para trabajar alrededor de todo esto, algunas de las cuales son absolutamente _masivas_; un drenaje de rendimiento que ha causado daño real y medible a la web, especialmente en dispositivos móviles.

    Ninguna de estas es mi problema principal con `Date`. Mi queja es sobre más que analizar o sintaxis o “ergonomía del desarrollador” o el impacto de rendimiento en toda la web de soluciones completamente necesarias o incluso la definición de la palabra “fecha”. Mi problema con `Date` es profundo en el alma. Mi problema con `Date` es que usarlo significa _desviarse de la naturaleza fundamental del tiempo mismo_.

    Todos los valores primitivos de JavaScript son **inmutables**, lo que significa que los valores mismos no pueden ser cambiados. El valor numérico `3` nunca puede representar nada más que el concepto de “tres” — no puedes hacer que `true` signifique algo diferente a “verdadero”. Estos son valores con significados concretos, inquebrantables, del mundo real. Sabemos qué es tres.
    No puede ser alguna otra cosa que no sea tres. Estos tipos de datos inmutables se almacenan **por valor**, lo que significa que una variable que representa el valor numérico `3` efectivamente “contiene” — y por lo tanto se comporta como — el valor numérico `3`.

    Cuando un valor inmutable se asigna a una variable, el motor de JavaScript crea una copia de ese valor y almacena la copia en memoria:

    const theNumber = 3;
    
    console.log( theNumber );
    // Resultado: 3

    Esto encaja bien con el modelo mental común para “una variable”: `theNumber` “contiene” `3`.

    Cuando inicializamos `theOtherNumber` con el valor vinculado a `theNumber`, ese modelo mental se mantiene: una vez más se crea un `3` y se almacena en memoria. `theOtherNumber` ahora puede pensarse como que contiene su propio `3` discreto.

    const theNumber = 3;
    const theOtherNumber = theNumber;
    
    console.log( theOtherNumber );
    // Resultado: 3;

    El valor de `theNumber` no cambia cuando alteramos el valor asociado con `theOtherNumber`, por supuesto — de nuevo, estamos trabajando con dos instancias discretas de `3`.

    const theNumber = 3;
    let theOtherNumber = theNumber;
    
    theOtherNumber = 5;
    
    console.log( theOtherNumber );
    // Resultado: 5;
    
    console.log( theNumber );
    // Resultado: 3

    Cuando cambias el valor vinculado a `theOtherNumber`, no estás cambiando el `3`, estás creando un nuevo valor numérico inmutable y vinculándolo en su lugar. De ahí un error cuando intentas manipular una variable declarada usando `const`:

    const theNumber = 3;
    
    theNumber = 5;
    // Resultado: Uncaught TypeError: invalid assignment to const 'theNumber'

    No puedes cambiar la vinculación de un `const`, y _definitivamente_ no puedes alterar el significado de `3`.

    Los tipos de datos que _pueden_ ser cambiados después de ser creados son **mutables**, lo que significa que el valor de datos _mismo_ puede ser alterado. Los valores de objeto — cualquier valor no primitivo, como un array, map o set — son mutables.

    Los objetos se almacenan **por referencia**, lo que significa que una variable que representa un objeto no contiene el objeto en sí, sino una referencia a la ubicación en memoria donde se almacena ese objeto. Cuando asignas un objeto a una variable, el motor de JavaScript almacena una referencia a ese objeto en memoria, no una copia del objeto mismo.

    const theObject = { value: 3 };
    
    console.log( theObject );
    // Resultado: { value: 3 }

    Cuando inicializamos `theOtherObject` con el valor vinculado a `theObject`, estamos creando una nueva referencia que apunta a la misma ubicación en memoria que `theObject`. Ambos `theObject` y `theOtherObject` ahora “apuntan” al mismo objeto en memoria.

    const theObject = { value: 3 };
    const theOtherObject = theObject;
    
    console.log( theOtherObject );
    // Resultado: { value: 3 };

    Cuando alteramos el objeto referenciado por `theOtherObject`, estamos alterando el mismo objeto en memoria que `theObject` también está referenciando. Ambos `theObject` y `theOtherObject` ahora apuntan a un objeto con un valor diferente.

    const theObject = { value: 3 };
    const theOtherObject = theObject;
    
    theOtherObject.value = 5;
    
    console.log( theOtherObject );
    // Resultado: { value: 5 };
    
    console.log( theObject );
    // Resultado: { value: 5 }

    Esto es lo que significa trabajar con valores mutables: cuando alteras el objeto, estás alterando el valor real que representa, no solo la variable que lo referencia.

    Y aquí está el problema con `Date`: `Date` es un objeto, lo que significa que es mutable. Cuando creas una instancia de `Date`, estás creando un objeto que representa un momento específico en el tiempo, y ese objeto puede ser alterado después de su creación.

    
    const today = new Date();
    
    console.log( today );
    // Resultado: Date Wed Dec 31 2025 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    const tomorrow = today;
    tomorrow.setDate( today.getDate() + 1 );
    
    console.log( tomorrow );
    // Resultado: Date Thu Jan 01 2026 00:00:00 GMT-0500 (hora estándar de Colombia)
    
    console.log( today );
    // Resultado: Date Thu Jan 01 2026 00:00:00 GMT-0500 (hora estándar de Colombia)
    

    ¡Oh no! `today` también cambió, porque `tomorrow` y `today` son referencias al mismo objeto `Date` en memoria. Cuando llamamos `setDate` en `tomorrow`, estamos alterando el mismo objeto que `today` está referenciando.

    Esto es un problema fundamental. El tiempo no funciona así. El tiempo es inmutable. El 31 de diciembre de 2025 siempre será el 31 de diciembre de 2025.
    No puedes “cambiar” el 31 de diciembre de 2025 para que sea el 1 de enero de 2026 — esos son dos momentos diferentes en el tiempo, y no puedes hacer que uno se convierta en el otro.

    Imagínate esto en una aplicación de e-commerce donde calculas fechas de entrega, o en un sistema de reservas donde manejas fechas de check-in y check-out. Un bug así puede costarte clientes y dinero real.

    Pero `Date` te permite hacer exactamente eso. Puedes tomar un objeto `Date` que representa el 31 de diciembre de 2025 y “cambiarlo” para que represente el 1 de enero de 2026, y eso es simplemente incorrecto. Es una violación de cómo funciona el tiempo en el mundo real.

    const today = new Date();
    
    const addDay = theDate => {
    	theDate.setDate( theDate.getDate() + 1 );
    	return theDate;
    };
    
    console.log(`Mañana será ${ addDay( today ).toLocaleDateString() }. Hoy es ${ today.toLocaleDateString() }.`);
    // Resultado: Mañana será 1/1/2026. Hoy es 1/1/2026.
    

    Esto es un desastre. `today` y el resultado de `addDay( today )` son el mismo objeto, por lo que ambos muestran la misma fecha. Esto no es cómo debería funcionar el tiempo.

    El tiempo es inmutable. No puedes cambiar el pasado, no puedes cambiar el presente, y ciertamente no puedes cambiar el futuro simplemente alterando un objeto en memoria. El tiempo simplemente no funciona así.

    Y aquí está la cosa: esto no es solo un problema filosófico. Esto causa errores reales en código real. Es fácil crear accidentalmente múltiples referencias al mismo objeto `Date` y luego alterar ese objeto de maneras que afectan todas esas referencias, lo que lleva a bugs sutiles y difíciles de rastrear.

    Entra `Temporal`.

    `Temporal` es una propuesta para una nueva API de JavaScript para trabajar con fechas y tiempos. Es una reescritura completa de cómo JavaScript maneja el tiempo, y está diseñada para abordar todos los problemas con `Date` que hemos estado discutiendo.

    Lo más importante: `Temporal` es inmutable. Cuando creas un objeto `Temporal`, ese objeto representa un momento específico en el tiempo, y ese momento no puede ser cambiado. Cuando quieres representar un momento diferente en el tiempo, creas un nuevo objeto `Temporal`.

    const today = Temporal.Now.plainDateISO();
    
    console.log( today );
    // Resultado: Temporal.PlainDate 2025-12-31
    
    const tomorrow = today.add({ days: 1 });
    
    console.log( tomorrow );
    // Resultado: Temporal.PlainDate 2026-01-01
    
    console.log( today );
    // Resultado: Temporal.PlainDate 2025-12-31
    

    ¡Perfecto! `today` sigue siendo el 31 de diciembre de 2025, y `tomorrow` es el 1 de enero de 2026. No hay confusión, no hay efectos secundarios inesperados, no hay violación de la naturaleza fundamental del tiempo.

    `Temporal` también aborda muchos de los otros problemas con `Date`:

    **Análisis consistente**: `Temporal` tiene un análisis de fechas mucho más predecible y consistente que `Date`.

    **Soporte de zonas horarias**: `Temporal` tiene soporte completo para zonas horarias, no solo la zona horaria local y GMT.

    **Soporte de calendarios**: `Temporal` puede trabajar con diferentes sistemas de calendario, no solo el calendario gregoriano.

    **API más clara**: `Temporal` tiene una API mucho más clara e intuitiva que `Date`.

    Pero lo más importante es que `Temporal` respeta la naturaleza inmutable del tiempo. Cuando trabajas con `Temporal`, estás trabajando con valores que representan momentos específicos en el tiempo, y esos valores no pueden ser cambiados. Si quieres representar un momento diferente en el tiempo, creas un nuevo valor `Temporal`.

    Esto es cómo debería funcionar el tiempo en programación. El tiempo es inmutable en el mundo real, y debería ser inmutable en nuestro código también.

    `Temporal` todavía está en desarrollo, pero ya está disponible en las versiones más recientes de Chrome y Firefox. Pronto estará disponible en todos los navegadores principales, y finalmente podremos dejar `Date` atrás y usar una API de tiempo que realmente respete cómo funciona el tiempo.

    Para desarrolladores, esto significa poder trabajar con zonas horarias de manera nativa sin depender de bibliotecas pesadas, manejar formatos de fecha locales (DD/MM/YYYY) de forma consistente, y evitar esos bugs raros que aparecen cuando trabajas con fechas en aplicaciones internacionales.

    const today = Temporal.Now.plainDateISO();
    
    // Fecha local actual:
    console.log( today );
    /* Resultado (expandido):
    Temporal.PlainDate 2025-12-30
    	<prototype>: Object { … }
    */
    
    // Año local actual:
    console.log( today.year );
    // Resultado: 2025
    
    // Fecha y hora local actual:
    console.log( today.toPlainDateTime() );
    /* Resultado (expandido):
    Temporal.PlainDateTime 2025-12-30T00:00:00
    	<prototype>: Object { … }
    */
    
    // Especificar que esta fecha representa la zona horaria America/Mexico_City:
    console.log( today.toZonedDateTime( "America/Mexico_City" ) );
    /* Resultado (expandido):
    Temporal.ZonedDateTime 2025-12-30T00:00:00-06:00[America/Mexico_City]
    	<prototype>: Object { … }
    */
    
    // O trabajar con otras zonas horarias latinoamericanas:
    console.log( today.toZonedDateTime( "America/Bogota" ) ); // Colombia
    console.log( today.toZonedDateTime( "America/Buenos_Aires" ) ); // Argentina
    console.log( today.toZonedDateTime( "America/Santiago" ) ); // Chile
    
    // Agregar un día a esta fecha:
    console.log( today.add({ days: 1 }) );
    /*
    Temporal.PlainDate 2025-12-31
    	<prototype>: Object { … }
    */
    
    // Agregar un mes y un día a esta fecha, y restar dos años:
    console.log( today.add({ months: 1, days: 1 }).subtract({ years: 2 }) );
    /*
    Temporal.PlainDate 2024-01-31
    	<prototype>: Object { … }
    */
    
    console.log( today );
    /* Resultado (expandido):
    Temporal.PlainDate 2025-12-30
    	<prototype>: Object { … }
    */

    Observa cómo ninguna de estas transformaciones requirió que manualmente creáramos nuevos objetos, _y_ que el valor del objeto referenciado por `today` permanece sin cambios. A diferencia de `Date`, los métodos que usamos para interactuar con un objeto `Temporal` resultan en objetos `Temporal` _nuevos_, en lugar de requerir que los usemos en el contexto de una nueva instancia o modificar la instancia con la que estamos trabajando — que es cómo podemos encadenar los métodos `add` y `subtract` juntos en `today.add({ months: 1, days: 1 }).subtract({ years: 2 })`.

    Claro, todavía estamos trabajando con objetos, y eso significa que estamos trabajando con estructuras de datos mutables que representan valores del mundo real:

    const today = Temporal.Now.plainDateISO();
    
    today.someProperty = true;
    
    console.log( today );
    
    /* Resultado (expandido):
    Temporal.PlainDate 2026-01-05
    	someProperty: true
    	<prototype>: Object { … }
    */

    …Pero el valor representado por ese objeto `Temporal` no está destinado a ser cambiado durante el curso normal de interactuar con él — aunque el objeto sigue siendo esencialmente mutable, no estamos atrapados usando ese objeto de maneras que podrían alterar lo que significa en términos de fechas y tiempos del mundo real.
    Lo acepto.

    Entonces, revisemos ese pequeño script “hoy es X, mañana es Y” que escribimos usando `Date` anteriormente. Primero, lo arreglaremos asegurándonos de que estamos trabajando con dos instancias discretas de `Date` en lugar de modificar la instancia que representa la fecha de hoy:


    const today = new Date();
    
    const addDay = theDate => {
    	const tomorrow = new Date();
    
    	tomorrow.setDate( theDate.getDate() + 1 );
    	return tomorrow;
    };
    
    console.log(`Mañana será ${ addDay( today ).toLocaleDateString() }. Hoy es ${ today.toLocaleDateString() }.`);
    // Resultado: Mañana será 1/1/2026. Hoy es 12/31/2025.
    

    Gracias, lo odio.

    Bien, está bien. Cumple su función, tal como lo ha hecho desde el día en que `Date` se abrió paso por primera vez en la web. No estamos alterando sin saberlo el valor de `today` ya que estamos creando una nueva instancia de `Date` dentro de nuestra función `addDay` — verboso, pero funciona, como lo ha hecho durante décadas ahora. Le agregamos `1`, que tenemos que simplemente _saber_ que significa agregar un _día._

    Luego en nuestro template literal necesitamos seguir empujando a JavaScript para que nos dé la fecha en un formato que no incluya la hora actual, como una cadena. Es funcional, pero verboso.

    Ahora, rehagámoslo usando `Temporal`:

    const today = Temporal.Now.plainDateISO();
    
    console.log(`Mañana será ${ today.add({ days: 1 }) }. Hoy es ${ today }.`);
    // Resultado: Mañana será 2026-01-01. Hoy es 2025-12-31.
    

    Ahora sí estamos hablando.

    _Mucho mejor_. Más delgado, más eficiente, y _mucho_ menos margen para error. Queremos la fecha de hoy sin la hora, y el objeto que resulta de invocar `plainDateISO` (y cualquier nuevo objeto `Temporal` creado a partir de él) retendrá ese formato _sin_ ser coaccionado a una cadena.

    Formato: _verificado_.

    Queremos generar un valor que represente la fecha de hoy más un día, y queremos hacerlo de una manera donde estamos diciendo inequívocamente “agregar un día” sin conjeturas de análisis: _verificado_ y _verificado_.

    Lo más importante, no queremos correr el riesgo de que nuestro objeto `today` original sea alterado sin intención — porque el resultado de llamar al método `add` siempre será un nuevo objeto `Temporal`: _verificado_.

    `Temporal` va a ser una _mejora masiva_ sobre `Date`, y solo digo “va a ser” porque todavía no está completamente listo para uso en producción.

    La especificación de borrador para el objeto `Temporal` propuesto ha alcanzado la etapa tres del proceso de estandarización, lo que significa que ahora está oficialmente “recomendado para implementación” — aún no es parte del estándar que informa el desarrollo continuo de JavaScript mismo, pero lo suficientemente cerca como para que los navegadores puedan empezar a experimentar con él.

    Eso significa que los resultados de esa experimentación temprana pueden usarse para refinar aún más la especificación, por lo que nada está escrito en piedra todavía. Los estándares web son un proceso iterativo, después de todo.

    Ahí es donde entramos tú y yo. Ahora que `Temporal` ha llegado a las versiones más recientes de Chrome y Firefox — y otros, pronto — es hora de que entremos y probemos un poco. Puede que no hayamos tenido ninguna opinión sobre `Date`, pero podemos experimentar con `Temporal` antes de que lleguen las implementaciones finales.

    Pronto, JavaScript tendrá un manejo de fechas sensato y moderno, y finalmente podremos meter `Date` muy atrás en el cajón de chatarra con las bandas elásticas, tapas de frascos sin pareja, llaves misteriosas y probablemente pilas AA medio vacías — todavía presente, todavía una parte inexorable de la plataforma web, pero ya no nuestra primera, última y única forma de manejar fechas.

    Y solo tuvimos que esperar— bueno, espera, déjame calcular los números rápidamente con `Temporal`:

    Pruébalo

    const today = Temporal.Now.plainDateISO();
    const jsShipped = Temporal.PlainDate.from( "1995-12-04" );
    const sinceDate = today.since( jsShipped, { largestUnit: 'year' });
    
    console.log( `${ sinceDate.years } años, ${ sinceDate.months } meses, y ${ sinceDate.days } días.` );
    

    Ejecutar

    Claro, el mejor momento para reemplazar `Date` habría sido allá por 1995, pero oye: el segundo mejor momento es `Temporal.Now`, ¿verdad?

    ## ¿Por Qué Esto Importa para Desarrolladores en Latinoamérica?

    Como desarrolladores latinoamericanos, trabajamos constantemente con:

    **Aplicaciones internacionales** que necesitan manejar múltiples zonas horarias

    **Formatos de fecha locales** (DD/MM/YYYY) que `Date` maneja de forma inconsistente

    **Dispositivos móviles** donde el rendimiento importa y las bibliotecas pesadas de fechas afectan la experiencia del usuario

    **E-commerce y sistemas de reservas** donde un error con fechas puede costar dinero real

    `Temporal` viene a resolver todos estos problemas de forma nativa, sin necesidad de bibliotecas externas pesadas. Es hora de empezar a experimentar con esta nueva API y prepararnos para el futuro del manejo de fechas en JavaScript.

    **¿Te resultó útil este artículo?** Compártelo con otros desarrolladores que también están luchando con `Date`. Y si quieres profundizar más en JavaScript moderno, asegúrate de seguir aprendiendo sobre las nuevas características del lenguaje.

  • Guía completa para hacer WordPress headless con Next.js

    Guía completa para hacer WordPress headless con Next.js

    Cómo hacer mi WordPress headless ? Tutorial completo paso a paso

    Tiempo estimado de lectura: 8 min

    • Backend: WordPress como contenido (REST o GraphQL).
    • Frontend: Next.js (SSG/ISR/SSR) o cualquier framework moderno.
    • Integración: peticiones al endpoint WP, manejo de imágenes, SEO y auth.
    • Automatización: webhooks y n8n para builds y regeneración on-demand.

    Referencias útiles: WPGraphQL, WP REST API, Next.js, n8n, Vercel.

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

    Este tutorial proporciona una guía práctica para transformar WordPress en un CMS headless utilizando Next.js como frontend. Se abordan temas como la elección entre REST y GraphQL, la optimización de fetch, las imágenes, el SEO y como desplegar el sistema.

    Paso 1 — Preparar el backend (WordPress)

    1. Instala WordPress en un entorno controlado (subdominio o local).
    2. Decide API: REST nativa o GraphQL. Recomiendo WPGraphQL para consultas precisas.
    3. Plugins recomendados:
      • WPGraphQL (o la REST API nativa si prefieres).
      • WPGraphQL for Advanced Custom Fields (si usas ACF).
      • Plugin de Headless Mode (opcional) para evitar duplicidad de frontend.
    4. Prueba tu endpoint: /wp-json/ para REST o /graphql para GraphQL.
    query GetPosts {
      posts(first: 10) {
        nodes { slug title excerpt date featuredImage { node { sourceUrl } } }
      }
    }

    Paso 2 — Configurar frontend con Next.js

    Crea la app Next.js (App Router recomendado):

    npx create-next-app@latest mi-wp-headless --typescript
    cd mi-wp-headless

    Variables de entorno (.env.local):

    NEXT_PUBLIC_WORDPRESS_URL=https://api.tudominio.com
    NEXT_PUBLIC_GRAPHQL_ENDPOINT=https://api.tudominio.com/graphql

    Instala Apollo si usas GraphQL:

    npm install @apollo/client graphql

    Paso 3 — Conexión y fetching eficiente

    Puedes usar fetch nativo o Apollo. Ejemplo ligero con fetch en Server Component (App Router):

    export async function fetchAPI(query: string, variables = {}) {
      const res = await fetch(process.env.NEXT_PUBLIC_GRAPHQL_ENDPOINT!, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ query, variables }),
        next: { revalidate: 60 } // ISR básico
      });
      const json = await res.json();
      if (json.errors) throw new Error(JSON.stringify(json.errors));
      return json.data;
    }

    En app/page.tsx (Server Component):

    const data = await fetchAPI(`query { posts(first:10){ nodes{ slug title excerpt } } }`);

    Usa Server Components para minimizar JS en cliente y activa ISR con `next: { revalidate }` o `revalidate` en getStaticProps según tu versión Next.js.

    Paso 4 — Build, ISR y despliegue

    • Para contenido estático estable: SSG.
    • Para catálogo con alta lectura: ISR (`revalidate: 60` o ISR on-demand).
    • Despliega frontend en Vercel.
    • Despliega backend en hosting WP gestionado o VPS y protege el endpoint.

    Para ISR on-demand (regeneración específica) usa la API de revalidate de tu hosting (Vercel) o implementa webhook que dispare un job.

    Puntos críticos: imágenes, SEO, autenticación y previews

    • Imágenes: configura dominios externos en next.config.js y usa <Image />.
    • SEO: extrae meta (Yoast/RankMath) desde WP y monta tags en <head> (JSON-LD, og, canonical).
    • Auth: para endpoints privados o preview, usa JWT o NextAuth con credenciales WP.
    • Previews: implementa rutas de preview que verifiquen token y muestren contenido no publicado.

    Automatización y flujos productivos (n8n, agentes, Dominicode Labs)

    Desacoplar WP abre automatización real: webhooks en publish -> n8n recibe evento -> desencadena:

    • ISR on-demand para páginas afectadas,
    • comprobaciones de consistencia (links, imágenes),
    • generación de snippets sociales por agentes IA.

    En Dominicode Labs documentamos plantillas y workflows que conectan WordPress con n8n y despliegues automáticos. Ofrecemos:

    • Flujos n8n listos para ISR on-demand y pruebas de integridad.
    • Ejemplos de agentes que generan borradores y los inyectan vía GraphQL.

    Revisa: Dominicode Labs

    Conclusión práctica

    Hacer tu WordPress headless significa más control y mejor rendimiento, pero también más superficie técnica: CI/CD, caching, imágenes, SEO y auth. Empieza por un prototipo: WPGraphQL + Next.js en SSG/ISR, añade webhooks para regeneración y automatiza los builds críticos. Mide (Lighthouse, RUM) y ajusta revalidación según tráfico y consistencia requerida.

    Si buscas ejemplos de pipelines y flujos productivos ya probados, Dominicode Labs ofrece plantillas y documentación para acelerar la migración sin inventar la rueda.

    FAQ

    ¿Qué es WordPress headless?

    WordPress headless se refiere a configurar WordPress solo como un backend para gestión de contenido, mientras que se utiliza un frontend separado para la presentación visual, permitiendo usar tecnologías modernas.

    ¿Por qué utilizar GraphQL en vez de REST?

    GraphQL permite realizar consultas más precisas y eficientes en comparación con REST, ya que se pueden solicitar exactamente los datos que se necesitan, evitando cargas innecesarias.

    ¿Cómo optimizar el fetching de datos?

    Se puede optimizar el fetching usando técnicas como ISR (Incremental Static Regeneration) y utilizando fetch con revalidación en el backend para asegurar que se obtienen datos frescos sin sacrificar rendimiento.

    ¿Qué es ISR y cuándo deberíamos utilizarlo?

    ISR o Incremental Static Regeneration permite regenerar contenido estático en intervalos específicos, ideal para páginas que requieren actualizaciones frecuentes pero que son costosas en términos de rendimiento si se renderizan en cada solicitud.

    ¿Cómo manejar la autenticación en un CMS headless?

    La autenticación en un CMS headless se puede manejar mediante el uso de JWT (JSON Web Tokens) o NextAuth, permitiendo la integración de credenciales para acceder a datos privados o funcionalidad de previews.

  • El peligro de estudiar sin aplicar en desarrollo

    El peligro de estudiar sin aplicar en desarrollo

    Estás estudiando demasiado… y por eso no progresas

    Tiempo estimado de lectura: 10 min

    • El estudio sin implementación real limita el progreso.
    • Confundir consumo de información con competencia técnica es un error común.
    • Buscar certeza antes de actuar resulta en estancamiento.
    • La práctica deliberada es crucial para el aprendizaje efectivo.
    • La construcción de proyectos reales enriquece la experiencia de aprendizaje.

    Tabla de contenidos

    Estás estudiando demasiado… y por eso no progresas. Suena contradictorio, pero es un patrón muy común en developers, tech leads y builders que intentan “ponerse al día” con IA, automatización, frameworks, arquitectura o n8n. Cuanto más lees, más tutoriales guardas y más cursos empiezas, menos construyes. Y sin construcción real, tu criterio no madura, tu confianza no mejora y tu carrera se estanca.

    Este artículo no va de motivación ni de “disciplina”. Va de entender por qué el estudio sin un sistema de aplicación se convierte en evasión productiva, cómo detectarlo con señales objetivas y cómo cambiarlo con un método operativo (no inspirational): estudio mínimo útil, práctica deliberada y proyectos que te obliguen a tomar decisiones.

    Por qué “estás estudiando demasiado… y por eso no progresas”

    El problema no es estudiar. El problema es estudiar sin cerrar el ciclo:

    Información → comprensión → decisión → implementación → feedback → ajuste

    Cuando solo haces las dos primeras (información y comprensión), obtienes sensación de avance sin el coste real de avanzar: equivocarte en código, tomar decisiones, romper cosas, medir, refactorizar.

    1) Confundes consumo con competencia

    Leer sobre testing no te hace mejor testeando. Ver un vídeo sobre Clean Architecture no te hace mejor diseñando. Estudiar “agentes” no te hace mejor construyendo flujos con herramientas reales. La competencia técnica se forma cuando:

    • eliges trade-offs con restricciones reales (tiempo, deuda técnica, equipo, legacy)
    • implementas y ves consecuencias
    • corriges con feedback (errores, métricas, revisiones, incidentes)

    Si tu progreso se mide por “horas estudiadas” o “cursos completados”, estás midiendo input, no output.

    2) Estás buscando certeza antes de actuar (y no existe)

    Mucho estudio es una forma elegante de evitar el riesgo. El cerebro te pide garantías: “cuando entienda bien X, empiezo”. Pero en ingeniería, la certeza llega después de implementar la primera versión y ver qué falla.

    En el mundo real:

    • Aprendes observabilidad cuando tu servicio se cae.
    • Aprendes colas cuando tu API no aguanta picos.
    • Aprendes prompts cuando tu pipeline alucina en producción.

    No es romanticismo. Es cómo funciona el aprendizaje en sistemas complejos.

    3) Te estás dopando con novedad

    El contenido técnico está optimizado para enganchar: “lo nuevo”, “lo que viene”, “la librería definitiva”. Saltar de tema en tema mantiene la dopamina alta y la incomodidad baja. Construir un proyecto real hace lo contrario: te enfrenta a fricción, bugs y límites.

    El síntoma típico: “Estoy aprendiendo mucho” pero no puedes señalar una mejora verificable en tus entregables de los últimos 30 días.

    4) Estás evitando el trabajo que duele (pero te hace crecer)

    Hay tareas que hacen crecer rápido y casi nadie quiere hacer:

    • escribir tests de verdad para código legacy
    • instrumentar logs/métricas/tracing
    • refactorizar sin romper contratos
    • documentar decisiones (ADRs)
    • diseñar APIs con backward compatibility
    • mantener un workflow en producción (no un demo)

    Estudiar es más cómodo porque no exige exponerte a evaluación: un PR, un incidente, un review, una métrica.

    Señales objetivas de que estás atrapado en “estudio infinito”

    No es un juicio moral. Son indicadores prácticos:

    Señal A: no produces artefactos

    En 2–4 semanas deberías poder señalar al menos uno:

    • PR mergeado
    • script o tool interna útil
    • workflow automatizado en tu equipo
    • mejora de rendimiento medida
    • test suite ampliada con cobertura significativa en módulos críticos
    • documentación de arquitectura que el equipo usa

    Si no hay artefactos, tu estudio no está aterrizando.

    Señal B: cambias de roadmap cada semana

    “Ahora me voy a centrar en…”. Si tu foco cambia antes de que exista un output, estás comprando la ilusión de que el siguiente tema sí te desbloqueará.

    Señal C: consumes más de lo que implementas

    Un ratio simple:

    • Horas de implementación / horas de consumo
    • Si estás por debajo de 1:1 durante semanas, algo va mal.
    • En fases de crecimiento sano suele ser 2:1 o 3:1 (más implementación que consumo).

    Señal D: tu stack mental está lleno, pero tu stack de código no

    Sabes explicar conceptos pero no tienes “músculo” de ejecución: configurar, desplegar, depurar, instrumentar, mantener.

    Cómo progresar: un sistema operativo de aprendizaje (no “más ganas”)

    El antídoto no es “estudia menos”. Es estudiar con restricciones y con una unidad mínima de entrega.

    1) Define una “unidad de progreso” verificable

    Ejemplos para un developer:

    • “Implementar un endpoint con tests + métricas + docs”
    • “Crear un workflow en n8n que procese X y tenga alertas”
    • “Reducir el tiempo de build un 20% con cambios medidos”
    • “Automatizar una tarea repetitiva del equipo y medir tiempo ahorrado”

    Si no es verificable, es humo.

    2) Usa la regla 20/80 del estudio: solo lo necesario para ejecutar

    Estudio mínimo viable:

    • Documentación oficial cuando aplica (no 10 vídeos)
    • Un ejemplo de referencia
    • Una prueba rápida (spike) de 30–60 minutos
    • Luego implementación real

    La pregunta guía no es “¿entiendo esto?”. Es:

    “¿Tengo suficiente para tomar la siguiente decisión e implementarla?”

    3) Convierte todo aprendizaje en un deliverable pequeño (en 48–72 horas)

    Si no puedes convertir lo estudiado en algo implementado en 2–3 días, el scope es demasiado grande o estás estudiando por evasión.

    Ejemplos de deliverables pequeños:

    • un repo con un caso real y README honesto
    • un PR con una mejora puntual (y tests)
    • un workflow automatizado con logs y manejo de errores
    • un dashboard mínimo para visualizar una métrica

    4) Practica deliberada: repite lo difícil, no lo divertido

    La práctica deliberada se centra en el borde de tu habilidad. En software, suele ser:

    • depuración sistemática
    • diseño de interfaces y contratos
    • resiliencia: retries, idempotencia, rate limits
    • pruebas: unitarias, integración, contract tests
    • observabilidad y diagnóstico

    Haz un inventario: ¿qué evitas siempre? Eso es el gimnasio.

    5) Introduce feedback real (sin feedback no hay aprendizaje)

    Tres fuentes de feedback que sí cuentan:

    • Producción: errores, latencias, incidentes, costes
    • Código revisado: PRs con comentarios concretos
    • Usuarios internos: soporte, operaciones, ventas, el equipo

    El feedback de “me siento más seguro” es secundario. Lo que cuenta es lo que el sistema devuelve.

    Ejemplo realista: IA aplicada y automatización (donde estudiar demasiado es una trampa)

    En IA aplicada el problema se magnifica. Hay exceso de contenido y cambios constantes. La progresión real no viene de “estar al día”, sino de montar pipelines que sobrevivan a:

    • entradas sucias
    • ambigüedad
    • costes variables
    • alucinaciones
    • latencias
    • compliance y privacidad

    Si estás “aprendiendo agentes” pero no has implementado:

    • un sistema de evaluación (tests de prompts, golden datasets)
    • observabilidad (logs estructurados de inputs/outputs, trazas)
    • control de costes (presupuestos, caching, batch)
    • guardrails (validación, esquemas, verificación)

    …entonces estás en consumo, no en ingeniería.

    Aquí es donde tiene sentido trabajar con un enfoque de laboratorio aplicado. En Dominicode Labs ayudamos a equipos y builders a pasar de “conceptos de IA/automatización” a sistemas productivos: workflows en n8n, agentes con criterios de fiabilidad, observabilidad y mantenimiento, y automatizaciones que realmente ahorran tiempo (con métricas y ownership claro). No es consultoría de slides: es implementación con criterio.

    Qué estudiar (y qué dejar de estudiar) según tu etapa

    Si eres junior / mid: menos teoría general, más fundamentos aplicados

    Prioriza:

    • Git fluido, debugging, herramientas del runtime
    • testing básico pero constante
    • HTTP, APIs, DBs (índices, transacciones)
    • leer código ajeno y refactorizar con seguridad

    Reduce:

    • arquitectura “de libro” sin contexto
    • debates de frameworks como identidad personal
    • maratones de cursos sin proyectos

    Si eres senior: menos “novedades”, más sistemas operables

    Prioriza:

    • observabilidad y confiabilidad
    • diseño de interfaces y contratos
    • estrategias de migración (legacy, incremental)
    • performance con medición
    • incident response y postmortems

    Reduce:

    • cambiar de stack por moda
    • sobre-optimizar antes de medir
    • acumular “conocimiento declarativo” no aplicable

    Si eres tech lead / founder: estudia lo que reduce riesgo y acelera entrega

    Prioriza:

    • sistemas de delivery (CI/CD, entornos, releases)
    • automatización de procesos internos
    • métricas de negocio conectadas a ingeniería
    • coste total: infra + tiempo + mantenimiento

    Reduce:

    • perfeccionismo en tecnología no diferencial
    • “aprender por aprender” sin impacto

    La barrera real: no es falta de información, es falta de decisiones

    El progreso técnico se destraba cuando tomas decisiones con información incompleta, y te responsabilizas del resultado. Estudiar infinito suele ocultar una de estas fricciones:

    • miedo a equivocarte públicamente
    • miedo a escoger mal (stack, patrón, herramienta)
    • falta de un problema real (aprendes sin necesidad)
    • falta de ownership (nadie te exige entregar)

    Solución práctica: elige un problema real y conviértelo en un proyecto con fecha.

    No “voy a aprender microservicios”, sino:

    “Voy a extraer este módulo a un servicio, con contrato versionado, métricas y rollback”

    No “voy a aprender n8n”, sino:

    “Voy a automatizar el alta de clientes: formulario → validación → CRM → correo → seguimiento, con reintentos y alertas”

    Checklist operativo: si mañana quieres salir del bucle de estudio

    1. Elige un tema (uno) para 2 semanas.
    2. Define un deliverable que se pueda usar (aunque sea interno).
    3. Pon una fecha de entrega corta (72h para primera versión).
    4. Estudia solo lo que desbloquea el siguiente paso.
    5. Implementa con logs, manejo de errores y documentación mínima.
    6. Pide una review o usa el sistema en un caso real.
    7. Escribe un postmortem: qué falló, qué aprendiste, qué harías distinto.
    8. Repite con scope ligeramente mayor.

    Ese ciclo crea progreso acumulativo y criterio. Y el criterio —no el consumo— es lo que te hace subir de nivel.

    Cierre: estudia como ingeniero, no como espectador

    Estás estudiando demasiado… y por eso no progresas cuando el estudio se vuelve un sustituto elegante de la ejecución. La salida no es abandonar el aprendizaje, sino reconstruirlo alrededor de entrega y feedback.

    Si te quedas con una idea: en software, lo que no pasa por implementación y mantenimiento es solo opinión informada. El progreso real empieza cuando conviertes conocimiento en decisiones y decisiones en sistemas que funcionan.

    FAQ

    ¿Por qué estudiar sin aplicar no ayuda a progresar?

    Estudiar sin aplicar genera una falsa sensación de avance. El verdadero aprendizaje proviene de tomar decisiones y enfrentar errores en situaciones reales.

    ¿Cómo saber si estoy estudiando demasiado?

    Puedes identificar que estudias demasiado si no produces artefactos, cambias constantemente de enfoque o consumes más información de la que implementas.

    ¿Qué es un sistema operativo de aprendizaje?

    Un sistema operativo de aprendizaje se basa en establecer unidades de progreso verificables y aplicar lo aprendido en plazos cortos y con feedback constante.

    ¿Por qué es importante la práctica deliberada?

    La práctica deliberada permite enfocarse en las áreas difíciles que necesitas mejorar, aumentando así tu competencia técnica.

    ¿Cómo introducir feedback real en el aprendizaje?

    Introduce feedback real mediante la revisión de código, el aprendizaje de incidentes en producción y la recopilación de métricas durante el desarrollo.