Category: Blog

Your blog category

  • Lifecycle hooks en Angular: lo que ya no necesitas (y qué usar en su lugar)

    Lifecycle hooks en Angular: lo que ya no necesitas (y qué usar en su lugar)

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Signals y primitivas reactivas reemplazan la mayoría de los lifecycle hooks clásicos.
    • Convierte Inputs en señales y usa computed() para derivados; usa effect() para side-effects.
    • Usa DestroyRef.onDestroy() para limpieza cerca del recurso, evitando ngOnDestroy globales.
    • Reacciona al DOM con signal queries y afterNextRender() en lugar de los hooks after-view.

    Tabla de contenidos

    Si llevas años programando en Angular, algunos hooks clásicos te resultarán familiares: ngOnChanges, ngOnInit, ngAfterViewInit, ngOnDestroy… La plataforma no elimina el lifecycle, pero las nuevas primitivas (Signals, DestroyRef y utilidades de renderizado) mueven el enfoque hacia relaciones declarativas entre datos y componentes reaccionando a cambios en señales en lugar de comprobar “cuándo ocurre X”.

    Resumen rápido (lectores con prisa)

    Qué: Signals y primitivas reactivas reemplazan la mayor parte de los lifecycle hooks clásicos.

    Cuándo: Úsalos al convertir Inputs, calcular derivados y gestionar side-effects que dependan de estado reactivo.

    Por qué: Código más declarativo, menos errores de timing y mayor testabilidad.

    Cómo: Inputs → señales; derivados → computed(); efectos → effect(); limpieza → DestroyRef.onDestroy().

    Lifecycle hooks en Angular: qué dejar atrás y por qué

    ngOnChanges → computed()

    ngOnChanges estaba destinado a detectar cambios en @Input() y recalcular derivados. Con Signals, transforma inputs en señales y usa computed() para expresar derivados de forma declarativa.

    price = input.required<number>();
    tax   = input.required<number>();
    total = computed(() => this.price() * (1 + this.tax()));

    Por qué: menos código, cero bugs de sincronización, lecturas deterministas.

    ngOnInit → constructor + effect() (o input())

    ngOnInit fue el cajón de sastre. Los patrones modernos distribuyen responsabilidades:

    • Estado inicial: declara signal() o computed() en la propiedad.
    • Side-effects reactivos: usa effect() para reaccionar a señales.
    • Carga inicial ligada a inputs: usa helpers que disparan requests cuando el input está disponible.

    Sigue usando ngOnInit solo si necesitas un efecto que explícitamente ocurra una vez y no dependa de reactividad (migraciones, integraciones legacy).

    ngDoCheck → casi siempre obsoleto

    Si creías necesitar ngDoCheck, para y replantea. ngDoCheck suele esconder un mal diseño. Signals cubren la detección fina sin chequeos manuales. Rediseña con señales y computed(); si aún necesitas inspección manual, documenta por qué.

    ngAfterViewInit / ngAfterContentInit → signal queries + afterNextRender

    Acceder a un ViewChild o ContentChild sin esperar el momento correcto fue una fuente interminable de bugs. Las Signal Queries (viewChild, contentChild) devuelven señales que cambian cuando el elemento existe.

    chartEl = viewChild<ElementRef>('chart');
    
    constructor() {
      effect(() => {
        const el = this.chartEl();
        if (el) { /* setup chart */ }
      });
    
      afterNextRender(() => {
        // ejecutar una vez después del primer render en cliente
      });
    }

    Combina con afterNextRender() para inicializar librerías del DOM (Chart.js, Leaflet) en cliente sin romper SSR.

    ngAfterViewChecked → afterRender

    Para reacciones tras cada render, afterRender() es la alternativa con semántica clara. Evita usarlo para lógica pesada; es para observabilidad y ajustes UI puntuales.

    ngOnDestroy → DestroyRef

    Olvida implementar OnDestroy en todas las clases. Inyecta DestroyRef y registra callbacks donde crees recursos. Esto vuelve la limpieza componible y cercana al lugar de creación del recurso.

    const destroyRef = inject(DestroyRef);
    const timer = setInterval(...);
    destroyRef.onDestroy(() => clearInterval(timer));

    Referencia: DestroyRef

    Reglas prácticas de migración (lista corta, aplicable)

    1. Inputs → convierte a Signal Inputs; todos los cálculos derivados en computed().
    2. Efectos → usa effect() en constructor; evita suscripciones manuales cuando sea posible.
    3. DOM post-render → viewChild() + effect() para reactividad; afterNextRender() para inicializaciones DOM-only.
    4. Limpieza → DestroyRef.onDestroy() junto al recurso.
    5. Legacy → mantén hooks si tu integración depende de ellos; documenta el porqué.

    Por qué importa (y cuándo no cambiar)

    Menos hooks = menos magia oculta. Código más testable. Menos errores por timing. Pero hay excepciones: bibliotecas legacy, patterns altamente instrumentados, o equipos que necesitan migraciones graduales. En esos casos, planifica parques de migración: componente por componente.

    Angular evoluciona hacia un modelo declarativo. Adoptar Signals no es solo reescribir métodos; es reorganizar la arquitectura mental del componente: de “controlador de eventos” a “descripción de relaciones”. El resultado: componentes más predecibles, más fáciles de probar y mantener.

    Lecturas recomendadas

    FAQ

     

    ¿Por qué debería convertir mis Inputs a señales?

    Convertir Inputs a señales permite expresar de forma declarativa los cálculos derivados con computed() y reaccionar con effect(). Esto elimina la necesidad de logic dispersa en ngOnChanges y reduce errores de sincronización.

     

    ¿Cuándo sigue siendo válido usar ngOnInit?

    Usa ngOnInit cuando necesites un efecto que debe ejecutarse exactamente una vez y no depende de la reactividad de señales (por ejemplo, migraciones o integraciones legacy). Para lógica ligada a estado reactivo, prefiere effect().

     

    ¿Cómo reemplazo las suscripciones manuales?

    Siempre que sea posible, reemplaza suscripciones con effect() o convierte fuentes externas en señales. Para recursos que aún usan callbacks o timers, registra limpieza con DestroyRef.onDestroy() junto al recurso.

     

    ¿Qué uso para inicializaciones que requieren el DOM?

    Usa signal queries (viewChild(), contentChild()) que devuelven señales y combínalas con effect(). Para inicializaciones que deben ocurrir solo en cliente después del primer render, utiliza afterNextRender().

     

    ¿Qué ventajas tiene DestroyRef frente a ngOnDestroy?

    DestroyRef permite registrar callbacks de limpieza cerca del recurso, haciendo la limpieza composable y localizada. Evita la necesidad de implementar la interfaz OnDestroy en cada clase y mejora la trazabilidad de recursos.

  • Cómo implementar evals como unit tests para LLMs

    Cómo implementar evals como unit tests para LLMs

    Qué son los evals; los unit test de los LLMs

    Tiempo estimado de lectura: 4 min

    • Los evals son unit tests para sistemas basados en LLMs: pipelines reproducibles que miden si un modelo/prompt/pipeline sigue entregando lo que el negocio necesita.
    • Tipos de evaluadores: determinista (regex/JSON Schema), semántico (embeddings + similitud) y LLM-as-a-Judge.
    • Práctica: crea un dataset representativo, define la métrica principal, implementa runner y scorer, e integra en CI/CD.

    Introducción

    Que son los evals; los unit test de los llms. Lo repito porque es la pregunta que nadie hace en serio hasta que algo falla en producción y empiezan a llover tickets.

    Resumen rápido (lectores con prisa)

    Eval: pipeline reproducible con dataset (golden set), runner, scorer y reporte que actúa como CI para la parte probabilística del sistema. Busca señales (factualidad, coherencia, formato), no igualdad exacta. Usa validación determinista, similitud de embeddings o un LLM-judge según el caso.

    ¿Qué son los evals; los unit test de los llms?

    Un eval es un pipeline reproducible: un dataset de entradas y salidas (golden set), un runner que envia prompts al modelo, un scorer que compara la respuesta con criterios, y un reporte que te dice si rompiste algo. Piénsalo como CI para la parte probabilística del sistema.

    A diferencia de un test unitario clásico, aquí no buscas igualdad exacta: buscas señales. Precisión factual, coherencia, formato JSON válido, ausencia de alucinaciones, y que el tono encaje con la interfaz. Todo eso se mide con métricas y reglas. Y sí: algunas veces el “juez” también es otro LLM.

    Tipos prácticos de evaluadores (y cuándo usarlos)

    Descripción breve de los enfoques más prácticos para evaluar salidas de LLMs y cuándo aplicarlos.

    Determinista

    Regex, validación de esquema (JSON Schema), comprobaciones de campo. Útil cuando la salida debe ser parseable. Ejemplo: validar que el LLM devuelva {"name": "...", "email": "..."}.

    Semántico

    Embeddings + similitud coseno. Ideal para summarization y Q&A donde importa el sentido, no la palabra exacta.

    LLM-as-a-Judge

    Un LLM potente evalúa las respuestas según una rúbrica. Sirve para tono, coherencia o seguridad, pero introduce sesgo y coste.

    No mezcles métricas porque sí. Prioriza la que más impacta tu negocio: si tu app depende de JSON bien formado, la métrica principal es “JSON parseable + campos obligatorios”.

    Herramientas y referencias prácticas

    Empieza con herramientas que ya existen:

    Estos proyectos te dan fixtures, runners y ejemplos para arrancar. No reinventes la rueda: adapta un benchmark a tu caso de uso.

    Cómo montar tu primer eval (en 5 pasos reales)

    Pasos concretos para crear un eval operativo.

    1. Crea un dataset de 50–100 ejemplos representativos

    Incluye casos comunes y edge cases que te aterran.

    2. Define la métrica principal

    Ej.: exact match para IDs, coseno>0.85 para respuestas semánticas, 0-1 score para seguridad.

    3. Implementa el runner

    Script que llama al LLM con el prompt actual y guarda outputs.

    4. Añade el scorer

    Validación JSON + embeddings o LLM-judge según necesites.

    5. Integra en CI/CD

    Si la puntuación baja del umbral, el pipeline falla y se bloquea el despliegue.

    Resultado: antes de tocar el botón de deploy sabes si rompiste la experiencia.

    Ejemplo corto: validar extracción de entidades en n8n

    Tienes un workflow que extrae nombre, email y producto de emails entrantes. Tu eval debería:

    • Enviar 200 emails sintéticos + reales.
    • Comprobar que el JSON sea válido.
    • Verificar que el campo email pase regex.
    • Comparar entidades con embeddings para detectar ocasionalmente false negatives.

    Si el score cae de 0.92 a 0.82 tras un cambio de prompt, no lo llames “variación normal”. Llama a la rollback.

    Peligros reales (y cómo evitarlos)

    • Data contamination: cuidado con ejemplos de test que el modelo ya vio en entrenamiento. Usa datos frescos.
    • Varianza: ejecuta cada caso varias veces (n=3–5) y usa la media o el percentil.
    • Métricas irrelevantes: BLEU o ROUGE por costumbre no te salvan; usa métricas alineadas con el objetivo del negocio.
    • Juez sesgado: si usas un LLM como juez, documenta la rúbrica y haz validaciones humanas periódicas.

    Punto para líderes técnicos

    Los evals transforman subjetividad en trazabilidad. Permiten comparar coste vs. calidad (GPT-4o-mini vs. otro) con cifras, no con intuiciones. Integrar evals es un paso pequeño en esfuerzo y gigante en reducción de riesgos.

    Haz esto ahora: crea un mini-eval con 50 ejemplos, añade una job en tu CI que ejecute el runner y falle si el score < 0.8. Si en 2 semanas no tienes alertas útiles, sube el umbral.

    No es sexy. Es necesario. Y cuando el sistema falle a las 3 a.m., agradecerás haberlos hecho.

    Dominicode Labs

    Si trabajas con automatización, IA aplicada, n8n o workflows, puede interesarte explorar recursos adicionales en Dominicode Labs. Es una continuación lógica para prototipar mini-evals y automatizar runners en pipelines existentes.

    FAQ

    Preguntas frecuentes — haz clic en una pregunta para ir a la respuesta.

    ¿Qué es un eval?

    Un eval es un pipeline reproducible que incluye un dataset (golden set), un runner que llama al modelo, un scorer que compara salidas según reglas o métricas y un reporte que indica si el rendimiento cumple el umbral esperado.

    ¿Cuándo usar evaluadores deterministas?

    Usa evaluadores deterministas cuando la salida debe ser parseable y exacta (por ejemplo JSON con campos obligatorios). Validaciones por regex y JSON Schema son adecuadas en esos casos.

    ¿Por qué usar embeddings en evaluaciones semánticas?

    Porque las tareas como summarization y Q&A requieren comparar significado, no coincidencia literal. Embeddings + similitud coseno capturan la proximidad semántica entre la salida y la referencia.

    ¿Cómo integrar evals en CI/CD sin frenar despliegues válidos?

    Define umbrales claros y ejecuta las evaluaciones en una job separada. Si el score baja del umbral, falla la job y bloquea el despliegue. Ajusta el umbral basado en datos y monitoriza alertas para evitar falsos positivos.

    ¿Qué precauciones tomar si uso un LLM como juez?

    Documenta la rúbrica, valida el juez con comparaciones humanas periódicas y considera el sesgo y coste. Guarda ejemplos y decisiones para auditoría.

  • Cómo implementar observabilidad en LLMs para evitar errores

    Cómo implementar observabilidad en LLMs para evitar errores

    El mayor error al trabajar con LLMs: no saber qué está pasando

    Tiempo estimado de lectura: 4 min

    Ideas clave

    • Sin observabilidad, los LLMs son cajas negras: problemas de calidad, inconsistencias y costes inesperados.
    • Instrumenta trazas completas: prompts versionados, pasos intermedios, tokens y costes por traza.
    • Usa herramientas existentes (Langfuse, LangChain, n8n) y métricas estándar (TTFT, latencias, tokens).
    • Un trace_id por interacción facilita depuración, reproducibilidad y ahorro de costes.

    Introducción

    ¿Tu LLM falla y no sabes por qué? Es el problema silencioso que paraliza a los equipos de IA. Implementas un agente, pasa las pruebas locales y en producción un usuario reporta una alucinación grave. La API devuelve 200 OK. No hay excepciones. Y, aun así, la respuesta es basura.

    Ese es el síntoma claro de el mayor error al trabajar con LLMs: no saber qué está pasando dentro de la caja negra. Tratar componentes estocásticos como si fuesen APIs deterministas es pedir problemas: calidad errática, usuarios frustrados y facturas que suben sin control.

    Resumen rápido (lectores con prisa)

    Observabilidad para LLMs = trazas completas + prompts versionados + tokens/costes por interacción. Útil cuando necesitas reproducir alucinaciones, recuperar cambios de prompt o reducir costes. Implementa un trace_id único por interacción y registra system prompt, contexto, pasos intermedios y métricas (TTFT, latencias, tokens).

    Los síntomas de operar a ciegas

    Integrar un LLM con un par de console.log puede ser aceptable en prototipos. En producción, verás tres dolores rápidos:

    • Prompts que “caducan”: un prompt que funcionaba cambia comportamiento sin que tú cambies nada. Puede ser una actualización del modelo, un token limit o un caso borde de entrada. Sin historial no hay diagnóstico.
    • Outputs inconsistentes y alucinaciones: usuarios reciben información inventada. Sin la traza completa (system prompt, contexto inyectado, temperatura, seed), no hay forma de reproducir ni arreglar.
    • Costes opaques: la factura sube. ¿Más usuarios o un bucle de agentes que consume 10k tokens por interacción? Sin coste por traza, la optimización es adivinatoria.

    De la “ingeniería de prompts” a la ingeniería de software

    En software serio nadie despliega sin observabilidad: logs estructurados (ELK), seguimiento de errores (Sentry), métricas y trazas (Prometheus, Datadog). Allí donde una API falla, un trace te muestra la consulta SQL que la bloqueó.

    Con LLMs muchos equipos siguen sin esos instrumentos. Error fundamental: los LLMs son componentes no deterministas. Requieren más visibilidad, no menos. No basta con saber que “falló”; hay que saber qué pasos siguió el modelo hasta fallar.

    Observabilidad para LLMs: qué necesitas medir

    No es cuestión de adoptar buzzwords. Es instrumentar las interacciones de forma estructurada. Un stack mínimo de LLM Ops debe capturar:

    • Trazas de ejecución: cada trace debe guardar los pasos intermedios —retrievals (RAG), llamados a herramientas, subtasks del agente— para reconstruir el árbol de decisiones.
    • Prompts versionados: almacenar el prompt exacto (y su versión) usado en cada llamada para poder comparar A/B y revertir cambios malos.
    • Tokens y costes por traza: tokens de entrada/salida, coste estimado por llamada, y agregados por feature (ej. “Resumen PDF” vs “Chat general”).
    • Latencias y TTFT: Time To First Token y latencia total, separando orquestación (vector search, DB) y generación de tokens.
    • Metadata contextual: user_id, request_id, model_version, env, y un trace_id único para correlación con logs y métricas.

    Con esos datos ya puedes responder preguntas operativas: ¿por qué un caso concreto alucina? ¿Qué prompt/version empeoró la calidad? ¿Qué feature consume más presupuesto?

    Herramientas y patrón de integración

    No reinventes todo. Hay proyectos y herramientas que encapsulan la observabilidad de LLMs:

    • Langfuse: plataforma orientada a trazas de LLMs, visualización de árboles de ejecución y curación de datasets. Convierte prompts en artefactos depurables.
    • LangChain: framework de chains/agents que puedes instrumentar.
    • n8n: útil cuando orquestas workflows y quieres nodos que emitan trazas estructuradas.

    Patrón práctico:

    1. Instrumenta cada llamada al modelo con un trace_id.
    2. Registra prompt completo, system prompt, variables, model_version, y parámetros (temperature, max_tokens).
    3. Registra pasos intermedios (RAG hits, tool outputs).
    4. Calcula y almacena coste estimado por traza.
    5. Expón dashboards y búsquedas por trace_id para depuración rápida.

    Ejemplo táctico (mental): un trace_id cambia todo

    Imagina que un usuario reporta una factura errónea. Buscas trace_id y ves:

    • System prompt v2.1
    • Contexto RAG: documento X con fecha antigua
    • Agent: intentó lookup_price → timeout → fallback generó precio estimado
    • Tokens: 9k tokens consumidos → coste alto

    Con esa traza decides: ajustar RAG freshness, aumentar timeouts de la tool, y agregar verificación post-mutation. Sin traza, solo especularías y aplicarías parches a ciegas.

    Cierre: deja de depender de la esperanza

    Operar LLMs sin observabilidad es lo mismo que conducir de noche sin luces: puedes avanzar, pero no sabes cuándo chocarás. La observabilidad transforma la incertidumbre en datos accionables: reproduce errores, reduce costes y crea ciclos de mejora operativa.

    Langfuse convierte tus prompts en algo depurable. Así debería ser cualquier sistema serio. Si estás construyendo con LLMs en producción, esto no es opcional: instrumenta, mide y mantén control.

    Dominicode Labs

    Si trabajas en automatización, agentes o workflows y buscas un punto de partida con prácticas de observabilidad, mira lo que propone Dominicode Labs. Es una referencia práctica para equipos que necesitan trazabilidad y herramientas operativas.

    FAQ

    ¿Qué es una traza de LLM?

    Una traza es un registro estructurado de una interacción completa con el sistema: prompts (system y user), pasos intermedios (RAG hits, llamadas a herramientas), tokens consumidos, latencias y metadata contextual (request_id, user_id, model_version, trace_id).

    ¿Qué debe incluir un prompt versionado?

    El prompt exacto usado, su versión o hash, variables inyectadas, system prompt asociado y el conjunto de reglas de post-procesado. Esto permite comparar A/B y revertir cambios.

    ¿Cómo se calcula coste por traza?

    Suma el coste estimado por token de entrada y salida para cada llamada al modelo, añade coste de herramientas externas si aplica, y agrega por feature para obtener métricas agregadas por funcionalidad.

    ¿Qué métricas operativas son críticas?

    TTFT (Time To First Token), latencia total, tokens de entrada/salida, coste estimado, tasas de alucinación o error y métricas de orquestación (ej. tiempos de búsqueda vectorial, timeouts de tools).

    ¿Cuándo usar Langfuse o LangChain?

    Usa Langfuse para trazas y curación de datasets; usa LangChain cuando tu arquitectura gira en torno a chains/agents que necesitas instrumentar. No son mutuamente excluyentes.

    ¿Cómo empezar con trazabilidad mínima?

    Implementa un trace_id por interacción, guarda prompt completo y system prompt, registra tokens consumidos y latencias, y al menos un campo de metadata (request_id/user_id). Esto ya te permite reproducir y diagnosticar muchos problemas.

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

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

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

    Tiempo estimado de lectura: 3 min

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

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

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

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

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

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

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

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

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

    Pila mínima recomendada

    Instalación

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

    Concepto: stateless

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

    Modelo de usuario y hashing (criterio práctico)

    Automatiza el hashing con Mongoose para evitar fugas por olvidos:

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

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

    Login: validar, firmar y enviar cookie

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

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

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

    Middleware protector de rutas

    Intercepta y valida la cookie antes de permitir el acceso:

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

    Adjunta req.user.userId para consultas posteriores.

    Logout: simple y efectivo

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

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

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

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

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

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

    Consideraciones de producción (criterio senior)

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

    Conclusión y siguiente paso

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

    FAQ

    Respuesta

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

    Respuesta

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

    Respuesta

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

    Respuesta

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

    Respuesta

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

    Respuesta

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

  • Sistema completo para reciclar tu contenido: de vídeo de YouTube a hilo de X, newsletter y post SEO con ayuda de IA

    Sistema completo para reciclar tu contenido: de vídeo de YouTube a hilo de X, newsletter y post SEO con ayuda de IA

    Tiempo estimado de lectura: 5 min

    Ideas clave

    • Documento Maestro: un JSON semántico como única fuente de verdad para generar todos los assets.
    • Pipeline técnico: n8n → extracción de audio → transcripción fiable → LLM de ventana amplia → agentes por canal.
    • Human-in-the-loop: automatiza generación, pero valida lógica y snippets antes de publicar.

     

    Introducción

    Un sistema completo para reciclar tu contenido transforma un único vídeo de YouTube en un post SEO, un hilo de X y una newsletter lista para enviar —con automatización y control humano—. En las primeras líneas: este artículo explica cómo montar ese pipeline técnico (n8n + transcripción fiable + LLMs), por qué usar un Documento Maestro intermedio y cómo evitar los errores que convierten la IA en ruido.

    Resumen rápido (lectores con prisa)

    Qué: Un pipeline que convierte un vídeo en múltiples assets usando transcripción y un Documento Maestro JSON.

    Cuándo usarlo: Cuando quieras economizar la producción de contenidos técnicos y mantener coherencia entre canales.

    Por qué importa: Reduce tiempo humano y disminuye contradicciones y alucinaciones entre outputs.

    Cómo funciona: n8n detecta vídeo → extrae audio → transcribe → LLM genera Documento Maestro → agentes producen post/hilo/newsletter → revisión humana.

    Por qué no basta con “resumir” y qué es el Documento Maestro

    Resumir un transcript directamente produce frases planas y repetitivas. La diferencia entre contenido útil y contenido reciclado con criterio es la estructura semántica: tesis, puntos clave, bloques de código y hooks de publicación. Ese artefacto es el Documento Maestro —un JSON que sirve como única fuente de verdad para todos los generadores posteriores.

    Ejemplo sencillo de esquema JSON

    {
      "tesis_central":"Por qué automatizar transcripciones mejora distribución técnica",
      "puntos_clave":["Transcripción fiable","Documento Maestro","Generación por canal"],
      "codigo_snippets":[{"lenguaje":"bash","codigo":"yt-dlp -x --audio-format mp3 ","explicacion":"Extrae audio del vídeo"}],
      "punto_dolor_resuelto":"Perder horas reescribiendo lo mismo para cada canal",
      "citas_destacadas":[{"ts":"00:02:15","texto":"La transcripción es tu índice semántico."}]
    }
    

    Genera ese JSON con un LLM de ventana amplia (Claude 3.5 Sonnet o GPT‑4o) para mantener coherencia en todo el pipeline.

    Fase 1 — Ingesta y transcripción (calidad sobre rapidez)

    Workflow básico en n8n

    1. Trigger: nuevo vídeo detectado por RSS/YouTube API.
    2. Descarga de audio: yt-dlp o Apify YouTube Scraper.
    3. Transcripción: OpenAI Whisper o Deepgram.

    Recomendación práctica

    – Empieza con Whisper por su integrabilidad y calidad con jerga técnica.

    – Usa Deepgram si procesas muchos minutos al mes o necesitas diarización y baja latencia.

    Salida

    Salida: transcript con timestamps (SRT/JSON). Ese archivo alimenta el LLM que genera el Documento Maestro.

    Fase 2 — Generación del Documento Maestro (estructura y criterio)

    Prompt base (simplificado)

    “Analiza esta transcripción técnica y devuelve un JSON con: tesis_central (1 frase), puntos_clave (3–5 bullets), codigo_snippets (lenguaje, codigo, explicacion), punto_dolor_resuelto y 2–3 citas_hook con timestamp.”

    ¿Por qué insistir en esto? Porque evita que cada asset derive de frases sueltas. Todas las piezas (blog, hilo, newsletter) referenciarán el mismo origen estructurado, reduciendo contradicciones y “alucinaciones”.

    Fase 3 — Agentes especializados: un prompt por canal

    Con el Documento Maestro listo, bifurca el proceso en n8n a tres agentes:

    Post SEO (800 palabras, Markdown + frontmatter)

    Prompt: “Actúa como editor técnico. Usa el Documento Maestro para escribir un artículo de 800 palabras con H2/H3, ejemplos de código y conclusión práctica. Incluye frontmatter para CMS.”

    Output: Markdown listo para publicar en Ghost/WordPress/Next.

    ---
    title: "Recicla tu vídeo: n8n + Whisper"
    date: 2026-01-24
    keywords: ["content repurposing","n8n","AI transcription"]
    ---
    

    Hilo de X (5–7 tweets)

    Prompt: “Genera un hilo de 5–7 tweets. Tweet 1: gancho basado en punto_dolor. Tweets intermedios: 1 idea por tweet. Último tweet: CTA al vídeo.”

    Output: Array de tweets listos para revisión o publicación programada.

    Newsletter (≈400 palabras)

    Prompt: “Escribe un email con tono de mentor: 1 anécdota, insight clave, tip exclusivo y CTA al vídeo/post.”

    Output: HTML/Markdown para Mailchimp/Substack.

    Fase 4 — Orquestación y Human-in-the-Loop

    No publiques automáticamente. Workflow recomendado en n8n:

    • Genera borradores en Notion/Google Docs/CMS.
    • Envía notificación a Slack con enlaces.
    • Un humano revisa: verifica código, corrige posibles alucinaciones y ajusta tono.

    Plantéalo como edición, no como revisión ortográfica: valida lógica, snippets y enlaces.

    Costes, métricas y escalado rápido

    Coste aproximado (orden de magnitud para 10 vídeos/mes): transcripción + LLMs + Apify/n8n infra — entre $15–50/mes según consumo y modelos. Prioriza calidad de transcripción; un transcript limpio reduce tiempo de edición humana drásticamente.

    Escalado

    – Añade Vector DB (Pinecone) para buscar fragments y crear FAQs.

    – Parsea el Documento Maestro a micro-formatos (carousels de LinkedIn, captions de Instagram).

    Conclusión práctica

    Montar este sistema toma unas horas si ya tienes n8n y claves de API: trigger → transcribir → Documento Maestro → tres agentes → revisión humana. El retorno es claro: un solo vídeo alimenta múltiples canales con coherencia técnica y menos trabajo repetitivo. Implementa la cadena hoy, ajusta prompts mañana y pisa el acelerador cuando el procedimiento sea sólido. Tu equipo agradece menos tareas manuales; tu audiencia recibe contenido pensado, no reciclado.

    Más recursos y experimentos relacionados con automatización y agentes están disponibles en Dominicode Labs. Es una continuación útil si buscas plantillas y ejemplos prácticos para n8n y pipelines de IA.

     

    FAQ

    ¿Por qué usar un Documento Maestro en JSON?

    Un Documento Maestro estructura la semántica del contenido (tesis, puntos clave, snippets, hooks). Sirve como una única fuente de verdad para generar assets consistentes y reducir contradicciones entre canales.

    ¿Qué transcriptor conviene para jerga técnica?

    Empieza con OpenAI Whisper por su integrabilidad y manejo de terminología técnica. Usa Deepgram si necesitas diarización o procesas muchos minutos con baja latencia.

    ¿Puedo automatizar la publicación directa desde n8n?

    Puedes, pero no es recomendable sin revisión humana. El flujo sugerido genera borradores en CMS/Notion y notifica a un revisor para validar código, enlaces y lógica antes de publicar.

    ¿Cómo evito alucinaciones en los outputs del LLM?

    Usa el transcript con timestamps como contexto, genera el Documento Maestro con un LLM de ventana amplia y establece prompts estructurados. Siempre valida hechos y snippets en la etapa humana.

    ¿Qué coste aproximado tiene este pipeline?

    Para 10 vídeos/mes, la estimación de orden de magnitud indicada es $15–50/mes, dependiendo de modelos y consumo. Prioriza calidad de transcripción para reducir horas de edición humana.

  • Cómo estructurar un proyecto Angular grande sin morir en el intento

    Cómo estructurar un proyecto Angular grande sin morir en el intento

    Tiempo estimado de lectura: 3 min

    Ideas clave:

    • Organiza por dominios (slicing vertical) en lugar de por tipo de archivo.
    • Divide librerías en feature / ui / data-access (domain) / util y aplica reglas de dependencia.
    • Usa el patrón Facade para aislar la UI del estado y poder cambiar implementaciones.
    • Automatiza límites con reglas (ESLint / depConstraints de Nx) y aprovecha caching y librerías en Nx.

    Tabla de contenidos

    Cómo estructurar un proyecto Angular grande sin morir en el intento empieza por admitir que el problema no es Angular: es tu arquitectura. Agrupar por tipo de archivo es cómodo al principio y mortal a los 6 meses. Aquí explico cómo organizar por dominios, qué librerías crear, qué patrones aplicar y qué reglas automatizar para que tu app crezca sin destrozar la productividad del equipo.

    En las primeras líneas: si tu equipo supera los dos desarrolladores, deja de pensar en carpetas por tecnología y empieza a pensar en dominios de negocio. Usa Nx para orquestar librerías, aplica el patrón Facade y fuerza límites con ESLint. Enlaces útiles: Nx, Module Boundaries, Standalone Components.

    Resumen rápido (lectores con prisa)

    Slicing vertical = agrupar por dominio. Librerías: feature / ui / data-access (domain) / util. Facade = única API estable para UI. Reglas (ESLint / depConstraints) hacen cumplir límites. Nx facilita orquestación y caching.

    Cómo estructurar un proyecto Angular grande sin morir en el intento (resumen práctico)

    La estructura que realmente escala combina tres decisiones simples: 1) Slicing vertical por dominio, 2) Librerías tipadas (feature/ui/data-access/util), 3) Patrones que desacoplan (Facade, Barrels) y reglas automáticas (ESLint). No es exotismo; es disciplina.

    Slicing vertical por dominio

    Evita el antipatrón: /components, /services, /models. En su lugar, crea dominios verticales:

    • /libs/booking → todo lo relacionado con reservas.
    • /libs/payments → lógica de pagos.
    • /libs/shared/ui-kit → componentes presentacionales.

    Cada dominio contiene sub-librerías con responsabilidades claras. El resultado: para cambiar una feature solo tocas la librería del dominio pertinente.

    Taxonomía de librerías que sí funcionan

    Divide las librerías en cuatro tipos. Cada tipo tiene reglas claras de dependencia.

    1. Feature (feature-*)

    • Contiene smart components, rutas y orquestación UI.
    • Puede depender de data-access, ui y util.
    • Normalmente cargada lazy.

    2. UI (ui-*)

    • Componentes dumb, sin lógica de negocio.
    • Solo depende de ui y util.
    • Reutilizable y testeable.

    3. Data-access / Domain (data-access-* o domain-*)

    • Estado, API services, interfaces y Facades.
    • No debe depender de feature ni ui.

    4. Util (util-*)

    • Funciones puras, helpers, form validators.
    • Sin dependencias del resto del repo.

    Ejemplo de estructura en Nx:

    /apps
      /customer-portal
    /libs
      /booking
        /feature-search
        /data-access
        /ui-seat-map
      /payments
        /feature-checkout
        /data-access
      /shared
        /ui-kit
        /util-formatters
    

    Patrón Facade: amortiguador entre UI y estado

    El patrón Facade es la guardia de contención. Cualquier componente de feature consume una Facade, no la store o servicios HTTP directamente. Si cambias la implementación de estado, solo cambias la Facade.

    import { Injectable } from '@angular/core';
    
    @Injectable({ providedIn: 'root' })
    export class BookingFacade {
      readonly bookings = this.store.selectSignal(selectBookings);
      readonly loading = this.store.selectSignal(selectLoading);
    
      loadBookings() { this.store.dispatch(BookingActions.load()); }
      createBooking(data: BookingInput) { this.store.dispatch(BookingActions.create({ data })); }
    }
    

    El componente usa solo la Facade. Cambias NgRx por Signals o por otra librería y el componente sigue igual.

    Reglas de dependencia: automatiza la disciplina

    No confíes en la buena voluntad. Usa reglas de ESLint para imponer límites. Con Nx configuras depConstraints para que una ui nunca importe data-access, o que feature solo dependa de ui, data-access y util.

    {
      "depConstraints": [
        { "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:ui","type:util"] },
        { "sourceTag": "type:feature", "onlyDependOnLibsWithTags": ["type:ui","type:data-access","type:util"] }
      ]
    }
    

    Documentación: Module Boundaries

    Estrategias prácticas para migración y crecimiento

    • Start small: convierte primero librerías compartidas críticas a ui-* y data-access-*.
    • Facades on day one: crea facades incluso si hoy solo usan simple HTTP; evitarás reescrituras.
    • Tests per lib: cada librería con su suite de unit y e2e en CI. Nx hará caching de builds/tests.
    • Standalone components: adopta standalone: true para mejorar tree-shaking y simplificar imports (Standalone Components).
    • Evita SharedModule gigante: usa barrels (index.ts) y exportaciones controladas.

    Criterio técnico final

    Un proyecto Angular grande que no se descomponga se basa en tres cosas: límites explícitos, contratos estables (Facades/interfaces) y herramientas que refuerzan la arquitectura. Nx + Slicing vertical + Facades + ESLint = velocidad sostenible. No prometo milagros, pero sí reproducibilidad: equipos que crecen y siguen entregando sin que el repo se convierta en una fosa de hormigón.

    La próxima decisión es cultural: implantar y hacer cumplir estas reglas. Hazlo y tu código dejará de pelearse consigo mismo; tu equipo ganará espacio para resolver problemas reales.

    FAQ

     

    ¿Por qué agrupar por dominios en lugar de por tipo de archivo?

    Agrupar por dominios (slicing vertical) reduce el acoplamiento entre features y evita que cambios en una área afecten todo el repo. Cuando el equipo supera los dos desarrolladores, la organización por tecnología se vuelve difícil de mantener y ralentiza la entrega.

    ¿Qué tipos de librerías debo crear?

    Crea cuatro tipos: feature-* (orquestación UI y rutas), ui-* (componentes dumb), data-access-* o domain-* (estado, servicios API, facades) y util-* (helpers puros). Cada tipo tiene reglas claras de dependencia para mantener límites.

    ¿Qué es una Facade y cuándo usarla?

    Una Facade es una capa que expone una API estable para la UI y oculta la implementación del estado (NgRx, Signals, etc.). Úsala desde el inicio: permite cambiar la implementación interna sin tocar componentes presentacionales.

    ¿Cómo aplico reglas de dependencia con Nx?

    Configura depConstraints en la configuración de Nx para imponer qué tags pueden depender entre sí. Complementa con reglas de ESLint para bloquear importaciones directas no deseadas y automatizar la disciplina arquitectónica.

    ¿Debería adoptar standalone components ahora?

    Adoptar standalone: true mejora tree-shaking y simplifica imports, por lo que es una buena práctica a considerar. Evalúa el impacto en tu flujo y migra de forma incremental donde aporte más valor.

    ¿Cómo migrar un repo monolítico a este enfoque por dominios?

    Empieza por mover librerías compartidas críticas a ui-* y data-access-*. Crea Facades desde el primer día para encapsular dependencias. Añade pruebas por librería y configura depConstraints en Nx para evitar regresiones.

  • Qué es LangGraph y para qué sirve?

    Qué es LangGraph y para qué sirve?

    Tiempo estimado de lectura: 5 min

    • LangGraph modela agentes LLM como grafos de estado para soportar reintentos, checkpoints y flujos cíclicos.
    • Arquitectura basada en State, Nodes y Edges que permite persistencia y rutas condicionales.
    • Ideal para agentes que requieren autocorrección, aprobaciones humanas y orquestación multi-agente; no es la mejor opción para RAG lineal.

    Introducción

    LangGraph es una arquitectura y biblioteca pensada para convertir prototipos basados en LLM en sistemas operativos de IA: máquinas de estado con persistencia, rutas condicionales y capacidad de autocorrección. A diferencia de flujos lineales, LangGraph modela grafos dirigidos con estado compartido entre nodos, lo que facilita reintentos, aprobaciones humanas y trazabilidad en producción.

    Resumen rápido (lectores con prisa)

    LangGraph modela aplicaciones LLM como StateGraphs: un objeto de estado central que pasa por nodos (unidades de trabajo) y aristas (rutas condicionales). Es útil cuando necesitas reintentos, checkpoints, aprobaciones humanas y observabilidad en flujos complejos. No sustituye a LangChain; complementa su ecosistema para agentes stateful.

    Qué es LangGraph y para qué sirve?

    Qué es LangGraph y para qué sirve: en pocas palabras, LangGraph es la evolución arquitectónica para construir agentes de IA que necesitan razonar en ciclos, mantener estado persistente y coordinar múltiples acciones en producción. Si LangChain te ayuda a encadenar pasos (A → B → C), LangGraph te permite modelar grafos con bucles y condiciones (A → B → ¿volver a A? → C), lo que convierte prototipos en sistemas robustos y auditables.

    Qué es LangGraph: definición técnica y contexto

    LangGraph es una biblioteca del ecosistema LangChain orientada a modelar aplicaciones LLM como grafos dirigidos con estado (StateGraphs). Su objetivo no es sustituir a LangChain, sino ofrecer una abstracción para agentes stateful: nodos que transforman un objeto de estado central y aristas que dictan rutas condicionales o recurrentes. Documentación oficial: Documentación oficial y LangChain.

    ¿Por qué importa esto? Porque los agentes reales no funcionan con una sola pasada. Necesitan checkpoints, reintentos, intervención humana y la capacidad de inspeccionar y reactivar flujos largos. LangGraph incorpora esos elementos de forma nativa.

    Arquitectura: State, Nodes y Edges

    La arquitectura de LangGraph se resume en tres piezas principales que convierten al agente en una máquina de estados capaz de iterar y autocorregirse.

    Estado (State)

    Estado (State): un TypedDict/estructura que contiene todo el contexto del agente (historial de mensajes, resultados de herramientas, flags de control). El estado persiste y se pasa entre nodos.

    Nodos (Nodes)

    Nodos (Nodes): unidades de trabajo que reciben el estado, ejecutan lógica (llamadas a LLM, ejecución de tools, transformaciones) y devuelven una actualización del estado.

    Aristas (Edges)

    Aristas (Edges): definen el flujo. Pueden ser incondicionales o condicionales, permitiendo rutas distintas según el resultado (p. ej. error → corrección, éxito → siguiente etapa).

    Esta organización convierte al agente en una máquina de estados que puede iterar, corregirse y limpiar su plan a medida que avanza.

    Ejemplo mínimo (conceptual)

    from langgraph.graph import StateGraph, END
    
    class AgentState(TypedDict):
        messages: list
        attempts: int
    
    g = StateGraph(AgentState)
    g.add_node("think", call_model_node)      # produce propuesta en estado
    g.add_node("act", call_tool_node)         # ejecuta acción externa
    g.set_entry_point("think")
    
    # condicional: si falla, volver a "think" (loop); si OK, terminar
    g.add_conditional_edges("act", check_result, {"retry": "think", "done": END})
    
    app = g.compile(checkpointer=MemorySaver())

    Este patrón es el que permite, por ejemplo, que un agente escriba código, ejecute tests, lea errores y reescriba hasta que todo pase.

    Casos de uso reales y por qué elegir LangGraph

    LangGraph es la opción adecuada cuando tu aplicación requiere control fino y durabilidad. Es preferible cuando la lógica no cabe en una sola pasada y necesitas checkpoints, reintentos o pausas para intervención humana.

    • Agentes de auto-corrección (self-correcting): p. ej., un asistente que escribe y prueba código repetidamente.
    • Planificación y ejecución por etapas: descomposición de objetivos complejos en subtareas que se ejecutan y replanifican.
    • Flujos human-in-the-loop: pausas para aprobación humana manteniendo estado; crítico en entornos regulados.
    • Orquestación multi-agente: coordinar agentes especializados (investigador, verificador, redactor) que comparten estado y tareas.

    Para casos simples de RAG o chatbots lineales, LangChain/LCEL es más rápido de implementar; LangGraph entra cuando la lógica necesita reintentos, memoria o human approvals. Referencia: LangChain/LCEL.

    Integraciones, durabilidad y observabilidad

    LangGraph se integra con herramientas de almacenamiento y trazabilidad que son esenciales en producción.

    • Checkpointers/Recorders: MemorySaver, PostgresSaver, etc., permiten reanudar ejecuciones y persistir threads largos.
    • Observabilidad: integración con LangSmith (tracing, evaluaciones) para depurar y medir decisiones internas.
    • Vector stores y RAG: combina con Pinecone, Chroma o Weaviate cuando necesitas evidencia externa en nodos de razonamiento.
    • Orquestación externa: útil en pipelines con n8n para integrar herramientas empresariales.

    La durabilidad es la gran ventaja: si un proceso falla a mitad, LangGraph permite retomar desde el último checkpoint con el mismo objeto estado.

    Riesgos y consideraciones técnicas

    LangGraph añade complejidad. No es la herramienta para todo; evalúa trade-offs antes de adoptar.

    • Curva de aprendizaje: modelar grafos y estados correctamente exige diseño y pruebas.
    • Overhead: persistencia y checkpoints implican coste y latencia; mide y optimiza.
    • Seguridad: cualquier tool que ejecute código o acceda a datos sensibles debe estar sandboxeada y auditada.
    • Depuración: sin buenas métricas y trazabilidad (LangSmith u otras), los grafos cíclicos pueden volverse opacos.

    Tu criterio de adopción debe basarse en evidencia: prototipa en LangChain; si la solución requiere reintentos, memoria o human approvals, modela en LangGraph.

    Conclusión práctica

    LangGraph transforma agentes experimentales en sistemas operativos de IA: máquinas de estado con persistencia, rutas condicionales y capacidad de autocorrección. Úsalo cuando tus flujos necesiten reintentos, checkpoints y supervisión humana; queda corto para tareas lineales de RAG. Documentación y templates: Documentación oficial y repositorio.

    Empieza con un caso controlado (un agente que intenta, falla y reintenta una acción concreta), instrumenta trazabilidad y luego amplía a flujos multi-agente. Es así como pasas de “probar IA” a “operar IA” con seguridad.

    Dominicode Labs

    Si tu equipo trabaja en orquestación, automatización o agentes de producción, considera explorar plantillas y pruebas conceptuales en Dominicode Labs. Puede ser útil como punto de partida para validar patrones de checkpointing, trazabilidad y human-in-the-loop en proyectos reales.

    FAQ

     

    ¿En qué casos conviene usar LangGraph en lugar de LangChain puro?

    Usa LangGraph cuando tu flujo requiere reintentos, checkpoints, memoria persistente o aprobaciones humanas. Si tu aplicación es lineal (RAG simple o chat básico), LangChain/LCEL suele ser suficiente y más rápido de implementar.

    ¿LangGraph reemplaza a LangChain?

    No. LangGraph complementa el ecosistema LangChain: ofrece abstracciones para agentes stateful y grafos con condiciones. LangChain sigue siendo útil para pipelines lineales y muchas integraciones.

    ¿Cómo se persiste el estado en LangGraph?

    El estado se persiste mediante checkpointers/recorders como MemorySaver o PostgresSaver. Estos componentes permiten reanudar ejecuciones y almacenar el objeto de estado para procesos largos o interrumpidos.

    ¿Qué impacto tiene LangGraph en latencia y costes?

    La persistencia y checkpoints añaden overhead: más I/O y potencialmente mayor latencia. Es importante medir, optimizar y balancear la durabilidad frente al coste operativo.

    ¿Cómo manejar la seguridad al ejecutar tools desde nodos?

    Sandboxea y audita cualquier herramienta que ejecute código o acceda a datos sensibles. Implementa controles de acceso, validación de inputs y trazabilidad exhaustiva para cada ejecución de herramienta.

    ¿Puedo integrar vector stores y herramientas externas?

    Sí. LangGraph se integra con servicios como Pinecone, Chroma y Weaviate, y se puede orquestar con herramientas externas como n8n para flujos empresariales.

  • Cuándo no usar Effects en Angular para optimizar

    Cuándo no usar Effects en Angular para optimizar

    Tiempo estimado de lectura: 2 min

    • Usa effects para interaccionar con el mundo exterior (analítica, localStorage, librerías imperativas).
    • No uses effects para propagar estado dentro del grafo reactivo; usa computed() para relaciones derivadas.
    • Para cancelación/debounce o flujos temporales complejos, usa RxJS y la interoperabilidad (toObservable / toSignal).
    • Audita effects: la mayoría suele ser reemplazable por computed o RxJS para evitar bugs y renders extra.

    La pregunta aparece en casi todos los equipos que adoptan Signals: ¿usar effect() para todo porque “reacciona” a cambios? No. Entender cuándo (no) usar Effects en Angular y qué hacer en su lugar te evita bugs raros, renders extra y código que nadie quiere mantener.

    Resumen rápido (lectores con prisa)

    Qué es: effect() ejecuta código al cambiar signals que lee.

    Cuándo usar: para interacciones fuera del grafo (analytics, localStorage, librerías imperativas, timers con cleanup).

    Cuándo evitar: para propagar estado dentro de la app; usa computed() o RxJS según convenga.

    Cómo aplicarlo: usa computed() para derivadas y RxJS (toObservable/toSignal) para debounce/cancelación.

    Contexto: qué hace un effect

    Un efecto se ejecuta automáticamente cuando los signals que lee cambian. Eso lo hace útil para side effects: analytics, localStorage, llamadas a librerías imperativas. Pero usarlo para sincronizar estados es un antipatrón. Si tu cambio en A debe producir un nuevo valor en B dentro de la app, usa una derivación, no un efecto.

    Documentación oficial: Documentación oficial
    Interop RxJS: Interop RxJS

    Antipatrones comunes (y por qué duelen)

    1) Sincronizar signals con effects

    Código que ves en todos lados:

    total = signal(0);
    
    constructor() {
      effect(() => {
        this.total.set(this.precio() * this.cantidad()); // antipatrón
      });
    }

    Problemas: escrituras desde effect requieren flags especiales, ocultan la relación entre datos y disparan detecciones de cambio y re-ejecuciones innecesarias.

    2) Resetear formularios desde effects

    Un ID cambia y un effect resetea el form. Funciona, pero la lógica queda dispersa: ¿dónde está la verdad del flujo? Difícil de testear, propenso a race conditions.

    3) Intentar manejar debounce/switchMap dentro de effects

    Effects no reemplazan a RxJS. Si necesitas cancelación, debounce o switchMap, acabarás reinventando operadores o creando fugas.

    Qué usar en su lugar: computed() para estado derivado

    Cuando B es función de A, declara esa relación.

    total = computed(() => precio() * cantidad);

    Ventajas:

    • Declarativo: defines qué es total, no cuándo actualizarlo.
    • Lazy: solo se recalcula si alguien lo lee.
    • Memoizado: evita trabajo innecesario.

    Esto reduce CD y hace el código explícito y testeable.

    Para flujos asíncronos complejos: RxJS + Signals

    Si tu flujo necesita debounce, cancelación o combinaciones complejas, usa RxJS y la interoperabilidad:

    • Convierte signal a observable: toObservable(signal)
    • Aplica operadores RxJS
    • Convierte a signal si lo necesitas en templates: toSignal(observable)

    Ejemplo type-ahead:

    const query$ = toObservable(querySignal);
    const results$ = query$.pipe(
      debounceTime(300),
      switchMap(q => httpClient.get(`/search?q=${q}`))
    );
    const resultsSignal = toSignal(results$, { initialValue: [] });

    Así no reinventas lógica de tiempo y mantienes señales limpias.

    Docs de interoperabilidad: Docs de interoperabilidad

    Dónde SÍ usar Effect: casos legítimos

    Usa effect cuando la acción produce algo fuera del grafo de Angular.

    • Logging y analíticas
      effect(() => sendEvent('filter-changed', { filter: filter() }));
    • Sincronizar con Browser APIs
      effect(() => localStorage.setItem('theme', theme()));
    • Librerías imperativas (Chart.js, Leaflet)
      effect(() => chart.update({ data: series() }));
    • Timers y observers con cleanup
      effect((onCleanup) => {
      const id = setInterval(() => poll(), 5000);
      onCleanup(() => clearInterval(id));
      });

    Si el resultado del effect no vuelve al estado de la app, está bien.

    afterRenderEffect y manipulación del DOM

    Para actualizar third-party widgets después del commit DOM, usa afterRenderEffect (evita layout thrashing y separa lectura/escritura). Útil para charts que requieren dimensiones reales del canvas antes de renderizar.

    Regla práctica y checklist rápido

    • Si vas a llamar a .set() o .update() sobre otro signal desde un effect: para. Usa computed o cambia la source-of-truth.
    • Si necesitas debounce, cancelación o combinaciones temporales: usa RxJS (toObservable/toSignal).
    • Si el cambio sale de Angular (localStorage, analytics, DOM imperativo): effect.
    • Audita effects: más del 70–90% deberían ser reemplazables por computed o RxJS.

    Adoptar esta disciplina no solo mejora rendimiento; hace tu código predecible y testeable. En próximos artículos veremos cómo migrar patrones comunes de RxJS a Signals paso a paso, con ejemplos reales y pruebas unitarias.

    FAQ

    ¿Puedo usar effect para sincronizar dos signals?

    Técnicamente sí, pero es un antipatrón. Si B depende de A dentro de la app, define B como computed() en vez de escribirle desde un effect. Evitas flags especiales, renders adicionales y relaciones ocultas entre datos.

    ¿Por qué es mejor usar computed para valores derivados?

    Porque es declarativo, lazy y memoizado. Definís la relación entre datos y solo se recalcula si alguien lee el valor, lo que reduce detecciones de cambio y hace el código más testeable.

    ¿Cuándo debería introducir RxJS con signals?

    Cuando necesitas debounce, cancelación, switchMap o combinaciones temporales complejas. Convierte signals a observables con toObservable(), aplica operadores RxJS y, si hace falta, vuelve a signal con toSignal().

    ¿Qué pasa si un effect escribe en otro signal?

    Genera código frágil: requiere flags especiales, oculta la relación entre datos y puede provocar re-ejecuciones innecesarias y bugs difíciles de rastrear. Mejor cambiar la fuente de la verdad o usar computed.

    ¿Es acceptable resetear formularios con effects?

    Funciona en muchos casos, pero dispersa la lógica y dificulta testing. Puede introducir race conditions. Evalúa mover la lógica al flujo de datos principal o utilizar patrones que mantengan la fuente de la verdad clara.

    ¿Cuándo usar afterRenderEffect en lugar de effect?

    Usa afterRenderEffect para actualizar widgets o librerías que requieren dimensiones reales del DOM después del commit. Evita layout thrashing y separa lectura/escritura del DOM imperativo.

  • De “consumir tutoriales” a “facturar con código”: framework Dominicode para elegir proyectos que generen dinero

    De “consumir tutoriales” a “facturar con código”: framework Dominicode para elegir proyectos que generen dinero

    Tiempo estimado de lectura: 6 min

    • Construye para resolver un dolor que alguien pague. Usa B.R.E., flujo de caja, nicho y low‑code como filtros.
    • Automatiza tareas Boring, Repetitive, Expensive para justificar precios recurrentes.
    • Valida rápido con low‑code (n8n, Supabase, Stripe) y cobra desde la primera iteración.

    Introducción

    De “consumir tutoriales” a “facturar con código” no es un eslogan bonito; es la diferencia entre practicar y cobrar. Si tu próximo repo solo busca estrellas en GitHub, estás desperdiciando tiempo. Este framework Dominicode te dice cómo filtrar ideas antes de abrir VS Code y construir proyectos que realmente paguen facturas.

    Resumen rápido (lectores con prisa)

    Qué es: Un conjunto de filtros prácticos para elegir proyectos que generen ingresos.

    Cuándo usarlo: Antes de empezar a construir: valida si el problema es real y pagable.

    Por qué importa: Prioriza impacto directo en caja y facilidad de venta, reduciendo tiempo hasta el primer pago.

    Cómo funciona: Aplica B.R.E., evalúa proximidad al flujo de caja, valida nicho accesible y prueba con herramientas low‑code.

    Framework Dominicode

    La regla es simple: construye para resolver un dolor que alguien está dispuesto a pagar. No para probar la última librería. Aquí tienes cuatro filtros concretos que aplicamos en Dominicode para transformar ejercicios técnicos en ingresos reales.

    1 — Índice B.R.E. (Boring, Repetitive, Expensive)

    Busca tareas aburridas, repetitivas y caras. Si una tarea cumple estas tres, es candidata perfecta para automatizar.

    • Boring: nadie quiere hacerlo (ej. copiar datos de PDFs).
    • Repetitive: ocurre con frecuencia (diario, semanal, mensual).
    • Expensive: el coste humano supera tu solución.

    Ejemplo: agencias que gastan 3–4 horas por cliente en informes mensuales. Eso es B.R.E. Automatiza con n8n + APIs y cobras por el tiempo que quitas.

    2 — Proximidad al flujo de caja

    Prioriza problemas que tocan dinero directamente: ingresos, costes o riesgos legales. Si tu solución afecta la caja, venderla es mucho más fácil.

    • Generación de ingresos: recuperación de carritos, lead routing.
    • Reducción de costes: automatización de nóminas, conciliación.
    • Evitar multas: cumplimiento y backups automáticos.

    Si tu código evita una pérdida de 1.000€ al mes, justificar 200€/mes por tu servicio es trivial.

    3 — Nicho accesible y pagante

    No hay ventas masivas sin ventas primero. Define un nicho claro que puedas contactar hoy sin anuncios.

    • ¿Dónde están tus primeros 10 clientes? Google Maps, LinkedIn, directorios.
    • ¿Tienen presupuesto? Pymes con 5–50 empleados suelen pagar por soluciones que ahorran tiempo.

    Ejemplo: sistema de recordatorios por WhatsApp para clínicas dentales. Encuentras 50 clínicas en Google Maps, llamas a 10 y cierras 2. Eso es escalable.

    4 — Low-code first: validar rápido, iterar según demanda

    No construyas microservicios si no hace falta. Valida con herramientas que aceleran el time-to-pay:

    Si lo puedes montar con n8n + Supabase + Stripe en menos de una semana, lánzalo como servicio productizado y cobra. Refactoriza solo cuando tengas clientes reales.

    Cómo evaluar una idea en 10 minutos (Checklist rápido)

    1. ¿Es B.R.E.? (sí = 1)
    2. ¿Impacta flujo de caja? (sí = 1)
    3. ¿Puedo localizar a 10 clientes hoy? (sí = 1)
    4. ¿Se puede validar con low‑code en <20h? (sí = 1)

    Suma ≥3 → procede a prototipo y venta directa. Suma <3 → proyecto de aprendizaje.

    Caso práctico: reportes automáticos para agencias

    Problema: 4 horas/mes por cliente en informes (GA4 + Meta Ads + resumen humano).

    Aplicación del framework:

    • B.R.E.: sí.
    • Proximidad caja: sí (retención y ahorro de horas senior).
    • Nicho: agencias (accesible en LinkedIn).
    • Low-code: n8n + APIs + GPT para resumen → MVP en 48h.

    Producto: informes automáticos, entregados el día 1 de cada mes. Modelo de cobro: 200–300€/mes por agencia. Resultado: ingresos desde el primer cliente.

    Pila pragmática para facturar rápido

    No persigas “lo último”. Prioriza estabilidad y velocidad:

    Esta pila permite mover de idea a pago en días, no meses.

    De servicio productizado a Micro‑SaaS: ruta mínima viable

    • Service productizado: ofrece la solución y hazlo tú mismo. Cobro inmediato.
    • Template/boilerplate: vende la instalación a otros proveedores.
    • Micro‑SaaS: empaqueta la solución con onboarding y facturación recurrente.

    No esperes al producto perfecto. Cobra desde la primera iteración que entregue valor real.

    Cierre con criterio

    Consumir tutoriales está bien. Pero si quieres facturar con código, cambia la pregunta: “¿Qué problema puedo resolver hoy que alguien pague mañana?” Aplica B.R.E. + flujo de caja + nicho + low‑code antes de empezar a construir. Tu próximo repo no tiene que impresionar a desarrolladores: tiene que convencer a quien te paga.

    Construye algo feo, aburrido y que haga que alguien deje de perder dinero. Eso es facturar con código.

    Dominicode Labs

    Si te interesa prototipar soluciones de automatización y validar ideas rápidamente, consulta Dominicode Labs. Es una continuación lógica para quien quiere pasar de MVP manual a una oferta replicable sin perder foco en el time-to-pay.

    FAQ

     

    ¿Qué es el índice B.R.E. y por qué importa?

    El índice B.R.E. evalúa si una tarea es Boring (aburrida), Repetitive (repetitiva) y Expensive (cara). Si cumple las tres, suele ser rentable automatizarla porque hay incentivo claro para pagar por la automatización.

    Importa porque ayuda a priorizar problemas donde el valor entregado es inmediato y fácil de comunicar al cliente.

     

    ¿Cómo identifico si un problema impacta el flujo de caja?

    Busca si el problema afecta ingresos, costes o riesgo de multas. Pregunta a clientes cuánto tiempo o dinero pierden por el problema y calcula la pérdida mensual aproximada.

    Si la solución evita una pérdida significativa (por ejemplo 1.000€/mes), justificar un precio recurrente es sencillo.

     

    ¿Qué tipo de nicho debo buscar primero?

    Empieza con nichos accesibles donde puedas localizar a 10 clientes hoy: Google Maps, LinkedIn o directorios locales. Pymes de 5–50 empleados suelen ser buenos clientes iniciales.

    El objetivo es cerrar ventas directas antes de escalar con marketing pagado.

     

    ¿Cuándo debo dejar de usar herramientas low‑code?

    Mantén low‑code hasta que tengas clientes reales y patrones de uso claros. Refactoriza a una arquitectura más robusta cuando la complejidad y la escala lo demanden.

    La regla práctica: prioriza el time-to-pay; reescribe solo cuando el coste de mantener el MVP supere los beneficios.

     

    ¿Cómo fijo precio para un servicio productizado?

    Calcula el valor evitado (tiempo ahorrado, retención, evitación de multas) y oferta un precio recurrente que sea una fracción razonable de ese valor. Ejemplo: si ahorras 4 horas/mes de un senior, 200–300€/mes por agencia es coherente.

    Empieza con un precio simple y ajusta según feedback y métricas de retención.

     

    ¿Qué herramientas recomiendan para un MVP rápido?

    La pila recomendada incluye n8n para orquestación, Supabase o PostgreSQL para datos, OpenAI/Claude para texto, y Stripe para pagos. Para UI rápido, usa Retool o una simple app en Next.js.

    Para transcripción, utiliza Whisper (documentación en la guía de Whisper) o Deepgram.

  • De Angular 17 a Angular 21+: qué ha cambiado de verdad y qué debes migrar ya

    De Angular 17 a Angular 21+: qué ha cambiado de verdad y qué debes migrar ya

    Tiempo estimado de lectura: 4 min

    • Standalone Components reducen complejidad y mejoran tree-shaking importando dependencias directamente en componentes.
    • Signals aportan reactividad de grano fino: menos re-render y modelo más explícito.
    • Control de flujo nativo y @defer mejoran rendimiento de templates y carga diferida por bloque.
    • Migración por capas: standalone + control-flow primero; signals y zoneless después tras pruebas.

    Introducción

    ¿Tu app Angular sigue anclada a NgModules y zone.js? De Angular 17 a Angular 21+ el framework cambió de piel: no es una serie de parches, es una reescritura de prioridades. Este artículo explica qué cambió de verdad, por qué importa a nivel de rendimiento y arquitectura, y qué debes migrar ya sin reinventar todo.

    Resumen rápido (lectores con prisa)

    Standalone Components: componentes sin NgModules, importas dependencias en el componente y se mejora tree-shaking.

    Signals: reactividad de grano fino: signal(), computed(), effect().

    Control de flujo nativo y @defer: mejor rendimiento en templates y carga diferida por bloque.

    Zoneless: camino hacia detección de cambios sin zone.js, mejor SSR/hydration y menos overhead.

    De Angular 17 a Angular 21+: cambios que importan (y por qué)

    Angular dejó atrás la era donde los NgModules eran la única forma de estructurar una app. El objetivo ahora es reducir el bundle inicial, minimizar work wasted en render y dar al dev un modelo de reactividad más explícito.

    Standalone Components

    Qué: sustituyen a NgModules. Importas dependencias directamente en el componente y el compilador hace tree-shaking fino.

    Por qué importa: reducción de complejidad, bundles más pequeños y lazy loading más sencillo.

    Docs: guía de Standalone

    Signals

    Qué: reactividad de grano fino que reduce la necesidad de recorrer el árbol entero para detectar cambios. signal(), computed(), effect() son la base.

    Por qué importa: menos re-render, estado más predecible y preparación para zoneless.

    Docs: Signals

    Control de flujo nativo en templates

    Qué: @if, @for, @switch reemplazan a *ngIf/*ngFor, con mejor performance y type narrowing.

    Por qué importa: rendimiento inmediato en renderizados y listas grandes; mejora la experiencia de desarrollo.

    Docs: Control Flow

    @defer

    Qué: carga diferida a nivel de bloque en la plantilla.

    Por qué importa: cargas sólo el JS cuando el usuario lo necesita, no por ruta completa; mejora Core Web Vitals.

    Zoneless

    Qué: camino hacia la detección de cambios sin zone.js, disponible experimentalmente y consolidándose en 20/21.

    Por qué importa: menos overhead y SSR/hydration más limpio.

    ¿Qué migrar ya? Prioridad práctica y razones

    No necesitas reescribir todo. Migrar con criterio minimiza riesgo y maximiza ganancia.

    1) Migración a Standalone — Prioridad Alta

    Por qué: reducción de complejidad, bundles más pequeños y lazy loading más sencillo.

    Cómo: usa las schematics oficiales y migra componentes compartidos primero.

    Guía: guía de Standalone

    2) Control de flujo en plantillas — Prioridad Alta

    Por qué: rendimiento inmediato en renderizados y listas grandes; casi siempre seguro migrar.

    Cómo: ng generate @angular/core:control-flow. Beneficio inmediato en DX y perf.

    Docs: Control Flow

    3) Aplicar @defer en componentes pesados — Prioridad Media

    Por qué: mejora Core Web Vitals (LCP/INP) sin reestructurar rutas.

    Dónde: dashboards, charts, modales pesados.

    Tip: combina con placeholders para UX fluida.

    4) Adoptar Signals progresivamente — Prioridad Estratégica

    Por qué: preparas tu codebase para zoneless; menor re-rendering y estados más previsibles.

    Cómo: en nuevo código, usa signal() para estado local; usa toSignal() para bridge con RxJS cuando necesites streams.

    Docs: Signals

    5) Zoneless — Prioridad Condicional (Post-setup)

    Por qué: mayor rendimiento y mejor SSR/hydration.

    Cuándo: cuando tu app ya sea standalone y use signals; aplica tests E2E exhaustivos antes de cortar zone.js.

    Ejemplo rápido: de BehaviorSubject a Signal

    Antes (RxJS):

    private items$ = new BehaviorSubject<Item[]>([]);

    Después (Signals):

    const items = signal<Item[]>([]);

    No es un trámite estético: cambia cómo Angular calcula la actualización del DOM.

    Señales de alarma: cuándo no usar nativamente Signals todavía

    • Workflows complejos de RxJS (combinaciones, debounce, backpressure) siguen siendo RxJS.
    • Librerías de terceros que dependen de zones o async pipe pueden requerir adaptación.
    • Workflows gigantes en templates (30+ nodos lógicos) mejor refactorizar a código.

    Herramientas y enlaces prácticos

    Hoja de ruta breve para equipos (4 pasos, pragmático)

    1. Ejecuta migración a Standalone en módulos compartidos y rutas lazy.
    2. Aplica control flow en las plantillas (schematic). Corre pruebas de UI.
    3. Introduce @defer en componentes no críticos para mejorar LCP.
    4. En nuevos componentes usa Signals; planifica migraciones en servicios críticos tras benchmarking.

    Conclusión

    Angular 21+ no es un “upgrade menor”. Es una plataforma más modular, más rápida y lista para SSR/Hydration moderno. No lo intentes todo de golpe: migra por capas. Empieza por standalone y control-flow —es el menor riesgo con mayor retorno— y luego adopta signals como estrategia para preparar tu app para un futuro zoneless.

    FAQ

    ¿Debo migrar toda la app a Standalone de inmediato?

    No. Migra por capas: empieza por módulos compartidos y rutas lazy donde el impacto es mayor y el riesgo contenido. Usa las schematics oficiales para automatizar el proceso y validar por partes.

    ¿Signals reemplazan RxJS completamente?

    No. Signals son excelentes para estado local y reactividad fina. Sin embargo, workflows complejos de RxJS (combinaciones, debounce, backpressure) siguen siendo más apropiados con RxJS. Usa toSignal() para integrar ambos mundos.

    ¿Qué ventaja práctica aporta el control de flujo nativo?

    Reduce trabajo innecesario al re-renderizar, mejora type narrowing en templates y ofrece mejor rendimiento en listas y vistas condicionadas. La migración suele ser segura y con mejoras inmediatas en render.

    ¿Cuándo aplicar @defer en mi app?

    Aplica @defer en componentes pesados y no críticos para mejorar LCP: dashboards, gráficos y modales que no son necesarios en el primer pintado. Combínalo con placeholders para no romper la UX.

    ¿Es seguro eliminar zone.js ahora mismo?

    Es condicional: sólo cuando tu app esté mayormente standalone y haya adoptado signals. Antes de eliminar zone.js, corre pruebas E2E completas y verifica compatibilidad con librerías de terceros.