Category: Blog

Your blog category

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

  • Por qué saber programar ya no es suficiente (y qué habilidades marcan la diferencia en 2026)

    Por qué saber programar ya no es suficiente (y qué habilidades marcan la diferencia en 2026)

    Tiempo estimado de lectura: 4 min

    • La sintaxis es commodity: IA generativa y plataformas low-code generan código; el valor real es orquestación y criterio.
    • Skills diferenciales: orquestación/automatización, product engineering, traducción Human↔AI y arquitectura/auditoría de IA.
    • Mide impacto: Priorizar por ROI y pruebas rápidas separa al ejecutor del decisor.

    Por qué saber programar ya no es suficiente (y qué habilidades marcan la diferencia en 2026): esa pregunta deja clara la verdad incómoda —escribir código ya es el ticket de entrada; el valor real viene después. En las primeras líneas: si tu aporte se reduce a traducir requisitos a líneas de código, tu rol es reemplazable por plantillas, Copilot o un workflow bien orquestado.

    Este artículo explica qué ha cambiado, qué habilidades concretas reclaman las empresas y cómo moverte desde “coder” a “arquitecto de soluciones” con ejemplos prácticos y recursos.

    Resumen rápido (lectores con prisa)

    Programar sigue siendo necesario, pero la generación automática de código convierte la sintaxis en commodity. Aprende a orquestar sistemas, validar hipótesis de producto, escribir especificaciones para LLMs y auditar resultados. Prioriza ROI con experimentos rápidos y mide impacto con KPIs claros.

    Por qué saber programar ya no es suficiente (y qué habilidades marcan la diferencia en 2026)

    La combinación de IA generativa, plataformas low-code y servicios cloud ha convertido la sintaxis en commodity. Herramientas como GitHub Copilot docs o modelos de LLM generan funciones, pruebas y componentes en segundos (docs: GitHub Copilot docs, OpenAI docs). Eso no elimina la necesidad de programar; la sitúa en un nuevo plano: auditoría, orquestación y decisión de producto.

    El mercado paga hoy por impacto, no por LOC. Eso redibuja el skill‑set: automatización fiable, product thinking, comunicación efectiva y arquitectura sistémica.

    Habilidad 1 — Orquestación y automatización (n8n, agentes, APIs)

    Qué significa: saber diseñar pipelines que conectan sistemas (CRM, DB, LLMs, notificaciones) y manejar errores, retries y observabilidad.

    • Herramienta práctica: n8n.
    • Ejemplo real: workflow de triage de leads —webhook → clasificación LLM → enrich con DB de clientes → crear ticket en CRM o crear borrador de email. Resultado: de horas a minutos en respuesta y mayor tasa de conversión.

    Lo diferencial no es arrastrar nodos; es entender idempotencia, backpressure, dead-letter queues y cómo insertar un human‑in‑the‑loop seguro cuando la IA toma decisiones. Un dev que monta este flujo, instrumenta métricas y define SLAs aporta más valor que quien codifica endpoints manualmente.

    Habilidad 2 — Product Engineering: medir impacto antes de escribir código

    El Product Engineer no pregunta “¿cómo lo hago?” sino “¿por qué lo hacemos?”. Antes de diseñar una feature se plantea hipótesis, métricas y tamaño mínimo viable.

    • Métricas prácticas: time‑to‑value, reducción de churn, TTM (time to market).
    • Técnica: validar con experimentos rápidos (MVP + automatización) antes de invertir en arquitectura compleja.

    Si una automatización en n8n reduce un proceso manual en 70% y prueba el caso de negocio, es preferible a un mes de engineering en un monolito. Saber priorizar por ROI separa al ejecutor del decisor.

    Habilidad 3 — Comunicación y “Human↔AI” Translation

    Dos frentes:

    1. Comunicar a humanos

    Traducir impacto técnico a negocio. No digas “optimizamos la query”; di “esto reduce latencia de checkout y evita pérdidas de ventas X%”.

    2. Comunicar a máquinas

    Ingeniería de contexto (prompts), esquemas de entrada/salida y validación de respuestas. Un prompt preciso es una especificación técnica.

    Recursos: guía de prompts y formatos de output en OpenAI chat guide. La habilidad consiste en escribir la especificación que un LLM pueda ejecutar sin introducir riesgo.

    Habilidad 4 — Arquitectura sistémica y auditoría de IA

    Cuando la IA genera código, los errores son sistémicos: alucinaciones, fallos de seguridad y decisiones de diseño inapropiadas.

    • Necesitas: patrones de diseño distribuido, observabilidad, testing de integración y threat modeling.
    • Tarea diaria: auditar código IA, escribir tests que prueben invariantes y monitorizar anomalias post‑deploy.

    Ejemplo: si un agente automatiza pagos, un fallo lógico puede significar pérdidas monetarias. La auditoría y las pruebas de contratos (contract testing) son no negociables.

    Roadmap práctico (6–12 meses)

    1. Aprende n8n: crea 5 workflows reales (webhooks, API auth, error handling). Recursos: n8n docs.
    2. Practica Product Thinking: lidera 2 experimentos MVP con métricas claras (A/B o cohortes).
    3. Mejora prompts y validación: crea specs para LLMs, exige output JSON y tests automáticos. OpenAI guide: OpenAI guides.
    4. Refuerza arquitectura: estudia patterns de sistemas distribuidos, observability y CI/CD. Implementa logging/tracing en tus flujos.
    5. Mide impacto: instrumenta KPIs (reduction in manual hours, conversion uplift, MTTR).

    Mide resultados: si automatizas una tarea crítica, cuantifica tiempo recuperado y efecto en ingresos o churn. Eso es lo que vende tu perfil.

    Conclusión

    Saber programar sigue siendo necesario, pero ya no suficiente. En 2026, el diferencial lo dan las personas que conectan sistemas, validan hipótesis de producto, escriben especificaciones que las IAs pueden ejecutar y auditan lo que las máquinas producen. La IA amplifica el criterio; el que lo tenga, manda la agenda técnica. Aprende a orquestar, medir impacto y auditar: eso convierte velocidad en ventaja competitiva.

    Para equipos y líderes que trabajan en automatización, workflows y agentes, una referencia práctica y ejercicios aplicados están disponibles en Dominicode Labs. Revisa esos recursos como continuación lógica para implementar pipelines, métricas y auditoría.

    FAQ

    ¿Saber programar dejará de ser necesario?

    Saber programar seguirá siendo necesario como habilidad base, pero dejará de ser el factor diferenciador principal. El mercado valora la capacidad de orquestar sistemas, medir impacto y auditar resultados.

    ¿Qué es orquestación en este contexto?

    Diseñar pipelines que conectan servicios (APIs, DBs, LLMs, CRMs), manejar errores/retries, asegurar idempotencia y observabilidad. No es solo conectar nodos, es gobernar el flujo y sus garantías.

    ¿Cómo empiezo con n8n?

    Crea workflows simples con webhooks y autenticación de API, añade manejo de errores y monitoreo. La documentación oficial (n8n docs) incluye ejemplos prácticos.

    ¿Qué métricas debo priorizar?

    Time‑to‑value, reducción de tareas manuales, conversión/retención (uplift), TTM y MTTR son métricas prácticas que conectan trabajo técnico con impacto de negocio.

    ¿Cómo auditar código generado por IA?

    Implementa tests que verifiquen invariantes, revisiones humanas focales, contract testing y monitoreo post‑deploy para detectar anomalías y fallos lógicos.

    ¿Qué es Product Engineering?

    Una disciplina que prioriza hipótesis de negocio, experimentación rápida y métricas antes de comprometerse con implementaciones costosas. Busca maximizar ROI por esfuerzo técnico.

    ¿Cómo mejorar mis prompts?

    Define esquemas de entrada/salida claros, exige formatos estructurados (por ejemplo JSON), añade validaciones automáticas y trata al prompt como una especificación técnica. Consulta guías oficiales como OpenAI chat guide.

  • Cómo uso IA en mi día a día como desarrollador (casos reales, no demos)

    Cómo uso IA en mi día a día como desarrollador (casos reales, no demos)

     

    Tiempo estimado de lectura: 4 min

    • La IA amplifica el juicio técnico: la uso para eliminar fricción repetitiva, acelerar tareas mecanizadas y validar hipótesis rápidas.
    • Patrones prácticos en producción: boilerplate, regex, tests, análisis de logs, OpenAPI y comunicación profesional.
    • Reglas claras: revisar siempre, no delegar diseño crítico ni lógica sensible, anonimizar datos.
    • Prompts y plantillas: prompts replicables y reglas de seguridad integradas en el flujo.

    Cómo uso IA en mi día a día como desarrollador (casos reales, no demos): la IA no sustituye el juicio técnico, lo amplifica. La uso para eliminar fricción repetitiva, acelerar tareas mecanizadas y validar hipótesis rápidas. Herramientas como GitHub Copilot, ChatGPT y Claude entran en mi flujo como asistentes fiables —con límites claros—, no como autoridades.

    Aquí tienes casos prácticos que realmente uso en producción, prompts replicables y reglas de seguridad. Nada de demos bonitas: solo patrones operativos que aumentan mi productividad sin comprometer calidad.

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

    Qué es: Uso de modelos de lenguaje e asistentes para tareas repetitivas, generación de boilerplate, tests y diagnóstico.

    Cuándo usarlo: Para acelerar trabajos mecanizados, generar ejemplos y validar hipótesis, no para decisiones críticas sin revisión.

    Por qué importa: Reduce fricción y libera tiempo cognitivo para problemas complejos.

    Cómo funciona: Integración de prompts claros en el flujo de trabajo, revisión humana y reglas de seguridad para evitar riesgos.

    Cómo uso IA en mi día a día como desarrollador: casos prácticos

    1) Boilerplate y scaffolding — arrancar rápido

    Problema: perder 10–20 minutos creando estructura y convenciones.

    Uso: pido un esqueleto preciso y lo integro.

    “Genera un componente React TypeScript llamado UserProfileCard. Props: { name: string; avatarUrl?: string; role?: string }. Usa Tailwind, incluye estado loading, y exporta tests básicos con Vitest.”

    Resultado: estructura, imports, interfaces y tests base. Mi trabajo: ajustar lógica y añadir casos de negocio.

    Recursos: GitHub Copilot y Vitest.

    2) Regex y transformaciones textuales — evitar prueba/error

    Problema: Regexs que nadie recuerda y que rompen luego.

    Uso: solicito patrón + explicación comentada.

    “Crea en JavaScript una regex para validar NIF español y explica cada grupo en un comentario.”

    Resultado: patrón correcto y explicación lista para documentación inline. Mantiene el código legible para el equipo.

    3) Tests automáticos y datos de prueba — cobertura base en minutos

    Problema: tests tediosos retrasan despliegues.

    Uso: la IA genera suites de tests y mocks realistas.

    “Genera tests Vitest para esta función calculateDiscount(items, total). Incluye casos límite, entradas inválidas y mocks de fetch.”

    Resultado: 10–20 tests que ejecuto y refino. Los tests sirven de base; siempre añado casos de negocio y comportamiento esperable.

    4) Análisis de logs y debugging — hipótesis rápidas

    Problema: logs inmensos y stack traces crípticos.

    Uso: pego fragmentos (sin datos sensibles) y pido diagnóstico por probabilidad.

    “Analiza este stack trace de Node y sugiere las cinco causas más probables y pasos para reproducirlo localmente.”

    Resultado: hipótesis priorizadas que me ahorran tiempo de búsqueda. Comienzo por las causas más probables y reproduzco localmente.

    Herramientas útiles: Node-postgres.

    5) Documentación y OpenAPI — sincronizar código con contratos

    Problema: documentación desactualizada.

    Uso: genero especificación OpenAPI a partir de controladores y tipos TypeScript.

    “Genera OpenAPI 3.0 YAML para estos controladores Express. Incluye schemas basados en los tipos TS y ejemplos de request/response.”

    Resultado: YAML que rechazo/ajusto y enlazo a Swagger UI. Acelera el mantenimiento del contrato API.

    Referencia: OpenAPI spec.

    6) Comunicación profesional — claridad sin ruido

    Problema: explicar retrasos o problemas técnicos sin sonar técnico o evasivo.

    Uso: pido redacciones con tono y objetivos claros (cliente, PM, equipo).

    “Redacta un Slack para stakeholders explicando que la integración con proveedor X se retrasará 5 días por cambios no documentados. Ofrece mitigaciones y nuevo cronograma.”

    Resultado: mensaje que prioriza soluciones y expectativas, ahorrando follow-ups.

    Reglas y límites (criterio profesional)

    • Nunca confiar ciegamente: todo código generado se revisa, se prueba y se audita. Tratar la salida como borrador de un desarrollador junior.
    • No para diseño arquitectural crítico: no pido a la IA que diseñe microservicios, esquemas de DB o contratos transaccionales complejos.
    • No para lógica sensible: jamás autopublico código que gestione pagos, criptografía o permisos sin revisión humana exhaustiva.
    • Protege datos: anonimiza logs y no pegues PII ni claves en prompts.

    Plantillas de prompt rápidas (copia y usa)

    • Boilerplate front: “Genera componente [framework] con TS: [props], [estilos], incluye tests base.”
    • Test suite: “Genera suite [framework] para esta función: cubre éxito, edge cases, errores.”
    • Logs: “Analiza este log/stacktrace (anonimizado). Enumera causas por prioridad y pasos de reproducción.”
    • Docs: “Genera OpenAPI 3.0 para estos endpoints Express basados en estos tipos TS.”

    Conclusión: IA como multiplicador de foco, no sustituto

    La IA en mi día a día reduce tareas mecánicas y acelera diagnóstico. El ahorro de tiempo es real: más horas de diseño, menos de formateo de texto o regex. Pero la mejora real es cognitiva: menos interrupciones y más tiempo de profundidad para problemas complejos.

    Dominar estas herramientas significa manejar prompts claros, revisar resultados como código humano y aplicar reglas estrictas de seguridad. Hecho así, la IA deja de ser un truco y pasa a ser un multiplicador de impacto para desarrolladores que quieren mover producto, no solo teclear.

    Para experimentación y prototipado de workflows relacionados con automatización e IA aplicada puedes ver materiales y experimentos en Dominicode Labs. Es un recurso complementario para probar patrones operativos y acelerar pruebas de concepto.

    FAQ

    Respuesta: No. La IA ayuda a generar borradores y ejemplos, pero la revisión de código por humanos es obligatoria. Trata la salida como trabajo de un desarrollador junior y audítala.

    Respuesta: No para sistemas críticos. No se debe delegar diseño arquitectural complejo ni decisiones transaccionales importantes sin experiencia humana y revisiones formales.

    Respuesta: Anonimiza y elimina PII antes de enviar datos a modelos. No pegues claves, tokens o información sensible en prompts.

    Respuesta: Herramientas como GitHub Copilot, ChatGPT y asistentes de LLM funcionan bien para boilerplate y pruebas; integra linters, CI y revisión humana en el flujo.

    Respuesta: La IA suele generar una cobertura base: casos felices, edge cases y algunas entradas inválidas. Es una base que hay que ampliar con casos de negocio y límites específicos.

    Respuesta: Usa prompts precisos, con ejemplos y formatos esperados. Incluye restricciones de seguridad (anonimización, no exponer credenciales) y pide explicaciones o comentarios inline cuando generes regex o transformaciones.

  • Cómo integrar la API de OpenAI

    Cómo integrar la API de OpenAI

    IA para Desarrolladores: Cómo integrar la API de OpenAI (o DeepSeek) en tu app de React

    Tiempo estimado de lectura: 4 min

    Ideas clave:

    • Separa responsabilidades: frontend ligero, backend proxy seguro y LLM como motor.
    • Usa streaming para mejorar UX y reducir latencia percibida.
    • Valida outputs estructurados con esquemas (Zod + streamObject) para fiabilidad.
    • Controla costes, rate limiting y observabilidad en producción.

    Tabla de contenidos

    Empecemos por lo que importa: llamar a la API directamente desde el navegador es una mala idea. Tu clave se filtra. Punto. La integración correcta separa responsabilidades: frontend ligero, backend proxy seguro y el LLM como motor. Patrón:

    Usuario → React (cliente) → Next.js API Route (proxy) → OpenAI/DeepSeek → stream → React

    Fuentes útiles: OpenAI, Vercel AI SDK, Next.js, DeepSeek, Zod.

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

    Proxy en servidor para proteger claves; streaming para UX; validar JSON con esquemas (Zod) cuando la app consume outputs estructurados; controlar rate limits y costes en el borde.

    Dependencias y entorno (rápido)

    Proyecto: Next.js (App Router) + React. Instala:

    npm install ai @ai-sdk/openai zod

    Variables (.env.local):

    OPENAI_API_KEY=sk-...
    DEEPSEEK_API_KEY=sk-...

    DeepSeek ofrece modelos compatibles y suele ser más económico para usos de alto volumen (ver docs en la URL arriba).

    Backend: API Route que hace streaming (segura y Edge-friendly)

    La API route actúa como proxy y gestiona streaming. Ejemplo mínimo (app/api/chat/route.ts):

    Ejemplo mínimo (route.ts)

    import { createOpenAI } from '@ai-sdk/openai';
    import { streamText } from 'ai';
    
    export const runtime = 'edge';
    
    const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
    const deepseek = createOpenAI({ baseURL: 'https://api.deepseek.com/v1', apiKey: process.env.DEEPSEEK_API_KEY });
    
    export async function POST(req: Request) {
      const { messages, provider = 'openai' } = await req.json();
    
      const client = provider === 'deepseek' ? deepseek : openai;
    
      const result = await streamText({
        model: client('gpt-4o-mini'),
        system: 'Eres un asistente técnico experto en React.',
        messages,
      });
    
      return result.toDataStreamResponse();
    }

    Qué hace bien: mantiene la clave en servidor, permite alternar proveedor, y entrega streaming de tokens al cliente.

    Frontend: UX con streaming usando useChat

    El hook useChat del SDK facilita el manejo del stream y del estado:

    Ejemplo de componente

    'use client';
    import { useChat } from 'ai/react';
    
    export default function Chat() {
      const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat({ api: '/api/chat' });
    
      return (
    {messages.map(m => <div key={m.id}>{m.role}: {m.content}</div>)}

    
      );
    }

    Beneficio: la respuesta aparece incrementalmente, reduciendo la latencia percibida y mejorando la sensación de “conversación”.

    Outputs estructurados: por qué y cómo (Zod + streamObject)

    Texto libre es bonito; los negocios prefieren JSON fiable. Forzar esquema reduce errores downstream.

    Ejemplo de esquema y uso de streamObject en la API:

    Ejemplo de esquema

    import { streamObject } from 'ai';
    import { z } from 'zod';
    
    const specSchema = z.object({
      componentName: z.string(),
      props: z.array(z.object({ name: z.string(), type: z.string(), required: z.boolean() })),
      description: z.string(),
    });
    
    const result = await streamObject({
      model: openai('gpt-4o-mini'),
      schema: specSchema,
      prompt: `Extrae el spec para un componente a partir de: "${prompt}"`,
    });

    Resultado: JSON validado progresivamente. Útil para autocompletar formularios, pipelines ETL o alimentar otros microservicios.

    Producción: rate limit, coste, observabilidad

    – Rate limiting: imprescindible. Usa Upstash Redis (Upstash Redis) o Cloudflare Workers KV para bloquear abusos.

    – Costos: mide coste por petición. Usa modelos como gpt-4o-mini o DeepSeek para la mayoría de tareas; reserva modelos grandes para casos de alto valor.

    – Context window: implementa resumen o recorte de historial para no enviar todo el chat cada vez.

    – Caching: cachea respuestas determinísticas (resúmenes, clasificadores) en Redis.

    – Observabilidad: trackea latencia, tokens usados y errores con métricas (Prometheus/Datadog).

    Rate limiting

    Implementar límites por IP/usuario en el borde para evitar abusos y picos inesperados.

    Costos

    Monitoriza coste por petición y ajusta modelo según valor del caso de uso.

    Context window

    Usa resumenes o recortes de historial para mantener tokens bajo control.

    Caching

    Cachear respuestas determinísticas en Redis reduce coste y latencia.

    Observabilidad

    Mide latencia, tokens y errores con Prometheus o Datadog para depurar y optimizar.

    OpenAI vs DeepSeek — guía rápida

    OpenAI: compatibilidad, calidad general, ecosistema maduro.

    DeepSeek: compatibilidad API con OpenAI, coste por token mucho menor en muchos casos. Ideal para alto volumen.

    Elige según coste por consulta, SLA y privacidad de datos.

    Cuándo merece la pena añadir IA

    SÍ: clasificación automática, asistentes internos, resumir logs, prellenado de formularios, extracción de entidades.

    NO: lógica determinista sencilla, validaciones que no requieren lenguaje natural, funciones con requisitos de latencia extremo (<200ms).

    Cierre con utilidad real

    Integrar la API de OpenAI o DeepSeek en React no es solo pegar un fetch: es diseñar tubos de datos seguros, eficientes y observables. Implementa proxy, usa streaming para la UX, obliga esquemas cuando la app lo necesita y controla costes y abusos en el borde. En la próxima entrega te muestro cómo encadenar esto con n8n para automatizar workflows híbridos (IA + APIs externas). Apúntalo: la parte interesante empieza cuando la IA se convierte en parte del pipeline, no en el punto final.

    Para quien busque continuidad práctica en automatización y workflows con IA, puede explorar recursos adicionales en Dominicode Labs. Es una continuación lógica para pruebas y prototipos que combinan IA con pipelines y agentes externos.

    FAQ

    ¿Por qué no debo llamar a la API desde el navegador?

    Porque la clave de API se expone en el cliente y puede ser robada. Un proxy en servidor mantiene la clave segura y permite controlar acceso, logging y límites.

    ¿Qué ventajas tiene el streaming para la UX?

    El streaming entrega tokens incrementalmente, reduciendo la latencia percibida y mejorando la sensación de conversación. Es especialmente útil en asistentes interactivos.

    ¿Cómo aseguro que los outputs sean fiables para mi app?

    Forzando un esquema y validando el output (por ejemplo con Zod y streamObject). Esto reduce errores downstream y facilita integración con pipelines o formularios.

    ¿Cuándo usar DeepSeek en lugar de OpenAI?

    Cuando el coste por token y el volumen justifican una alternativa compatible. DeepSeek ofrece compatibilidad API con OpenAI y suele ser más económico en muchos casos.

    ¿Qué debo monitorizar en producción?

    Latencia, tokens usados por petición, tasa de errores y patrones de uso. Integra métricas en Prometheus o Datadog y registra costes por petición.

    ¿Cómo implemento rate limiting en el borde?

    Usa soluciones como Upstash Redis (Upstash Redis) o Cloudflare Workers KV para imponer límites por IP o por usuario y bloquear abusos.

  • Ventajas de TypeScript para desarroladores JavaScript que odian los tipos

    Ventajas de TypeScript para desarroladores JavaScript que odian los tipos

    TypeScript para desarrolladores JavaScript que odian los tipos

    Tiempo estimado de lectura: 4 min

    • Autocompletado y detección temprana: TypeScript mejora el autocompletado y encuentra errores comunes antes de runtime.
    • Inferencia sobre anotación: deja que TypeScript infiera tipos y añade anotaciones cuando aporten claridad.
    • Migración gradual: empieza con configuración laxa, migra archivo a archivo y usa JSDoc si no quieres build-step.
    • Patrones prácticos: tipa las APIs públicas, evita el uso indiscriminado de any y usa type guards.
    • Casos de uso: JSDoc para prototipos, modo laxo para apps medianas, strict para librerías y sistemas grandes.

    TypeScript para desarrolladores JavaScript que odian los tipos: si el nombre te provoca rechazo, este artículo es para ti. Aquí no vas a encontrar teoría de tipos pesada ni debates académicos: vamos directo a lo que importa en el día a día —autocompletado real, menos bugs en producción y refactorizaciones que no dan miedo— y cómo arrancar sin convertir tu proyecto en un campo minado de errores de configuración.

    Resumen rápido (lectores con prisa)

    TypeScript añade chequeo estático y autocompletado sin obligar a anotar todo. Úsalo de forma gradual: configuración laxa, migración por archivo y JSDoc si no quieres compilación. Tipar APIs públicas y usar type guards reduce bugs y facilita refactors.

    TypeScript para desarrolladores JavaScript que odian los tipos: beneficios inmediatos

    La promesa práctica de TypeScript no es “más reglas”, sino “menos fricción”. Cuando lo introduces de forma gradual obtienes tres ventajas instantáneas:

    • Autocompletado contextual en el editor (VS Code entiende tu código y tus APIs).
    • Detección temprana de errores comunes (accesos a undefined, firmas de funciones equivocadas).
    • Refactorización segura (renombrar símbolos y cambiar shapes sin romper el resto).

    Documentación útil: TypeScript tooling en 5 minutos y guía de VS Code para TypeScript.

    Inferencia: la clave para odiar menos los tipos

    No tienes que anotar todo. TypeScript infiere tipos en la mayoría de los casos. Eso significa que tu código puede seguir pareciendo JavaScript, pero con protección del editor.

    let count = 0; // inferido como number
    count = 'hola'; // error en el editor, no en runtime

    Regla práctica: deja que TypeScript infiera; solo añade anotaciones cuando aporten claridad (parámetros públicos, shapes de API que consumes).

    Cómo empezar sin dolor (estrategia pragmática)

    No transformes todo el repo de golpe. Sigue este plan de tres pasos:

    Instala y configura de forma laxa

    • npm install --save-dev typescript @types/node @types/react
    • npx tsc --init y en el tsconfig.json pon allowJs: true y strict: false para comenzar.

    Migra un archivo a la vez

    • Renombra un helper: util.jsutil.ts.
    • Observa las sugerencias del editor; corrige lo esencial.

    Usa JSDoc si quieres cero build-step

    • Añade // @ts-check al inicio de archivos .js y documenta con JSDoc.
    • Ejemplo JSDoc: JSDoc

    Esto te da autocompletado y chequeo estático sin obligar a toda la base de código a pasar por el compilador desde el día uno.

    Patrones prácticos que importan

    – Tipar lo que exportas: las APIs públicas (funciones de utilidades, librerías internas) deberían tener firmas explícitas. El resto puede seguir con inferencia.

    • Evita any como hábito: úsalo solo como parche temporal. Prefiere unknown si necesitas un tiempo para decidir el shape.
    • Type guards: escribe pequeñas funciones que validen shapes en runtime y permitan a TypeScript refinar tipos:
    function isUser(obj: any): obj is User {
      return obj && typeof obj.id === 'number' && typeof obj.name === 'string';
    }

    – Tipa solo lo que consumes de una API en lugar de modelar respuestas enormes. Usa Pick, Partial o Omit para mantener interfaces manejables.

    Casos de uso realistas: cuándo merece la pena

    • Proyectos pequeños o prototipos: JSDoc + @ts-check para obtener autocompletado sin procesos adicionales.
    • Apps medianas (crecen rápido): TypeScript en modo laxo. La inversión paga cuando haces refactors frecuentes.
    • Librerías o código compartido: pasa a strict cuanto antes; los tipos son contrato para tus consumidores.
    • Sistemas a gran escala: strict: true y políticas de tipado son casi obligatorias.

    Problemas comunes y cómo resolverlos rápido

    • “Todo está en rojo”: usa any temporalmente y migra por capas.
    • “Demasiadas anotaciones”: recuerda la inferencia. Limpia anotaciones redundantes.
    • “Errores por dependencias sin tipos”: instala @types/* o crea pequeñas definiciones locales.

    Integración con el ecosistema moderno

    – Next.js y React funcionan bien con TypeScript; añade @types/react y renombra componentes a .tsx.

    – Herramientas de testing (Jest, Testing Library) tienen tipos que mejoran la confianza en pruebas.

    – Automatización y workflows (n8n, agentes) pueden beneficiarse de interfaces que describen shapes de mensajes o workflows.

    Conclusión: comienza pequeño, gana práctico

    Si odias los tipos, no necesitas amar TypeScript para aprovecharlo. Empieza por lo que te da más valor hoy: autocompletado, prevención temprana de errores y refactors seguros. Usa inferencia, JSDoc y una configuración laxa. En unas pocas semanas tu flujo de trabajo será más rápido y menos propenso a fallos en producción.

    Recursos

    Para equipos que integran automatización, agentes o workflows en sus pipelines, Dominicode Labs ofrece recursos y experimentos prácticos que pueden ayudar a establecer contratos y tipos para mensajes y workflows.

    FAQ

    ¿Por qué no debo anotar todo con tipos?

    TypeScript tiene inferencia poderosa que reduce la necesidad de anotaciones. Anotar solo lo que aporta claridad evita ruido y trabajo innecesario.

    ¿Cómo empiezo sin romper mi repo en producción?

    Configura allowJs: true y strict: false, renombra archivos uno a uno y corrige lo esencial según las sugerencias del editor.

    ¿Qué hago si todo aparece en rojo?

    Usa any como parche temporal y migra por capas. Prioriza tipar APIs públicas primero.

    ¿Puedo obtener beneficios sin configurar un build-step?

    Sí. Añade // @ts-check y documentación JSDoc en archivos .js para autocompletado y chequeo estático sin compilación.

    ¿Cuándo debo pasar a strict?

    Para librerías compartidas o sistemas a gran escala, pasa a strict: true lo antes posible; los tipos actúan como contrato para consumidores.

    ¿Cómo manejo dependencias sin tipos?

    Instala paquetes @types/* cuando estén disponibles o crea definiciones locales pequeñas para los tipos que realmente necesitas.

    ¿Debería evitar el uso de any por completo?

    Evítalo como hábito. Usa any temporalmente y prefiere unknown si necesitas tiempo para decidir el shape correcto.
  • Patrones de Diseño en React

    Patrones de Diseño en React

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

    Tiempo estimado de lectura: 4 min

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

    Introducción

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

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

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

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

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

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

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

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

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

    El problema en 3 líneas

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

    Spaghetti Code vs Clean Code: ejemplo práctico

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

    Spaghetti (anti-patrón):

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

    Clean (composición + hook):

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

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

    Reglas claras para escribir componentes reutilizables

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

    Señales de que debes refactorizar ahora

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

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

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

    Conclusión práctica

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

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

    Dominicode Labs

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

    FAQ

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

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

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

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

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

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

  • Cómo los desarrolladores de JavaScript pueden iniciarse en Ruby

    Cómo los desarrolladores de JavaScript pueden iniciarse en Ruby

    Introduccion a Ruby para Javascript devs

    Tiempo estimado de lectura: 5 min

    • Choque de modelo mental: Ruby es una filosofía orientada a la legibilidad y la productividad, no solo “otro lenguaje”.
    • Todo es objeto y retorno implícito: números, strings y nil exponen métodos; los métodos devuelven la última expresión.
    • Flujo tradicionalmente síncrono: MRI con GIL cambia las decisiones de concurrencia respecto a Node.
    • Ecosistema maduro: Bundler/Gems y Rails favorecen convención sobre configuración para backends monolíticos.

    Introducción

    Una Introduccion a Ruby para Javascript devs debe arrancar por el choque de modelo mental: Ruby no es “otro lenguaje”; es una filosofía que prioriza legibilidad, consistencia y productividad. Si vienes de Node/Browser —event loop, promesas, callbacks— aquí verás un sistema más lineal, orientado a objetos en su núcleo y con convenciones que reducen decisiones repetitivas. Este artículo explica las diferencias prácticas, ejemplos comparativos y criterios para decidir cuándo Ruby aporta valor real.

    Resumen rápido (lectores con prisa)

    Qué es: Un lenguaje orientado a objetos cuya sintaxis y convenciones priorizan legibilidad y productividad.

    Cuándo usarlo: Scripts, automatización, backends monolíticos con reglas de negocio complejas y proyectos donde la claridad importa.

    Por qué importa: Reduce boilerplate, facilita flujos secuenciales y favorece código mantenible.

    Cómo funciona (breve): Todo es objeto, retorno implícito en métodos, bloques nativos y ejecución tradicionalmente síncrona (MRI con GIL).

    ¿Qué cambia para un desarrollador JS en Ruby?

    Ruby fue diseñado por Yukihiro “Matz” Matsumoto con la idea de que el lenguaje se adapte al programador. Eso tiene consecuencias concretas:

    • Todo es objeto. No hay primitivos discontinuos: números, strings y hasta nil son instancias de clases y exponen métodos.
    • Retorno implícito. El valor de la última expresión de un método se devuelve automáticamente.
    • Sintaxis más permisiva. Paréntesis opcionales, bloques nativos (do...end / {}) en lugar de pasar callbacks como en JS.
    • Modelo de ejecución tradicionalmente síncrono. MRI usa GIL; para más contexto, leer sobre GIL, frente al modelo asíncrono y no bloqueante de Node.

    Estos puntos no son ornamentales; cambian cómo estructuras errores, pruebas y scripts.

    Ejemplos prácticos: comparar mentalidades

    Iteración y callbacks

    JavaScript (Node):
    const doubled = [1,2,3].map(n => n * 2);
    
    Ruby:
    doubled = [1,2,3].map { |n| n * 2 }  # Bloque inline
    

    Retorno implícito y string interpolation

    JavaScript:
    const greet = name => {
      return `Hello, ${name}`;
    };
    
    Ruby:
    def greet(name)
      "Hello, #{name}"  # se retorna implícitamente
    end
    

    Hashes y Symbols (clave frecuente en Ruby)

    user = { name: "Alex", role: :admin }
    user[:role] # => :admin
    

    Symbols (:admin) son inmutables y ocupan menos memoria que strings repetidos.

    Ecosistema y herramientas: Bundler, Gems y Rails

    La gestión de dependencias en Ruby se apoya en Bundler y RubyGems: Gemfile y Gemfile.lock garantizan reproducibilidad (bundle install). Documentación: Bundler y RubyGems.

    Rails es el marco de referencia para backends monolíticos en Ruby (Rails). Rails impone convención sobre configuración, patrones MVC claros, generators y un ORM maduro (ActiveRecord). Si vienes de Express —minimalista— Rails te obliga a organizar, lo que es ventaja cuando buscas consistencia en equipos.

    Asincronía y concurrencia: cómo pensar distinto

    Node te enseña a diseñar alrededor de la no-bloqueo. Ruby, especialmente MRI con GIL, tiende a bloquear en operaciones de I/O, aunque existen alternativas: JRuby o runtimes y bibliotecas como async. Para scripts, migraciones o procesos batch, el bloqueo secuencial simplifica el razonamiento y debugging; para sistemas de alta concurrencia I/O-bound, Node/Go siguen siendo mejores elecciones.

    Criterio técnico

    • Usa Ruby para tareas donde la simplicidad del flujo secuencial sea prioridad (scripts, ETL, CLIs).
    • Considera otros runtimes o arquitecturas (workers, colas) si necesitas alta concurrencia.

    Dónde Ruby aporta más valor (y por qué)

    • Scripting y automatización: escribir tareas con menos boilerplate que en Node.
    • Backends monolíticos con reglas complejas: la convención de Rails acelera decisiones arquitectónicas.
    • Ecosistemas específicos: Shopify y muchas apps legacy usan Ruby; entenderlo es estratégico (Shopify).
    • Calidad de código a largo plazo: menos churn en librerías y una comunidad conservadora y estable.

    Riesgos y cuándo evitarlo

    No elijas Ruby solo por moda. Si necesitas máxima concurrencia I/O o latencia ultrabaja, evalúa Node/Go/Rust. Si tu equipo no acepta la convención (opinionated stacks), Rails puede chocar con culturas “libertarias” de micro-librerías.

    Pasos prácticos para empezar (ruta recomendada)

    1. Escribe scripts sencillos: instala Ruby, crea un script.rb que conecte a la DB o lea ficheros.
    2. Aprende bloques y Symbols: son idiomáticos y aparecerán en todas las librerías.
    3. Usa Bundler y Gemfile desde el primer día.
    4. Súbete a Sinatra para entender HTTP mínimo, luego Rails para apps completas.
    5. Integra pruebas (RSpec) y tasks con Rake.

    Recursos

    Ruby oficial: Ruby oficial, Rails: Rails, Bundler: Bundler.

    Dominicode Labs

    Para quienes exploran automatización y workflows en proyectos de ingeniería, continúe con ejercicios prácticos y comparativas de integración. Más recursos y experimentos prácticos están disponibles en Dominicode Labs, donde publicaremos ejemplos: scripts de migración, patrón de servicios en Rails y comparativas de rendimiento real entre stacks.

    FAQ

    Respuesta: El choque es que Ruby favorece un flujo lineal orientado a objetos y convenciones que reducen decisiones repetitivas, mientras que JS/Node tienden a modelos asíncronos basados en event loop, promesas y callbacks.

    Respuesta: Usa Ruby cuando priorices simplicidad del flujo secuencial: scripts, ETL, CLIs y backends monolíticos con reglas de negocio complejas. Para sistemas I/O-bound con alta concurrencia, considera Node/Go.

    Respuesta: Ruby (MRI) tiende a un modelo síncrono y puede bloquear en I/O por el GIL; existen alternativas y bibliotecas que permiten concurrencia, pero la recomendación es diseñar en consecuencia o usar otros runtimes si la concurrencia es crucial.

    Respuesta: Los Symbols (ej. :admin) son valores inmutables usados como claves y etiquetas, ocupan menos memoria que strings repetidos y son idiomáticos en librerías Ruby.

    Respuesta: Empieza por escribir scripts sencillos, aprende bloques y Symbols, usa Bundler y Gemfile, prueba con Sinatra y luego Rails; integra pruebas con RSpec y tareas con Rake.

    Respuesta: No es obligatorio. Rails es la opción estándar para aplicaciones monolíticas por su convención y herramientas, pero puedes usar Sinatra u otros frameworks según la necesidad del proyecto.

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

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

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

    Tiempo estimado de lectura: 4 min

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

    Introducción

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

    Resumen rápido (lectores con prisa)

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

    Estrategias de Versionado y Gestión del Cambio

    Mensajes estandarizados

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

    Flujo

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

    Artefactos inmutables

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

    Generación de CHANGELOG

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

    Referencias: Conventional Commits

    Arquitectura de Construcción y Optimización del Bundle

    Build toolchain

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

    Zoneless

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

    Lazy & defer

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

    Budgets

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

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

    Assets

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

    Analítica de bundle

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

    Docs Angular build: angular.io/guide/build

    Estrategia de Contenedorización con Docker

    CSR (static)

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

    SSR

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

    Ejemplo multi-stage para SSR

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

    Seguridad

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

    Docker best practices: docs.docker.com

    Orquestación de CI/CD con GitHub Actions

    Pipeline clave

    Pipeline como contrato de calidad. Ejemplo de jobs clave:

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

    Snippets y prácticas

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

    Docs GitHub Actions: docs.github.com/actions

    Estrategias de Despliegue e Infraestructura

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

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

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

    Blue/Green o Canary

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

    Observabilidad

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

    Estrategia de Pruebas y Calidad (QA)

    Unit tests

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

    E2E

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

    Playwright: playwright.dev

    Visual regression & Performance

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

    Checklist mínimo previo a producción

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

    Conclusión

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

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

    FAQ

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

    Respuesta:

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

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

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

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

    Tiempo estimado de lectura: 4 min

    Ideas clave

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

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

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

    Resumen rápido (lectores con prisa)

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

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

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

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

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

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

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

    Arquitectura típica

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

    Referencia conceptual: Referencia conceptual

    Ejemplo práctico (TypeScript, simplificado)

    Command side

    Command side:

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

    Query side (proyector)

    Query side (proyector):

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

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

    Por qué usar CQRS: beneficios reales

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

    Stack sugerido

    El coste: por qué no es para todos

    CQRS trae dos impuestos que muchos subestiman:

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

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

    Cuándo aplicarlo (criterios claros)

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

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

    Evítalo si:

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

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

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

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

    Conclusión y siguiente paso

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

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

    FAQ

    ¿Qué significa CQRS?

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

    ¿Cuándo es una buena idea implementarlo?

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

    ¿Qué impacto tiene en la consistencia de datos?

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

    ¿Qué componentes operativos debo considerar?

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

    ¿Puedo aplicar CQRS solo a partes de mi sistema?

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

    ¿Qué herramientas ayudan con CQRS y Event Sourcing?

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

  • Scripts en Python para optimizar tu productividad semanal

    Scripts en Python para optimizar tu productividad semanal

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

    Tiempo estimado de lectura: 4 min

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

    Introducción

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

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

    Resumen rápido (lectores con prisa)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    3) Scraping controlado y monitoreo (Requests + BeautifulSoup)

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

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

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

    Cuando la web es dinámica, usa Playwright.

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

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

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

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

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

    Patrón:

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

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

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

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

    Integración práctica con n8n y workflow orquestado

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

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

    Conclusión: dónde empezar hoy

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

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

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

    Recursos

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

    FAQ

    ¿Qué dependencias necesito?

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

     

    ¿Cómo integrarlo con n8n?

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

     

    ¿Qué pruebas debo escribir?

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

     

    ¿Cómo manejar secretos y configuración?

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

     

    ¿Qué hacer si el scraping requiere JS?

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

     

    ¿Cómo convertir HTML a PDF?

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