Tag: React

  • Aprende a tipar correctamente props, hooks y contextos en TypeScript y React

    Aprende a tipar correctamente props, hooks y contextos en TypeScript y React

    TypeScript + React: cómo tipar correctamente props, hooks y contextos

    Tiempo estimado de lectura: 4 min

    • Tipado explícito evita errores silenciosos: evita atajos como as any y prefiere contratos claros.
    • Props y refs: evita React.FC, usa referencias DOM con null inicial y valores mutables con valor inicial.
    • Contextos seguros: inicializa con null y expón hooks que hagan fail-fast si se usan fuera del provider.
    • Handlers y hooks: aprovecha los tipos de React (ChangeEvent, FormEvent) y deja que TS infiera cuando sea seguro.

    ¿Quieres dejar de parchear bugs con as any y que tu base de código deje de tener sorpresas en producción? Bien. Esto es lo que realmente necesitas saber sobre TypeScript + React: cómo tipar correctamente props, hooks y contextos. No es teoría. Son patrones que evitan errores silenciosos, mejoran el autocompletado y hacen que el código sea mantenible cuando el equipo crece.

    Resumen rápido (lectores con prisa)

    Tipar React con TypeScript reduce errores en producción y mejora DX. Evita React.FC, inicializa contextos con null y valida con hooks, usa refs con null para DOM y valores iniciales para mutables, y aprovecha los tipos sintéticos de eventos de React.

    Evita React.FC: tipa los parámetros explícitamente

    React.FC fue útil en tutoriales, pero introduce problemas: children implícitos, genéricos torpes y ruido. Tipar la función es más claro y explícito.

    interface ButtonProps {
      label: string;
      onClick: () => void;
      variant?: 'primary' | 'secondary';
      children?: React.ReactNode;
    }
    
    export function Button({ label, onClick, variant = 'primary', children }: ButtonProps) {
      return <button className={`btn-${variant}`} onClick={onClick}>{children ?? label}</button>;
    }
    

    React.ReactNode cubre todo lo que necesitas para children. Punto.

    useState: deja que TS infiera cuando pueda, explícito cuando haga falta

    Si el estado empieza con un primitivo, no especifiques el tipo. Si empieza vacío y luego será un objeto, usa una unión con null.

    interface User { id: string; email: string; }
    
    const [count, setCount] = useState(0);            // OK, inferido
    const [user, setUser] = useState<User | null>(null); // OK, explícito
    

    ¿Por qué? Porque evitarás tener que castear más adelante y te proteges contra undefined al acceder a propiedades.

    useRef: dos usos, dos reglas

    useRef sirve para referencias DOM y para valores mutables que no disparan re-render. Los tipos cambian según el valor inicial.

    • DOM refs: inicializa con null y maneja optional chaining.
    • Valores mutables: inicializa con el valor y muta .current.
    const inputRef = useRef<HTMLInputElement | null>(null);
    const renderCount = useRef(0);
    
    inputRef.current?.focus();
    renderCount.current += 1;
    

    No uses as para saltarte el null check. Esa falsedad te estallará en runtime.

    Eventos del DOM: tipa cada handler

    No uses any. React expone tipos sintéticos bien definidos. Úsalos y disfruta del autocompletado.

    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
      console.log(e.target.value);
    };
    
    const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
      e.preventDefault();
    };
    

    Esto evita errores tontos como leer propiedades inexistentes.

    useContext: seguro, explícito y con fail-fast

    No hagas createContext({} as ThemeContext). Ese as silencia al compilador y deja el error para producción.

    Patrón correcto: contexto con null y hook personalizado que comprueba la presencia del provider.

    interface ThemeContextType { theme: 'light'|'dark'; toggle: () => void; }
    const ThemeContext = createContext<ThemeContextType | null>(null);
    
    export function useTheme() {
      const ctx = useContext(ThemeContext);
      if (!ctx) throw new Error('useTheme debe usarse dentro de ThemeProvider');
      return ctx;
    }
    

    Fail-fast: si alguien usa el hook fuera del provider, fallas rápido y el stack trace dice dónde.

    forwardRef: firma invertida, atención al tipo genérico

    La firma de forwardRef es contraintuitiva: el primer genérico es el tipo de ref, el segundo las props.

    interface InputProps { label: string }
    
    export const CustomInput = forwardRef<HTMLInputElement, InputProps>(({ label, ...props }, ref) => (
      <label>
        {label}
        <input ref={ref} {...props} />
      </label>
    ));
    CustomInput.displayName = 'CustomInput';
    

    Siempre define displayName para facilitar debugging en React DevTools.

    Tips prácticos que cambian proyectos

    • Exporta type con export type cuando sean solo contratos. Eso deja claro que no hay runtime.
    • No uses as para “callar” al compilador. Es un atajo que se vuelve deuda.
    • Si necesitas tipos genéricos en componentes, tipa explícitamente props y evita React.FC.
    • Para APIs y carga asíncrona, combina Zod (o similar) con z.infer si necesitas validación runtime y tipos derivados.

    Checklist rápido antes de push

    • ¿Contextos inicializados con null y validados por hooks? ✔
    • ¿useRef con null para DOM y con valor inicial para mutables? ✔
    • ¿Handlers con React.ChangeEvent / FormEvent? ✔
    • ¿No hay as any salvo casos documentados? ✔

    Cierra con criterio

    Tipar React no es un ejercicio académico. Es la forma más barata de prevenir fallos en producción y mejorar el DX de tu equipo. Haz estas tres cosas hoy:

    1. Revisa contextos: elimina as y añade hooks defensivos.
    2. Estándariza useRef y useState según lo explicado.
    3. Añade displayName a los componentes con forwardRef.

    Aplica esto en tu repo. Si algo rompe después, sabrás exactamente por qué. Esto no acaba aquí. Hay más patrones (componentes polimórficos, inferencia con generics, overloads en hooks) que merecen otra nota.

    FAQ

    ¿Por qué evitar React.FC?

    Porque introduce children implícitos, dificulta genéricos y añade ruido. Tipar explícitamente los parámetros es más claro y evita sorpresas.

    ¿Cuándo especificar el tipo en useState?

    No lo especifiques si el estado inicia con un primitivo (deja que TS infiera). Si el estado inicia vacío y luego será un objeto, usa una unión con null (por ejemplo User | null).

    ¿Cómo tipar correctamente useRef para DOM?

    Inicializa la ref con null y usa el tipo del elemento: useRef<HTMLInputElement | null>(null). Accede con optional chaining (inputRef.current?.focus()).

    ¿Qué hacer si alguien usa un context fuera del provider?

    Exponer un hook que haga fail-fast: si el contexto es null, lanzar un error claro (por ejemplo throw new Error('useTheme debe usarse dentro de ThemeProvider')).

    ¿Es aceptable usar as en alguna situación?

    Evita as salvo casos documentados y justificables. Usarlo para “callar” al compilador oculta problemas que aparecerán en runtime.

    ¿Cómo mejorar la validación de APIs y mantener tipos?

    Combina validación runtime con librerías como Zod y usa z.infer para derivar tipos TypeScript a partir de los esquemas de validación.

  • Soluciones efectivas para props drilling en React

    Soluciones efectivas para props drilling en React

    ¿Qué es el props drilling en React? Guía de arquitectura y soluciones

    Tiempo estimado de lectura: 4 min

    • Props drilling es pasar props a través de varios niveles que no las usan.
    • Opciones: composición, Context API o stores externos según alcance y frecuencia de cambio.
    • Decisión técnica: privilegia composición; Context para valores estables; store para coordinación entre subsistemas.

    Resumen rápido (lectores con prisa)

    Props drilling: pasar datos por componentes intermedios que no los usan. Si atraviesa pocos niveles (<3) suele estar bien; si escala, considerar composición, Context o un store externo. Elige según alcance, frecuencia de cambio y acoplamiento.

    ¿Qué es el props drilling en React?

    El props drilling en React es cuando pasas propiedades a través de múltiples niveles de componentes que no las usan, solo las retransmiten hasta el componente que sí las necesita.

    Ejemplo visual:

    App (tiene user)
    └─ Layout
    └─ Sidebar
    └─ Menu
    └─ UserProfile (usa user)

    Código mínimo:

    function App() {
      const user = { name: "Ana", avatar: "/ana.jpg" };
      return <Layout user={user} />;
    }
    
    function Layout({ user }) { return <Sidebar user={user} />; }
    function Sidebar({ user }) { return <UserProfile user={user} />; }
    function UserProfile({ user }) { return <img src={user.avatar} alt={user.name} />; }

    Layout y Sidebar no necesitan user. Solo lo llevan. Eso genera acoplamiento y ruido en el código.

    ¿Cuándo es un problema real?

    No todo props que viaja es pecado. Pasar props uno o dos niveles es totalmente aceptable. El problema aparece cuando:

    • La propiedad atraviesa tres o más niveles.
    • Múltiples props no relacionadas llenan la firma de componentes intermedios.
    • Mover un componente exige recablear docenas de firmas.
    • Los re-renders se disparan y el rendimiento cae.

    Consecuencias prácticas: acoplamiento innecesario, refactors costosos, más tests y mayor probabilidad de bugs al cambiar la forma del dato.

    Soluciones (con criterio técnico)

    No hay varita mágica. Hay herramientas y criterios para escoger la correcta.

    1) Composición de componentes (cuando aplica)

    Primera regla: intenta composición antes que librerías. Si el componente final vive en un subárbol que puedes construir desde el padre que tiene el dato, inyecta el subárbol.

    function App() {
      const user = { name: "Ana", avatar: "/ana.jpg" };
      return (
        <Layout sidebar=&{``} />
      );
    }

    Ventaja: cero dependencias, cero props intermedios. Referencia: docs de React sobre composición

    Cuándo usar: datos locales a una sección, poco compartidos fuera del subárbol.

    2) Context API (cuando el dato es global y estable)

    Context te permite proveer un valor desde arriba y consumirlo en cualquier punto del árbol, sin pasar props intermedios.

    const UserContext = React.createContext(null);
    
    function App() {
      const user = { name: "Ana" };
      return <UserContext.Provider value={user}><Layout /></UserContext.Provider>;
    }
    
    function UserProfile() {
      const user = useContext(UserContext);
      return <span>{user.name}</span>;
    }

    Docs oficiales: React Context

    Advertencia: Context es ideal para valores que cambian poco (tema, idioma, sesión). Si el valor cambia con alta frecuencia, todos los consumidores se re-renderizan y el rendimiento puede sufrir.

    3) Estado global / stores (cuando la app escala)

    Cuando múltiples partes desconectadas del árbol necesitan leer y escribir el mismo estado, un store fuera del árbol es la opción práctica.

    Opciones razonables hoy:

    • Zustand: simple, sin boilerplate, buen rendimiento.
    • Redux Toolkit: trazabilidad y patterns para apps enterprise.
    • Jotai/Recoil: atom-based state para control fino de re-renders.

    No uses un store global por moda. Úsalo cuando la composición y Context se queden cortos.

    Cómo decidir (lista rápida)

    Hazte estas preguntas antes de refactorizar:

    1. ¿Cuántos niveles atraviesa la prop? (<3 → probablemente ok)
    2. ¿Los componentes intermedios la usan? (si sí, deja el flujo)
    3. ¿El dato cambia con frecuencia? (si sí, evita Context)
    4. ¿Se comparte entre partes no relacionadas de la UI? (si sí, considera un store)

    Si la respuesta apunta a complejidad real, planifica: migración por fases, pruebas y medición de re-renders.

    Buenas prácticas finales

    • Mantén el estado lo más cerca posible del lugar donde se usa.
    • Prefiere composición cuando sea viable.
    • Usa Context para datos estables.
    • Reserva stores externos para coordinación entre subsistemas.
    • Evita micro-optimizaciones prematuras: primero estructura, luego perf.

    Referencias útiles

    Esto no acaba aquí: en el siguiente post veremos cómo migrar un árbol con props drilling a Zustand paso a paso, sin romper la app ni a los desarrolladores. Suscríbete al boletín de Dominicode para recibir la guía y los snippets listos para copiar.

    FAQ

    ¿Qué es exactamente el props drilling?

    Es el patrón donde pasas propiedades desde un componente superior hasta uno profundo, atravesando componentes intermedios que no las usan. Genera firmas de props infladas y acoplamiento innecesario.

    ¿Cuándo puedo ignorarlo?

    Cuando la prop atraviesa uno o dos niveles y no complica el mantenimiento. No todo pasaje de props requiere refactor.

    ¿Cuándo usar Context en lugar de un store?

    Usa Context para valores globales y estables (tema, idioma, sesión). Evita Context para datos que cambian con alta frecuencia o requieren escrituras concurrentes desde múltiples partes.

    ¿La composición siempre es la mejor opción?

    No siempre, pero es la primera estrategia a intentar: sin dependencias y con menor acoplamiento cuando puedes construir el subárbol desde el padre que tiene el dato.

    ¿Qué problemas de rendimiento trae Context?

    Si el valor del Provider cambia con frecuencia, todos los consumidores se re-renderizan, lo que puede impactar el rendimiento. Se puede mitigar con memos, splitting de contexts o stores que controlen re-renders finos.

    ¿Qué store elegir si la app escala?

    Depende: Zustand para simplicidad y rendimiento, Redux Toolkit para trazabilidad en enterprise, y Jotai/Recoil para control fino de re-renders.

  • Implementa autenticación sencilla en Next.js con NextAuth.js

    Implementa autenticación sencilla en Next.js con NextAuth.js

    NextAuth.js / Auth.js: deja de reinventar la autenticación

    Tiempo estimado de lectura: 6 min

    Ideas clave

    • NextAuth.js (y la evolución a Auth.js v5) evita errores comunes en sesiones, cookies y tokens.
    • Dos estrategias de sesiones: JWT (performance) vs sesiones en base de datos (revocación instantánea).
    • Evita gestionar credenciales si no dominas hashing, salting y políticas de seguridad.
    • Si usas Next.js 14+, técnica y pragmáticamente conviene empezar con Auth.js (v5).

    Tabla de contenidos

    ¿Cansado de perder semanas armando autenticación como si fuera magia negra? Eso se acaba aquí.
    NextAuth.js no es solo otra librería bonita. Es el pegamento que evita que tu aplicación se desangre en sesiones, cookies y tokens. Es la forma razonable de decir: “No voy a reinventar esto” y, aun así, tener control total.

    Resumen rápido (lectores con prisa)

    NextAuth.js es una solución de autenticación open source diseñada para Next.js y arquitecturas Serverless. Úsala para gestionar sesiones, providers OAuth y passwordless sin construir todo desde cero. Importa porque abstrae validación en peticiones y reduce errores de cookies/tokens. Funciona con JWT o sesiones en base de datos según tus necesidades de rendimiento y revocación.

    Qué es, en pocas palabras

    NextAuth.js es una solución de autenticación open source creada para Next.js. Funciona bien con Serverless, Edge y App Router. No pretende sustituir a un proveedor de identidad empresarial, pero te da una capa de seguridad sólida, lista para producción, sin pagar licencia.
    Es como poner una cerradura profesional en la puerta de tu app sin contratar a un cerrajero. Es segura por defecto. Y flexible cuando lo necesitas.

    Por qué importa en el mundo moderno (y no es marketing)

    Next.js con App Router difumina la línea entre servidor y cliente. Ahora puedes ejecutar lógica en el servidor sin servidor fijo. Eso complica cookies, tokens y el control de sesiones. Si lo haces mal, expones rutas, filtrás datos y te conviertes en el peor tipo de soporte: el que responde “está en producción, no puedo tocarlo”.
    NextAuth.js abstrae eso. Valida en el momento de la petición, ideal para funciones Serverless o runtimes Edge. No necesitas un proceso que viva todo el tiempo. Ahorras costes. Escalas mejor. Y, sobre todo, reduces la superficie de errores humanos.

    Características que realmente importan

    No voy a pintarte una lista vacía. Esto es lo importante:
    • Soporte multi-proveedor (OAuth): conecta Google, GitHub, Apple, Discord, lo que sea. No reinventes OAuth.
    • Magic Links (passwordless): menos fricción, menos contraseñas que filtrar.
    • Adapters para DB y ORMs: Prisma, Drizzle, Mongo, Supabase. Tú eliges dónde almacenarlos.
    • Seguridad por defecto: CSRF, firma de cookies, tokens cifrados (JWE) —activados sin tener que leer 200 páginas.
    • Edge-ready: puedes validar sesiones casi al instante si lo ejecutas cerca del usuario.
    No es perfecto, pero es un buen comienzo. Y un comienzo con sentido.

    El dilema clásico: manejar credenciales propias

    Puedes usar el proveedor de “credentials” y manejar usuarios/contraseñas tú mismo. Suena bien hasta que lees la letra pequeña: gestionar contraseñas implica responsabilidad. Hashing, salting, políticas, reset de contraseñas, protección frente a ataques por fuerza bruta.
    NextAuth.js permite credenciales, pero la recomendación oficial y práctica es evitarlo si no sabes exactamente lo que haces. Además, si usas ese proveedor, NextAuth.js fuerza sesiones en JWT y desactiva sesiones en BD. No es capricho: es una decisión para reducir malas prácticas.

    Arquitectura de sesiones: dos caminos y una decisión

    Aquí viene la parte que separa proyectos tranquilos de proyectos que arden. Hay dos estrategias principales:

    1) JWT (por defecto)

    La información viaja en la cookie, cifrada. Rápido. No consultas la DB en cada petición. Ideal para apps con mucho tráfico. Problema: invalidar sesiones inmediatamente es más complejo. Si quieres expulsar a alguien ya, el token sigue válido hasta que expire.

    2) Sesiones en base de datos

    La cookie guarda solo un ID. La verificación exige una consulta a la base de datos. Perfecto si necesitas cortar accesos en tiempo real (ej. revocar a un usuario). Coste: latencia adicional por cada validación.
    No hay una respuesta universal. Si tu app es una red social con millones de peticiones, JWT suele ser la opción. Si es un panel de control bancario donde cortar accesos rápido es crítico, la base de datos gana.

    La transición que incomoda: NextAuth.js v4 → Auth.js v5

    Esto tiene que quedar claro: el ecosistema está en movimiento. NextAuth.js está evolucionando hacia Auth.js (v5). ¿Por qué? Porque no quieren quedarse atados solo a Next.js. Quieren que funcione también en otros frameworks: SvelteKit, SolidStart, Express…
    ¿Qué implica esto para ti?
    • Nueva API unificada. Más poderosa, mejor integración con Server Actions y App Router.
    • Documentación en proceso. La v5 ha estado en beta y la documentación todavía se alinea.
    • Si arrancas un proyecto con Next.js 14 o superior: técnica y pragmáticamente, empieza con v5. Evitarás dolores de migración.
    Sí, duelen las beta-docs. Pero es mejor migrar ahora que rehacerlo todo después.

    Un ejemplo real y útil (sin poesía)

    Piensa en un componente servidor que solo debe ver el usuario autenticado. Con Auth.js/NextAuth puedes validar en el servidor antes de renderizar:
    import { auth } from "@/auth"
    import { redirect } from "next/navigation"
    
    export default async function Dashboard() {
      const session = await auth()
      if (!session) redirect("/login")
      return <h1>Panel seguro de {session.user.name}</h1>
    }
    Eso no es ciencia ficción. Es práctica común. Y evita que la UI se entere antes que el servidor. La seguridad no depende del cliente.

    El dev escéptico y su evolución

    Conozco al dev escéptico. Le encanta escribir autenticaciones a mano. Cree que es más “limpio”. Tres meses después, está leyendo logs a las 3 a.m. por un problema de sesiones. Cambió. Aprendió. No renunció a la personalización: NextAuth.js ofrece callbacks, eventos y handlers. Puedes seguir siendo “muy tuyo” sin pagar el precio de hacerlo todo desde cero.

    Cuando no deberías usar NextAuth.js

    No todo es para todos. Elige otra cosa si:
    • Necesitas SAML/SSO empresarial complejo con flujos B2B avanzados.
    • Quieres una consola de usuarios y control RBAC administrada por un tercero (Clerk, Auth0 lo hacen).
    • Tu equipo no quiere tocar la infraestructura de usuarios y prefiere externalizarlo.
    Si la respuesta es “no me complico la vida” y estás dispuesto a pagar por eso, un SaaS puede ser mejor.

    Migración y estrategia práctica

    Si ya tienes autenticación casera, ¿migrar? Hazlo por fases:
    • Analiza qué needs tienes: invalidación en tiempo real, multi-provider, passwordless.
    • Implementa NextAuth.js en modo JWT en staging. Observa.
    • Si necesitas revocar sesiones pronto, añade el adapter y cambia a sesiones en DB.
    • Aprovecha callbacks para mapear datos de usuario sin romper tus modelos.
    No es tan doloroso. Pero exige disciplina.

    Pequeñas trampas que nadie te cuenta

    • No uses credentials si no entiendes hashing y políticas de seguridad.
    • Validar en cliente es mala idea. Siempre valida en servidor.
    • Documentación: si empiezas con v5, sigue la docs oficiales y ejemplos; ignora tutoriales viejos de v4.
    • Cookies seguras: en producción siempre en https y withSameSite apropiado.

    Metáfora útil (porque la mente recuerda imágenes)

    NextAuth.js es el portero de tu club. No decide a quién gustas. Solo asegura que el que entra esté autorizado. El portero no es el dueño del club, pero conoce la lista, sabe cuándo pedir identificación y tiene la potestad de sacar a cualquiera que cause problemas.

    Urgencia realista

    Si estás empezando un proyecto con Next.js 14+:
    • Empieza con Auth.js (v5).
    • Si pospones, la migración será más cara dentro de 6-12 meses.
    • No por FOMO, sino por coherencia técnica.

    CTA simple y sin vueltas

    ¿Quieres un setup funcional en 10 minutos? Haz esto: instala next-auth o auth y sigue la guía oficial para tu provider preferido. ¿Quieres que te lo arme paso a paso? Respóndeme este mensaje y te doy una checklist práctica y un ejemplo listo para copiar y pegar.

    Esto no acaba aquí

    Autenticación es una zona de fricción constante. Cambian las APIs, aparecen runtimes Edge, el usuario espera menos fricción y más seguridad. NextAuth.js te da una base sólida. Pero ninguna herramienta es el final de tu trabajo. Es la primera línea del plan.
    Si quieres, la próxima entrega la dedicamos a:
    • Migración completa v4 → v5.
    • Ejemplos avanzados con adapters (Prisma, Supabase).
    • Estrategias para invalidación instantánea y refresh tokens.
    ¿Quieres que sigamos? Respóndeme. No es una promesa vacía: es la continuación que necesitas para no aprender las cosas a las malas.

    FAQ

    ¿Qué es NextAuth.js / Auth.js?

    NextAuth.js es una solución de autenticación open source creada para Next.js; Auth.js (v5) es la evolución que amplía soporte a otros frameworks. Proporcionan manejo de sesiones, providers OAuth, magic links y adapters para DB/ORMs.

    ¿Cuándo usar JWT vs sesiones en BD?

    Usa JWT si priorizas performance y quieres evitar consultas a la DB por petición. Usa sesiones en BD si necesitas revocar accesos en tiempo real y tener control inmediato sobre sesiones.

    ¿Puedo manejar credenciales con NextAuth.js?

    Sí, existe el proveedor de “credentials”, pero no es recomendado si no controlas hashing, salting, políticas y protección frente a fuerza bruta. Además, usar credentials suele forzar sesiones en JWT y desactivar sesiones en BD.

    ¿Auth.js v5 es estable para nuevos proyectos?

    La v5 ha estado en beta y la documentación aún se alinea. Sin embargo, si arrancas con Next.js 14+, técnica y pragmáticamente conviene empezar con v5 para evitar migraciones futuras.

    ¿Cómo validar sesiones en servidor con App Router?

    Puedes llamar a la utilidad de autenticación en componentes servidor antes de renderizar. Ejemplo: llamar a auth() y redirigir si no hay sesión para evitar exponer rutas desde el cliente.

    ¿Qué proveedores soporta?

    Soporta múltiples providers OAuth como Google, GitHub, Apple, Discord, además de magic links y adapters para Prisma, Drizzle, Mongo, Supabase, entre otros.

    ¿Qué debo evitar al implementar autenticación?

    No uses credenciales si no entiendes seguridad de contraseñas. No confíes en validación en cliente. Asegura cookies en producción (https, SameSite). Sigue la documentación actual de la versión que eliges y evita tutoriales desactualizados.
  • Enviar correos transaccionales con Resend en React y NestJS

    Enviar correos transaccionales con Resend en React y NestJS

    Cómo usar Resend en React y NestJS

    Tiempo estimado de lectura: 4 min

    • Mantén consistencia visual entre web y correo usando plantillas React Email.
    • Protege claves renderizando en el servidor (NestJS) y guardando API keys en variables de entorno.
    • Escala correctamente con colas (BullMQ/Redis) para evitar bloquear peticiones.

    Cómo usar Resend en React y NestJS para enviar correos transaccionales sin sangrar tiempo en HTML quebrado ni exponer claves. Esta guía práctica muestra plantillas en React, render en servidor (NestJS) y entrega con Resend.

    Resumen rápido (lectores con prisa)

    Qué es: patrón para generar y enviar emails transaccionales usando plantillas React Email, render en NestJS y entrega vía Resend.

    Cuándo usarlo: cuando quieres consistencia visual entre web y email y no exponer claves en frontend.

    Por qué importa: reduce deuda técnica, mejora DX y entregabilidad al separar render y envío.

    Cómo funciona: escribe plantillas React, renderízalas en servidor con @react-email/render y envía con la API de Resend; procesa con colas para escalar.

    Cómo usar Resend en React y NestJS: flujo y por qué importa

    No es solo “mandar un email”. Es:

    • mantener consistencia visual entre web y correo,
    • no exponer claves,
    • evitar render duplicado,
    • y escalar sin convertir cada registro en un bloqueo HTTP.

    La solución: escribir plantillas con React Email, renderizarlas en NestJS usando @react-email/render y llamar a Resend para la entrega. Docs oficiales: Resend, React Email, NestJS.

    1) Plantilla en React (React Email)

    Instala dependencias en tu monorepo o carpeta compartida:

    npm install @react-email/components @react-email/render
    npm install -D react @types/react

    Ejemplo mínimo: src/emails/WelcomeEmail.tsx

    import * as React from 'react';
    import { Html, Body, Container, Text, Button } from '@react-email/components';
    
    export function WelcomeEmail({ name, url }: { name: string; url: string }) {
      return (
          
            
              Hola, {name}
              Verifica tu cuenta para empezar a usar la plataforma.
              
            
          
        
      );
    }

    Ventaja: el componente es testable, reutilizable y legible. React Email genera HTML compatible con clientes antiguos.

    2) Render y envío en NestJS

    Instala el SDK de Resend:

    npm install resend

    email.service.ts (esqueleto)

    import { Injectable, Logger } from '@nestjs/common';
    import { ConfigService } from '@nestjs/config';
    import { Resend } from 'resend';
    import { render } from '@react-email/render';
    import { WelcomeEmail } from '../emails/WelcomeEmail';
    
    @Injectable()
    export class EmailService {
      private resend: Resend;
      private logger = new Logger(EmailService.name);
    
      constructor(private config: ConfigService) {
        this.resend = new Resend(this.config.get('RESEND_API_KEY'));
      }
    
      async sendWelcome(to: string, name: string, verificationUrl: string) {
        const html = render(WelcomeEmail({ name, url: verificationUrl }));
        const res = await this.resend.emails.send({
          from: 'TuApp <noreply@tu-dominio.com>',
          to: [to],
          subject: `Bienvenido ${name}`,
          html,
        });
        this.logger.log(`Enviado: ${res.data.id}`);
        return res;
      }
    }

    Puntos clave:

    • La API key vive en variables de entorno. Nunca en frontend.
    • render() convierte JSX a HTML listo para enviar.
    • Usa ConfigService para separar entornos.

    Referencia de la API de envío: Referencia de la API de envío

    3) No bloquees peticiones: usa colas

    Enviar emails sin cola = romper UX y escalar mal. Usa BullMQ/Redis:

    • BullMQ docs
    • Patrón: controlador crea job -> responde 202 -> worker procesa job (llama a EmailService)

    Beneficios:

    • reintentos automáticos,
    • backpressure controlada,
    • workers horizontales.

    4) Producción: dominios, entregabilidad y observabilidad

    Configura DKIM, SPF y DMARC. Resend te da valores concretos durante la verificación. Enlaces útiles:

    Ejemplo mínimo SPF/DKIM

    • TXT @ v=spf1 include:resend.com ~all
    • Registros DKIM proporcionados por Resend
    • TXT _dmarc “v=DMARC1; p=quarantine; rua=mailto:postmaster@tu-dominio.com”

    Añade headers o tags en los envíos para trazar campañas o templates. Resend Dashboard permite ver bounces, opens y eventos.

    5) Buenas prácticas y decisiones técnicas

    • Reutiliza componentes visuales entre web y email cuando tenga sentido. No todo componente de UI es apto para email: usa @react-email/components para compatibilidad.
    • Mantén plantillas en una carpeta compartida o paquete npm interno (monorepo).
    • En entornos dev, whitelistea destinatarios para no spamear usuarios reales.
    • Telemetría: registra message-id, template tag y userId en logs para debug.
    • Si no usas React en tu stack, no añadas React Email solo por moda. El coste de la dependencia debe justificarse.

    Conclusión rápida

    Usar Resend en React y NestJS no es una moda: es un patrón que reduce deuda, mejora DX y facilita la entregabilidad. Resumen práctico:

    1. escribe plantillas con React Email;
    2. renderiza en NestJS con @react-email/render;
    3. envía con Resend y procesa con colas (BullMQ) en producción;
    4. verifica dominio y monitoriza.

    Si quieres, te dejo un ejemplo con BullMQ integrado y un pipeline de observabilidad (logs + Sentry + Resend tags) listo para copiar y pegar. Esto no acaba aquí.

    FAQ

    Respuesta: Renderizar en servidor evita exponer claves en frontend, asegura HTML consistente y permite centralizar lógica de plantillas. Además facilita pruebas y control de versiones.

    Respuesta: En variables de entorno del servidor o servicio de secretos. Nunca en el cliente ni en repositorios públicos.

    Respuesta: Para no bloquear peticiones HTTP, manejar reintentos, control de backpressure y escalar workers horizontalmente.

    Respuesta: Sí. React Email está diseñado para generar HTML compatible con clientes antiguos y simplificar estilos inline.

    Respuesta: Configura SPF, DKIM y DMARC. Ejemplo mínimo: TXT @ v=spf1 include:resend.com ~all, registros DKIM proporcionados por Resend y un registro DMARC como TXT _dmarc "v=DMARC1; p=quarantine; rua=mailto:postmaster@tu-dominio.com".

    Respuesta: Resend Dashboard muestra bounces, opens y eventos. Además, añade tags/headers en los envíos para integrar con logs y sistemas de observabilidad.

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

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

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

    Tiempo estimado de lectura: 4 min

    Ideas clave

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

    Introducción

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

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

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

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

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

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

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

    Server Actions vs APIs REST — visión general

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

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

    Cómo funcionan, en dos líneas

    Server Action

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

    Route Handler (API REST)

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

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

    Ejemplo: crear un post (Route Handler)

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

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

    Frontend (cliente):

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

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

    Ejemplo: crear un post (Server Action)

    Acción (app/actions.ts):

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

    Frontend:

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