Author: Dominicode

  • Cuándo usar WebMCP y MCP: diferencias y criterios técnicos

    Cuándo usar WebMCP y MCP: diferencias y criterios técnicos

    Cuándo usar WebMCP y MCP: diferencias, casos de uso y criterio técnico

    Tiempo estimado de lectura: 5 min

    • Idea clave: MCP expone datos y herramientas de forma programática; WebMCP aporta semántica de UI para agentes en navegador.
    • Idea clave: Si la tarea vive en datos/APIs usa MCP; si vive en la interfaz web y necesita lógica visual, añade WebMCP.
    • Idea clave: La mayoría de flujos reales cruzan ambas capas; diseñar límites, autorización y auditoría evita inconsistencias.
    • Idea clave: Evita usar WebMCP como scraping del DOM o exponer operaciones sensibles solo vía WebMCP.

    Introducción

    Cuándo usar WebMCP y MCP es la pregunta que aparece en equipos de producto y arquitectura tras el anuncio de la vista previa de WebMCP. No es una elección técnica trivial: ambos protocolos resuelven capas distintas del mismo problema. Elegir el adecuado requiere entender la responsabilidad que vive la tarea del agente: datos o interfaz.

    Referencias útiles: Anthropic — documentación general, Claude (Anthropic)

    Resumen rápido (lectores con prisa)

    MCP es el protocolo para exponer datos y herramientas a modelos en backends y servicios automatizados. WebMCP añade semántica sobre la UI en el navegador para que agentes entiendan acciones y flujos visuales. Usa MCP para operaciones centradas en datos/APIs; usa WebMCP cuando la tarea requiere comprensión de la interfaz.

    Cuándo usar WebMCP y MCP: el criterio básico

    MCP (Model Context Protocol) y WebMCP no son intercambiables. Cada uno atiende una capa distinta:

    • MCP es el protocolo de contexto y herramientas pensado para exponer datos y capacidades de manera programática a modelos. Funciona en backends, pipelines y servicios automatizados.
    • WebMCP es una capa específica para navegador que añade semántica a la UI: explica qué hace cada elemento, qué secuencias son válidas y qué intenciones soporta la interfaz.

    Regla simple: si la tarea “vive” en datos y APIs, usa MCP. Si la tarea “vive” en la interfaz web y requiere comprender lógica visual o flujos humanos, añade WebMCP. La mayoría de flujos reales cruzan ambas capas; entonces ambos protocolos deben coexistir en la arquitectura.

    ¿Qué problema resuelve cada uno?

    MCP

    • Acceso programático a recursos: bases de datos, APIs, servicios internos.
    • Orquestación de herramientas y ejecución de operaciones sin interfaz (CI, backend, bots).
    • Reutilización: un mismo MCP sirve a agentes en CLI, servidores y apps.

    WebMCP

    • Traducción de UI a semántica: campos, opciones, relaciones de pasos.
    • Desambiguación de acciones visuales que el DOM por sí solo no comunica (¿este botón envía o guarda? ¿este panel es lectura o edición?).
    • Permite que un agente interactúe de forma segura con la experiencia de producto sin “adivinar” intenciones.

    Piensa en MCP como el sistema central de información y herramientas; WebMCP es el manual semántico de la tienda que obliga al agente a seguir los caminos definidos por el equipo de producto.

    Casos de uso concretos

    MCP es la opción cuando:

    • El agente debe crear eventos en calendarios corporativos, leer CRM, actualizar inventarios o ejecutar jobs en un pipeline.
    • Automatizas procesos en n8n, Airflow o sistemas de backend donde la UI no existe.
    • Necesitas consistencia de datos y gobernanza centralizada (auditoría, permisos, logs).

    WebMCP encaja cuando:

    • El agente necesita completar formularios complejos dentro de tu app SaaS (onboarding, configuración avanzada).
    • El producto es la UI y quieres que agentes de navegador ejecuten acciones sin romper flujos o introducir comportamientos no contemplados.
    • Quieres exponer ayuda contextual, tooltips y secuencias válidas para que el agente no interprete el DOM de forma errónea.

    Ejemplo real: “Programar una reunión”

    MCP se usa para consultar calendarios, proponer franjas y crear el evento en el backend. WebMCP se usa para verificar y ajustar preferencias visuales del usuario en el panel de configuración del calendario dentro del sitio (zona horaria específica, visibilidad de invitados).

    Arquitectura práctica: cómo combinarlos

    No se trata de elegir uno u otro, sino de diseñar límites claros:

    1. Define los contratos MCP — qué operaciones y datos expone el backend (endpoints, credenciales, scopes).
    2. Publica una capa WebMCP que mapea componentes UI a acciones seguras y semánticas (ej. checkout.submit, profile.update).
    3. Diseña autorización cruzada: acciones WebMCP deben validar permisos contra MCP antes de ejecutar cambios críticos.
    4. Registra y audita: todo cambio iniciado por WebMCP que afecte datos sensibles pasa por MCP para persistencia y trazabilidad.

    Esto evita dos errores frecuentes: agentes que manipulan la UI sin persistir cambios en backend, y agentes que modifican datos sin respetar la UX (rompiendo expectativas de usuario).

    Checklist de arquitectura antes de implementar

    • ¿La tarea requiere acceso a datos centralizados o a la UI? (MCP vs WebMCP)
    • ¿Tienes contratos claros y versionados en MCP? (endpoints, scopes, rate limits)
    • ¿Has documentado la semántica UI en WebMCP? (acciones autorizadas, flujos válidos)
    • ¿Existe validación cruzada entre WebMCP y MCP para evitar inconsistencias?
    • ¿Se auditan todas las acciones ejecutadas por agentes en una capa centralizada?

    Riesgos y anti-patrones

    • Tratar WebMCP como atajo para “scraping” del DOM. Sin semántica declarada, el agente va a fallar con cambios menores de UI.
    • Exponer operaciones sensibles solo vía WebMCP sin pasar por MCP: pérdida de control y trazabilidad.
    • Diseñar agentes que dependen de un único protocolo cuando las tareas cruzan capas; eso genera fragilidad.

    Conclusión técnica

    Cuándo usar WebMCP y MCP no es una disputa de protocolos: es diseño de responsabilidades. MCP administra datos y herramientas de forma agnóstica al entorno; WebMCP aporta la semántica que convierte interfaces humanas en elementos ejecutables y seguros para agentes. Diseña ambos desde el inicio cuando tus agentes deben operar en el mundo real: la combinación adecuada reduce errores, mejora la experiencia del usuario y protege los invariantes del sistema.

    Para equipos interesados en experimentar con patrones de integración entre agentes, UI y backend, una continuación natural es explorar prototipos y guías prácticas en Dominicode Labs. Allí se pueden encontrar ejemplos de contratos MCP y mapeos WebMCP aplicables a flujos reales.

    FAQ

    ¿Qué es MCP y para qué sirve?

    MCP (Model Context Protocol) es un protocolo para exponer contexto, datos y operaciones a modelos de forma programática. Sirve para orquestar herramientas, acceder a recursos y ejecutar operaciones en backend sin depender de la UI.

    ¿Qué es WebMCP y cuándo debería usarlo?

    WebMCP es una capa semántica para navegador que describe la intención y el comportamiento de elementos de UI. Debes usarlo cuando agentes en el navegador necesiten entender flujos visuales, acciones permitidas y relaciones entre campos.

    ¿Puedo usar solo WebMCP para todo?

    No. WebMCP no reemplaza la necesidad de un backend bien gobernado. Exponer operaciones sensibles únicamente vía WebMCP lleva a pérdida de gobernanza y trazabilidad. WebMCP y MCP deben coexistir según las responsabilidades.

    ¿Cómo debo gestionar permisos entre WebMCP y MCP?

    Diseña validaciones cruzadas: cualquier acción iniciada desde WebMCP que afecte datos sensibles debe validar permisos contra MCP antes de persistir. Mantén contratos claros y scopes en MCP para control centralizado.

    ¿Qué errores debo evitar al diseñar agentes que interactúan con la UI?

    Evita depender del scraping del DOM sin semántica, exponer operaciones críticas solo en la capa de UI y diseñar agentes acoplados a un único protocolo cuando la tarea cruza capas.

    ¿Dónde encuentro documentación adicional sobre protocolos y agentes?

    Consulta la documentación oficial de proveedores y recursos sobre diseño de agentes y contratos API. En este artículo se citaron las guías de Anthropic — documentación general y la página de Claude (Anthropic) como puntos de partida.

  • Automatiza tareas avanzadas con Claude Code sin programar

    Automatiza tareas avanzadas con Claude Code sin programar

    Claude Code no es solo para devs: 3 cosas que puedes hacer sin escribir una línea de código

    Tiempo estimado de lectura: 4 min

    • Claude (modelo) permite resolver problemas técnicos sin instalar herramientas.
    • Puedes orquestar flujos en n8n, transformar datos y generar documentación técnica sin escribir JS.
    • La diferencia clave: la CLI modifica repos locales; el modelo responde a instrucciones conceptuales.

    Introducción

    Claude Code no es solo para devs: 3 cosas que puedes hacer sin escribir una línea de código. Si leíste “CLI” y cerraste la pestaña, vuelve. La herramienta CLI existe y exige terminal, Git y permisos. Pero el ecosistema Claude —el modelo razonador accesible vía web y API— te permite resolver problemas técnicos reales sin teclear una sola línea de código. Aquí explico cómo, con ejemplos y enlaces para que lo pruebes.

    Resumen rápido (lectores con prisa)

    Qué es: Claude Code (CLI) es una herramienta para desarrolladores; Claude como modelo es un motor de razonamiento accesible vía web y API.

    Cuándo usarlo: Usa la CLI cuando necesites que un agente toque tu repo y ejecute tests; usa el modelo web/API para diseño de flujos, limpieza de datos, diagramas o specs.

    Por qué importa: Si sabes describir un problema técnico con precisión, puedes extraer valor sin instalar nada.

    Cómo funciona: El CLI actúa sobre repos locales; el modelo responde a instrucciones bien formuladas y genera artefactos accionables.

    1) Orquestar automatizaciones avanzadas en n8n — sin tocar JS

    Problema común

    Un webhook llega con JSON irregular y el flujo se rompe. Solución habitual: pedir a un dev un snippet de JavaScript. Alternativa real: usar Claude.

    Qué pedirle al modelo

    • “Este es el payload (pega ejemplo). Necesito extraer user.id, normalizar created_at a ISO y crear un campo active (true/false) según status. Dame el fragmento listo para pegar en un Code Node de n8n.”

    Qué obtendrás

    • Código listo para pegar que itera arrays, maneja nulos y transforma fechas.
    • Instrucciones de configuración del nodo HTTP (headers, auth).
    • Un plan de manejo de errores: retry con backoff exponencial, alertas en caso de 500/429.

    Por qué importa

    Reduces la fricción de integración y acortas el tiempo desde idea a flujo en producción. Documentación n8n: Documentación n8n

    2) Transformar datos y generar consultas SQL sin abrir una hoja de cálculo

    Caso real

    Recibes CSV/XML legacy y necesitas convertirlo a un esquema usable o sacar métricas complejas.

    Lo que puedes pedir

    • “Toma estas 50 filas (pega muestra). Genera un JSON Schema y un script de transformación (pseudocódigo) que normalice fechas, campos anidados y valores por defecto.”
    • “Explícame la regex para extraer IDs que empiezan por TX- seguido de 8 dígitos.” (Ejemplo: TX-\d{8})

    Qué devuelve Claude

    • JSON Schema validado y reglas de transformación.
    • Consultas SQL optimizadas para tu motor (Postgres, BigQuery), con JOINs, window functions y filtros temporales.

    Por qué importa: eliminas horas de limpieza manual y reduces errores humanos en pipelines de datos. Si necesitas precisión, adjunta la estructura de tablas y el motor SQL para que la query sea ajustada.

    3) Generar documentación técnica y diagramas antes de la implementación

    Usos prácticos

    • Describe el flujo de registro y pide código Mermaid.js para un diagrama de secuencia; pega el resultado en Notion o GitHub y obtén el gráfico inmediato. (Mermaid.js (diagramas))
    • Describe un endpoint (inputs, outputs, errores) y pide un contrato OpenAPI/Swagger listo para revisión. (OpenAPI Spec)
    • Pide un mapa de infraestructura cloud (colas, bases, funciones) con puntos de fallo y recomendaciones de mitigación.

    Qué ganas

    Conversaciones técnicas más cortas, menos malentendidos y decisiones con criterios concretos en lugar de intuición.

    Cómo decidir: CLI o modelo web/API

    Usa Claude Code (CLI) cuando quieras que un agente toque tu repo, ejecute tests o refactorice código localmente. Requiere desenvoltura con terminal y control de versiones.

    Usa Claude Web/API o integraciones (p. ej. n8n) cuando necesites diseño de flujos, limpieza de datos, diagramas o specs. Necesitas claridad conceptual, no sintaxis.

    La ventaja real no es escribir código: es estructurar problemas. Si puedes describir el estado actual, los invariantes y el resultado esperado, Claude lo transforma en artefactos técnicos accionables.

    Dominicode Labs

    Si quieres explorar integración práctica de automatizaciones y artefactos generados por IA en procesos de ingeniería, considera profundizar con recursos adicionales en Dominicode Labs. Es una continuación lógica para llevar los fragmentos y especificaciones que genera Claude hacia pruebas reproducibles y gobernanza de despliegue.

    FAQ

    ¿Necesito instalar algo para usar Claude como modelo?

    No. Claude como modelo está accesible vía web y API, por lo que puedes usarlo sin instalar la CLI ni herramientas locales.

    ¿Cuándo debo preferir la CLI de Claude?

    Prefiérela cuando necesites que un agente modifique repositorios locales, ejecute tests o interactúe con tu entorno de desarrollo. Requiere terminal y control de versiones.

    ¿Puedo usar Claude para generar código listo para n8n?

    Sí. Puedes pedir fragmentos listos para pegar en Code Nodes, junto con configuración HTTP y planes de manejo de errores.

    ¿Claude puede generar consultas SQL optimizadas?

    Sí. Claude devuelve queries ajustadas por motor (Postgres, BigQuery) incluyendo JOINs, window functions y filtros temporales si proporcionas la estructura de tablas.

    ¿Es seguro usar el modelo para datos sensibles?

    El artículo no añade recomendaciones de seguridad concretas, pero sugiere integrar artefactos en procesos de gobernanza y despliegue para que la automatización sea segura y mantenible.

    ¿Dónde encuentro documentación oficial de Claude y herramientas relacionadas?

    Referencias citables en el artículo: Anthropic – Claude Code overview, Claude (Anthropic), n8n (automatización), Mermaid.js (diagramas) y OpenAPI Spec.

  • Cómo migrar tu código a TypeScript 6.0 con `strict` activado

    Cómo migrar tu código a TypeScript 6.0 con `strict` activado

    ¿Tu repo se va a romper cuando actualices a TypeScript 6.0? Probablemente sí. Si no, felicidades: estás en el 10% que ya hacía las cosas bien.

    Tiempo estimado de lectura: 7 min

    • Strict: true en TS 6.0 es un cambio por defecto que destapará muchos errores latentes.
    • Activa banderas gradualmente (noImplicitThis, useUnknownInCatchVariables, noImplicitAny, etc.) y haz PRs pequeños.
    • Plan de migración de 6 semanas práctico y enfocado para repos medianos.
    • Herramientas y atajos (skipLibCheck, unknown vs any, validadores) para acelerar sin sacrificar seguridad.

    Introducción

    Poca gente habla de esto en voz alta: “strict: true por defecto en TS 6.0” no es una actualización menor. Es un detector de mierda técnica. Es un escáner que va a mostrar lo que has estado ignorando con // TODO: arreglar después.

    Esto no es moralina. Es ingeniería. Y sí: te voy a dar el plan para sobrevivir sin incendiar el CI.

    Resumen rápido (lectores con prisa)

    Strict: true en TS 6.0 activa varias banderas que convierten advertencias silenciosas en errores compilables. Migrar requiere activar banderas por fases, priorizar endpoints/serializadores y usar atajos como skipLibCheck y unknown para avanzar sin bloquear el desarrollo.

    Primero, lo obvio (pero que nadie quiere admitir)

    • Si ya tienes “strict”: true: no cambia nada.
    • Si no lo tenías, al actualizar a TS 6.0 el compilador se pondrá estricto y te lloverán errores.
    • Puedes parchear rápido con “strict”: false en tsconfig, pero eso es anestesia. No arregla la enfermedad.

    strict no es una única regla. Es un paraguas que enciende varias banderas. Cada una toca piezas distintas del código y cada una duele distinto.

    Piensa en strict como un filtro de rayos X: te muestra huesos rotos que antes eran invisibles.

    Las banderas, qué hacen y por qué importan (con ejemplos reales)

    1) noImplicitAny — El fin de los `any` silenciosos

    Qué hace: hace error todo lugar donde TS no pueda inferir el tipo y quedaría any.

    Problema típico:

    const fn = (payload) => { return payload.name; }

    Antes: compila. Después: error.

    Arreglo:

    const fn = (payload: { name: string }) => payload.name;

    Por qué importa: porque any es una mentira que llega a producción sin avisar.

    2) strictNullChecks — La regla que evita el “Cannot read properties of undefined”

    Qué hace: separa null y undefined de otros tipos.

    Problema típico:

    function greet(name: string) { return name.toLowerCase(); }
    const maybeName: string | null = getName();
    greet(maybeName); // Boom si es null

    Arreglo:

    function greet(name: string) { return name.toLowerCase(); }
    const maybeName = getName();
    if (maybeName) greet(maybeName);

    O escribe la firma:

    function greet(name: string | null) { if (!name) return; return name.toLowerCase(); }

    Por qué importa: obliga a pensar en ausencia de datos. Nada peor que toLowerCase() explotando en prod.

    3) strictFunctionTypes — Contravarianza correcta

    Qué hace: comprueba que los tipos de parámetros en funciones sean seguros al asignarlas.

    Ejemplo peligroso:

    type Handler = (e: Event) => void;
    function specificHandler(e: MouseEvent) { /* usa e.clientX */ }
    const h: Handler = specificHandler; // antes ok, ahora error

    Arreglo: Alinea firmas o usa overloads. No metas handlers con supuestos concretos donde se espera generalidad.

    4) strictBindCallApply — Validación de bind/call/apply

    Qué hace: valida que los argumentos pasados con call/apply/bind casen con la firma original.

    Caso:

    function sum(a: number, b: number) { return a + b; }
    sum.call(null, "str", 3); // antes pasaba, ahora no

    Por qué importa: evita bugs raros cuando se manipula this y argumentos dinámicos.

    5) strictPropertyInitialization — Clases más predecibles

    Qué hace: exige inicializar propiedades o declararlas como posiblemente undefined.

    Antes:

    class Foo { value: number; constructor() {} }
    const f = new Foo(); console.log(f.value); // undefined

    Con la bandera activada: error en compilación. Arreglo:

    class Foo { value: number = 0; }

    O si la inicialización viene después:

    class Foo { value!: number } // use with care
    o
    class Foo { value?: number }

    6) noImplicitThis — Control del “this”

    Qué hace: si TS no sabe qué es this en una función, da error.

    Ejemplo:

    const obj = {
      x: 1,
      getX() { return this.x; }
    }
    const f = obj.getX;
    f(); // this es global — error ahora

    Arreglo: tipa this en la función:

    getX(this: { x: number }) { return this.x; }

    7) useUnknownInCatchVariables — catch seguro

    Qué hace: cambia catch (e) de any a unknown.

    Ejemplo:

    try { ... } catch (e) { console.log(e.message); } // ahora error

    Arreglo:

    try { ... } catch (e: unknown) {
      if (e instanceof Error) console.log(e.message);
    }

    Esto fuerza buenos patrones de manejo de errores y evita suposiciones peligrosas.

    Estrategia práctica: cómo migrar sin suicidarte

    No intentes arreglar 5.000 errores en un PR. Eso no es noble; es destructivo.

    Plan de 6 semanas (realista para repos medianos)

    Semana 0 — Preparación

    • Crea branch “ts-migration/strict”.
    • Añade CI que compile en strict y que no bloquee master todavía.
    • Si la migración debe ser suave, temporalmente coloca “strict”: false para que CI siga pasando mientras trabajas.

    Semana 1 — Protección inmediata

    • Activa noImplicitThis y useUnknownInCatchVariables.
    • Razon: cambios pequeños, refactors simples, impacto bajo.
    • Haz PRs pequeños. Revisa con atención en code owners.

    Semana 2 — Sanear `any`

    • Activa noImplicitAny.
    • Prioriza endpoints y parsers: handlers de API, converters, deserializadores.
    • Empieza a reemplazar any por tipos concretos o unknown + validación.

    Semana 3-4 — Funciones y propiedades

    • Activa strictFunctionTypes.
    • Refactoriza callbacks y firmas exportadas.
    • Activa strictPropertyInitialization y arregla inicializaciones en clases públicas.

    Semana 5 — El gran salto

    • Activa strictNullChecks.
    • Este es el más complicado. Reescribe funciones que asumían no-null implícito.
    • Amplía tests unitarios. Añade pruebas para casos nulos/undefined.

    Semana 6 — Finalización

    • Ejecuta npx tsc --noEmit en todo el repo.
    • Revisa errores residuales; limpia // @ts-ignore que se usó como parche.
    • Mueve strict a true en tsconfig principal y mergea.

    Comandos útiles que vas a repetir hasta odiarlos

    • Inicializar config: npx tsc --init
    • Compilar sin emitir: npx tsc --noEmit
    • Compilar un proyecto con build mode: npx tsc -b
    • Detectar errores en workspace monorepo: npx lerna exec -- npx tsc --noEmit (o tu equivalente)

    Trucos y atajos para acelerar la migración

    • Usa skipLibCheck: true mientras migras para no pelear con tipos de dependencias.
    • Introduce types o index.d.ts temporales sólo para las partes que bloquean.
    • Prefiere unknown a any y escribe validadores pequeños si es necesario.
    • Añade eslint rule para prohibir // @ts-ignore excepto con razón y ticket.

    Checklist mínimo antes de mergear a master

    • CI compila con strict: true.
    • Tests cubren entradas nulas y errores.
    • No any sin ticket que lo justifique.
    • Documentación de breaking changes en PR.
    • Plan de rollback (tag + branch) por si algo explota en prod.

    Errores comunes que vas a ver (y cómo solucionarlos rápido)

    • "Object is possibly 'null'." → Optional chaining o comprobación previa.
    • "Parameter implicitly has an 'any' type." → Añade tipo explícito o infiere con generics.
    • "Property has no initializer and is not definitely assigned in the constructor." → Inicializa o marca como optional.

    Metáfora corta: ¿por qué todo esto vale la pena?

    Poner strict por defecto es como obligar a instalar cinturón de seguridad en todos los coches nuevos. Al principio fastidia, pero salva vidas y reduce reclamaciones. Te obliga a pensar, a documentar y a dejar menos trampas para producción.

    Argumentos para convencer a la gerencia

    • Menos bugs en producción = menos on-call nocturno.
    • Código más legible = onboarding más rápido.
    • Mejores contratos = menos regresiones en refactors.

    Cuando NO migrar ahora

    • Repos monolíticos gigantes sin tests.
    • Sistemas que requieren compatibilidad inmediata con CommonJS profundo.
    • Migraciones sincronizadas de múltiples equipos donde el riesgo de romper pipelines es alto.

    Si caes en estos casos, planifica la migración, no la improvises.

    Cierre agresivo pero útil

    No es una moda. Es una obligación técnica. Si tu equipo no puede permitirse el tiempo de migrar, al menos que lo documente y tenga roadmap. No dejes el problema para “cuando tengamos tiempo” — ese cuando nunca llega.

    ¿Quieres una ayuda real y directa?

    Respóndeme con “MIGRAR MI REPO” y te envío:

    • Un script que crea el branch de migración,
    • Activa strict en un tsconfig de prueba,
    • Ejecuta npx tsc --noEmit,
    • Genera un reporte con los 100 errores más comunes ordenados por archivo y tipo.

    Esto no acaba aquí. Si lo ejecutas, empezaremos la siguiente nota: cómo redactar PRs pequeños para migraciones de types (plantillas incluidas). ¿Lo quieres?

    FAQ

    ¿Qué es “strict: true” en TypeScript 6.0?

    Es una configuración por defecto que habilita un conjunto de banderas (noImplicitAny, strictNullChecks, strictFunctionTypes, etc.) que convierten inferencias inseguras en errores de compilación.

    ¿Por qué voy a ver tantos errores al actualizar?

    Porque el compilador ahora detecta patrones que antes pasaban silenciosamente (usos implícitos de any, accesos a null, firmas incompatibles, etc.). Son errores latentes que la nueva configuración expone.

    ¿Puedo parchear temporalmente la migración?

    Sí: puedes mantener "strict": false en el tsconfig principal mientras trabajas en un branch de migración y añadir CI que compile en modo estricto sin bloquear master.

    ¿Cuál es la bandera que menos impacto tiene al principio?

    Activar noImplicitThis y useUnknownInCatchVariables suele tener impacto bajo y es buena para comenzar la limpieza sin grandes refactors.

    ¿UseUnknownInCatchVariables romperá mi manejo de errores actual?

    Puede exigir cambios si tu código asume que catch ofrece un objeto con propiedades específicas. La solución típica es tipar como unknown y validar con instanceof Error u otros guard checks.

    ¿Qué hacer con librerías de terceros con tipos rotos?

    Usa skipLibCheck: true temporalmente y/o añade types o index.d.ts auxiliares para las partes que bloquean la migración.

    ¿Cómo evitar PRs enormes durante la migración?

    Divide la migración en PRs pequeños por área funcional; prioriza endpoints y parsers; documenta los cambios y pide revisión por code owners.

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

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

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

    Tiempo estimado de lectura: 4 min

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

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

    Resumen rápido (lectores con prisa)

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

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

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

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

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

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

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

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

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

    useRef: dos usos, dos reglas

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

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

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

    Eventos del DOM: tipa cada handler

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

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

    Esto evita errores tontos como leer propiedades inexistentes.

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

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

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

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

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

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

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

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

    Siempre define displayName para facilitar debugging en React DevTools.

    Tips prácticos que cambian proyectos

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

    Checklist rápido antes de push

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

    Cierra con criterio

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

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

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

    FAQ

    ¿Por qué evitar React.FC?

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

    ¿Cuándo especificar el tipo en useState?

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

    ¿Cómo tipar correctamente useRef para DOM?

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

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

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

    ¿Es aceptable usar as en alguna situación?

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

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

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

  • Cómo implementar Spec-Driven Development con generación de código

    Cómo implementar Spec-Driven Development con generación de código

    Spec-Driven Development y la librería sin código: lecciones prácticas para equipos que usan IA

    Tiempo estimado de lectura: 4 min

    • Los tests y las especificaciones pasan a ser el activo estratégico principal.
    • Los agentes aceleran la prototipación, pero la última milla exige juicio humano y arquitectura.
    • Modularidad y contratos claros son imprescindibles para desarrollo paralelo con agentes.
    • Trátalo como diseño de comportamiento: invierte en especificaciones y suites de pruebas vivas.

    Spec-Driven Development con IA no es una moda; es una reordenación de prioridades. Cuando los agentes pueden generar sintaxis fiable, el verdadero valor deja de estar en el archivo .js o .rs y pasa a estar en la especificación y la suite de tests. Eso no lo hace más fácil: lo hace más exigente.

    Resumen rápido (lectores con prisa)

    Spec-Driven Development centra el valor en especificaciones y suites de tests para permitir que agentes generen implementaciones confiables. Útil cuando las specs y tests son completos; no sustituye el juicio humano en la última milla. Diseña módulos con contratos claros y valida invariantes del sistema.

    Spec-Driven Development y la librería sin código: qué es y por qué importa

    El experimento es simple y brutal. Publicas en GitHub una librería sin código: un README/markdown que define el comportamiento, cientos —o miles— de pruebas de conformidad y un prompt de instalación para que un agente genere el código. Drew Brunig y otros mostraron que eso funciona para problemas acotados y deterministas: el agente lee la spec, ejecuta tests y genera código que pasa las pruebas.

    Los ejemplos más ambiciosos han escalado esto: reimplementaciones de Bash en TypeScript, intérpretes de Python en Rust o intentos de compilar C usando agentes. Vercel, Anthropic y otros equipos han probado variantes de este enfoque; el patrón es claro: la implementación fluye si la especificación y la suite de tests son precisas.

    Fuentes: Anthropic, Vercel.

    Tres razones por las que esto cambia la arquitectura del equipo

    1) Los tests son tu nuevo activo estratégico

    El código generado es barato; las pruebas no. Todos los proyectos que escalaron partieron de suites de testing masivas ya existentes. Si quieres que agentes produzcan un sistema confiable, primero inviertes en definir con precisión cada comportamiento, cada caso borde y cada ambigüedad. Eso es trabajo intelectual, no texto que copia una IA.

    2) La velocidad inicial es real. La última milla, no tanto.

    Con suficientes agentes y presupuesto puedes alcanzar rápidamente un prototipo que pasa el 80–90% de pruebas. Pero los últimos porcentajes —casos borde, coherencia entre módulos, performance y seguridad— requieren arquitectura, diseño y juicio humano. Ahí los agentes tropiezan: arreglar un fallo local puede romper otro subsistema.

    3) La modularidad ya no es sólo bonita; es imprescindible

    Si vas a ejecutar múltiples agentes en paralelo, necesitas módulos con contratos claros y dependencias mínimas. Un sistema fuertemente acoplado multiplica regresiones y conflictos de merge. Diseñar para desarrollo paralelo es diseñar para agentes: interfaces estables, tests de contrato y boundaries claros.

    Qué aprenden los equipos grandes (ejemplos y síntesis)

    • Reutiliza suites de tests fiables cuando existan; son la fruta madura.
    • Divide el problema en paquetes pequeños y bien definidos que puedan implementarse y probarse de forma independiente.
    • Añade pruebas que validen propiedades transversales (invariantes del sistema), no sólo outputs unitarios. Las pruebas que capturan invariantes evitan que arreglos locales creen fallos sistémicos.
    • Mantén la especificación viva: la implementación te enseñará dónde la spec era ambigua. No es un fallo; es el flujo natural: la implementación mejora la spec.

    Historia y perspectiva académica no son decoración: Margaret Hamilton acuñó “software engineering” para evitar exactamente este problema —la complejidad que excede la capacidad cognitiva de una persona— y para recordarnos que el software es diseño de sistemas, no solo código (https://en.wikipedia.org/wiki/Margaret_Hamilton_(computer_scientist)).

    Cómo aplicar esto en tu equipo hoy (guía práctica)

    • Prioriza las pruebas de dominio antes de automatizar la generación. Invierte en casos reales y casos borde.
    • Diseña el repo como una colección de contratos y tests: cada módulo debe tener su spec y su suite independiente.
    • Automate CI con pruebas de contrato y pruebas de integración reducidas que se ejecuten en cada PR generado por un agente.
    • Establece guardrails: linters, análisis estático y políticas de seguridad que los agentes deben respetar.
    • Trátalo como arquitectura colaborativa: los PRs no solo corrigen código; corrigen intención. Revisa tests con la misma seriedad que revisarías código.

    Qué no esperar (y por qué el hype falla)

    No esperes que este enfoque elimine la necesidad de ingenieros senior. No lo hará. Lo que cambia es la naturaleza del trabajo senior: menos tipografía de código, más diseño de comportamiento, más política de pruebas y más pensamiento sistémico. Los agentes son amplificadores; sin criterio técnico, amplifican errores más rápido.

    No esperes soluciones mágicas para sistemas no deterministas: sistemas distribuidos, UI con estados complejos, políticas de seguridad o requisitos de latencia siguen necesitando diseño humano profundo.

    Conclusión

    Spec-Driven Development con IA es una herramienta poderosa, pero exige una reorientación: de escribir código a diseñar comportamientos verificables. El activo que deberías proteger no es el repo, sino la suite de pruebas y los contratos que definen tu dominio. Si empiezas hoy a convertir ambigüedades en tests, estarás construyendo la infraestructura que permite a los agentes realmente escalar tu producto sin destruirlo. Haz eso y la IA deja de ser un truco y pasa a ser una línea de producción fiable.

    Para equipos que exploran flujos de trabajo con agentes y automatización, puede ser útil revisar enfoques prácticos y herramientas en Dominicode Labs. Esto complementa la práctica de convertir especificaciones en suites de tests desplegables.

    FAQ

    Respuesta: Spec-Driven Development con IA es un enfoque donde la especificación y una suite de tests rigurosa son la fuente de verdad; agentes generan implementaciones que son validadas contra esas pruebas.

    Respuesta: Es apropiado para problemas acotados y deterministas donde puedes definir comportamientos y casos borde exhaustivamente. Funciona menos bien en dominios no deterministas sin especificaciones completas.

    Respuesta: No. Los agentes amplifican productividad, pero el trabajo senior evoluciona hacia diseño de comportamiento, arquitectura de pruebas y evaluación de trade-offs.

    Respuesta: Las suites de tests de dominio y las pruebas que validan invariantes transversales son las más valiosas. Tests de contrato e integración automatizados evitan que soluciones locales rompan el sistema.

    Respuesta: Diseña módulos con contratos estables, limita dependencias y ejecuta pruebas de contrato en CI para cada PR generado por un agente. Linters y análisis estático ayudan como guardrails.

    Respuesta: Anticipa limitaciones en casos borde, performance, seguridad y sistemas no deterministas. La última milla requiere diseño humano; no es una solución automática para todos los dominios.

  • Cómo sincronizar especificaciones, pruebas y código en el desarrollo

    Cómo sincronizar especificaciones, pruebas y código en el desarrollo

    El Triángulo del Desarrollo Dirigido por Especificaciones: cómo evitar gestionar un proceso de programación que superó la capacidad de manejo de un solo hombre. Ni siquiera de un equipo; de un solo hombre..

    Tiempo estimado de lectura: 5 min

    • Ideas clave:
    • El triángulo fundamental: especificación, tests y código deben mantenerse sincronizados.
    • Los agentes aceleran implementación pero introducen decisiones trazables que deben registrarse.
    • Herramientas como Plum extraen decisiones de diffs y traces para actualizar la spec y generar artefactos auditable.
    • Procesos claros (captura de traces, aprobación humana, sync en CI) son necesarios para evitar deuda técnica acelerada.

    El triángulo es simple y brutal: especificación, tests y código. Si uno se despega, el proyecto se rompe. So welcome: este artículo explica por qué el Spec‑Driven Development dejó de ser una ecuación lineal y cómo convertir ese triángulo en una práctica gobernable cuando agentes de IA escriben código.

    Resumen rápido (lectores con prisa)

    Qué es: Un enfoque que trata a la especificación, la suite de tests y el código como un triángulo que debe permanecer sincronizado.

    Cuándo usarlo: Cuando agentes (LLMs/automations) o equipos múltiples generan cambios rápidos y necesitas trazabilidad.

    Por qué importa: Para evitar deuda técnica acelerada y pérdida de intención por decisiones no documentadas.

    Cómo funciona (resumen): Captura diffs y traces, extrae decisiones, confirma con humanos y sincroniza spec↔tests↔código.

    El triángulo: Spec, Tests, Código — So welcome: por qué no basta con una spec

    So welcome: si piensas que subir una spec y soltar agentes en ella es todo lo que hace falta, estás confundiendo velocidad con control. La spec define qué debe pasar. Los tests validan. El código implementa y descubre cosas. Pero la implementación introduce decisiones —humanas y de IA— que permanecen en los traces. Si no capturas esas decisiones, la spec se queda atrás y el sistema deriva. Resultado: managing a coding process that grew beyond one man’s ability to manage. Not even a team, one man.

    ¿Por qué esto importa hoy?

    • Porque los agentes aceleran la implementación.
    • Porque la implementación revela ambigüedades que la spec no anticipó.
    • Porque los hotfixes y cambios urgentes suelen entrar directo al código y no a la spec.

    Si no sincronizas, la velocidad se vuelve deuda técnica exponencial.

    Señales que te indican que el triángulo está roto

    • Commits frecuentes sin cambios en la spec.
    • Pull requests que corrigen tests porque la spec no reflejaba decisiones recientes.
    • Conversaciones largas con el agente donde se tomaron decisiones y nadie las documentó.
    • Cobertura de tests alta en líneas, baja en intención (las pruebas no cubren los requisitos del producto).

    Estas señales son tangibles. Úsalas. Git te cuenta qué cambió. Los traces de los agentes (chats, prompts, respuestas) contienen las decisiones. Los tests te dicen qué se ejecuta. Cruza esas fuentes y tendrás diagnóstico.

    Plum — la plomada que mide la verticalidad del triángulo

    No es teoría: existen herramientas prácticas. Plum (sí, como plomada) busca las decisiones en los diffs y en los traces y las convierte en artefactos verificables. Flujo resumido:

    Plum: Flujo resumido

    1. Ejecutas commit.
    2. Plum lee los diffs y analiza los traces del agente.
    3. Extrae decisiones, las dedupea y te pide aprobación.
    4. Actualiza la spec (Markdown) según lo aprobado.
    5. Ejecuta sync y te muestra brechas spec↔tests↔código.

    Genera además un archivo .jsonl con el historial de decisiones: pregunta, decisión, autor (humano/LLM), rama, timestamps. Eso pasa de “intención perdida en Slack” a “artefacto auditable en el repo”.

    Plum: Instalación mínima

    Instalación mínima: pip install plum-dev. (Limitación actual: integrado con pytest; funciona mejor cuando la spec está por delante del código.)

    Prácticas para mantener el triángulo en sincronía

    • Escribe la spec como un contrato de comportamiento, no como un manifiesto aspiracional. Casos de borde incluidos.
    • Prioriza la suite de tests como activo estratégico: invierte en pruebas que describan la intención, no solo en asserts unitarios.
    • Trata los traces de agente como código: captúralos, régistralos y asócialos a commits.
    • En cada PR generado por agente: exige la checklist de decisiones aprobadas y la actualización del spec.
    • Añade pruebas de invariantes sistémicas (property tests) que detecten regresiones causadas por cambios locales.
    • Diseña módulos con contratos estables para permitir paralelismo de agentes sin colisiones.

    Qué no esperar de los agentes (y por qué necesitas humanos

    • No esperes que un LLM mantenga la visión de producto a largo plazo. Puede sugerir cambios documentales, pero la validación de negocio es humana.
    • No esperes que arreglen deuda técnica sistémica solos. Pueden parchar, pero no rediseñar la arquitectura sin dirección.
    • No esperes que la spec se actualice mágicamente: necesita decisiones aprobadas y trazables.

    Checklist rápido para equipos que van a integrar agentes

    1. Tener specs en Markdown rastreables en repo.
    2. Tener suite de tests ejecutable en CI (pytest u otro).
    3. Integrar captura de traces de agentes (logs/JSON).
    4. Añadir herramienta de reconciliación (ej. Plum) en el pipeline local/CI.
    5. Forzar aprobación humana de decisiones extraídas antes de merge.
    6. Ejecutar sync spec↔tests↔código en cada PR.

    Cierre (acción clara)

    Si tu equipo ya usa agentes y no tiene un proceso de reconciliación entre spec, tests y código, estás acelerando la creación de un legado ilegible. Haz esto hoy: instala plum‑dev, apunta la herramienta a tu spec y a tus tests, y corre plum sync en tu CI. Si no puedes hacerlo aún, al menos comienza a registrar las decisiones en cada PR. No es glamour. Es gobernanza. Y sin eso, la velocidad que prometen los agentes solo te dará más problemas.

    Haz clic aquí para empezar: pip install plum-dev y corre plum init en un repo con spec y pytest.

    Para equipos que integran agentes y workflows de automatización, una continuación natural es explorar recursos y prácticas en Dominicode Labs, donde se agrupan experimentos y herramientas relacionadas con reconciliación de specs, capture de traces y pipelines de pruebas.

    FAQ

    ¿Qué es el “triángulo” en Spec‑Driven Development?

    Es la idea de que especificación, tests y código forman un conjunto interdependiente. Si cualquiera de los tres se desincroniza, el proyecto corre riesgo de perder intención y acumular deuda técnica.

    ¿Por qué los agentes rompen la sincronía entre spec, tests y código?

    Porque aceleran la implementación y toman decisiones durante el desarrollo (en prompts, chats, respuestas) que a menudo no quedan reflejadas en la spec ni en los tests, creando discrepancias trazables en diffs y commits.

    ¿Qué hace Plum exactamente?

    Plum analiza diffs y traces de agentes, extrae decisiones, las dedupea, solicita aprobación y actualiza la spec en Markdown. También genera un archivo .jsonl con el historial de decisiones para auditoría.

    ¿Cómo debo tratar los traces de agentes?

    Captúralos y regístralos como artefactos vinculados a commits; trátalos como código: deben estar versionados, asociados a PRs y revisados por humanos para extraer decisiones verificables.

    ¿Qué requisitos mínimos necesito para integrar este flujo?

    Specs en Markdown rastreables, suite de tests ejecutable en CI (por ejemplo pytest), captura de traces (logs/JSON) e integración de una herramienta de reconciliación en el pipeline.

    ¿Quién debe aprobar las decisiones extraídas por herramientas automatizadas?

    Siempre un humano con responsabilidad de producto o arquitectura. Las herramientas extraen y proponen; la validación de negocio y la aprobación final deben ser humanas.

  • Cómo Angular 21 Optimiza la Asincronía Sin Zone.js

    Cómo Angular 21 Optimiza la Asincronía Sin Zone.js

    Sin Zone.js — Native Async/Await: Angular 21 y la arquitectura Zoneless

    Sin Zone.js — Native Async/Await es el cambio arquitectónico que Angular 21 trae para cerrar años de parches y trampas alrededor de la asincronía. En lugar de confiar en interceptores globales, el framework apuesta por Signals y async/await nativo, lo que redefine cómo se detectan cambios, cómo se escribe lógica asíncrona y cómo se depura una aplicación Angular a escala.

    Resumen rápido (lectores con prisa)

    Angular 21 elimina Zone.js y usa Signals como fuente de verdad. Usa async/await nativo sin envoltorios. Resultado: detección de cambios localizada, trazas de error más limpias y menos bundle inicial.

    Cuándo: migraciones planificadas por fases. Cómo: mover estado a signals y usar OnPush; auditar subscribes y callbacks externos.

    Tiempo estimado de lectura

    Tiempo estimado de lectura: 5 min

    Ideas clave

    • Signals reemplazan a Zone.js: reactividad explícita y renderizado localizado.
    • Async/await nativo: sin transpile ni parches que oculten trazas y comportamiento.
    • Mejoras medibles: bundle inicial más pequeño, trazas limpias y menos comprobaciones globales.
    • Migración requiere auditoría: refactor de suscripciones y adopción de OnPush y signals.

    Sin Zone.js — Native Async/Await: qué cambia y por qué importa

    Zone.js surgió como una solución pragmática: interceptar (monkey‑patch) APIs del navegador —setTimeout, fetch, Promises, addEventListener— para saber cuándo lanzar la detección de cambios. Funcionó, pero con costes claros: comprobaciones globales innecesarias, trazas de pila contaminadas y fricción con APIs modernas. Más aún, Zone.js no intercepta await a nivel de motor V8, lo que obligó a técnicas de transpile para mantener el comportamiento esperado.

    Angular 21 elimina esa capa. En su lugar, Signals actúan como la fuente de verdad: cuando un signal cambia, Angular calcula qué partes de la vista dependen de él y actualiza solo esas piezas. El async/await se usa tal cual lo diseñó ECMAScript —sin envoltorios ni polyfills— y el navegador ejecuta la asincronía de manera óptima.

    Documentación clave:

    Cómo funciona el modelo Zoneless en práctica

    La filosofía es simple: reactividad explícita y renderizado localizado. Un servicio realiza una llamada asíncrona con async/await, actualiza signals y la vista reacciona únicamente a esos cambios.

    Ejemplo: servicio con signals

    @Injectable({ providedIn: 'root' })
    export class UserService {
      readonly users = signal([]);
      readonly loading = signal(false);
    
      async loadUsers(): Promise {
        this.loading.set(true);
        try {
          const response = await fetch('/api/users');
          this.users.set(await response.json());
        } finally {
          this.loading.set(false);
        }
      }
    }

    Ejemplo: template con signals

    En el template, consumir signals es directo y elimina muchos patrones previos (pipes async, subscribes que mutan propiedades de clase):

    <if (loading()) {>
      <app-skeleton />
    <} @else {>
      @for (user of users()) {
        <app-user-card [user]="user" />
      }
    }

    Angular gestiona internamente las dependencias entre signals y templates; no necesitas Zone.js para “avivar” la vista.

    Beneficios técnicos medibles

    • Reducción del bundle: eliminar Zone.js suele suponer ~100 KB minificados menos en el bundle inicial, con impacto directo en LCP.
    • Trazas de error limpias: las stack traces muestran tu código, no los callbacks internos de Zone.js, lo que reduce tiempo de diagnóstico.
    • Rendimiento en runtime: eventos de alta frecuencia (scroll, mousemove) dejan de disparar comprobaciones globales; solo los cambios efectivos actualizan la UI.
    • Compatibilidad natural con APIs modernas: fetch, Web Streams, WebSockets y librerías modernas funcionan sin trampas de detección.

    Riesgos y puntos a auditar antes de migrar

    Eliminar Zone.js no es solo quitar un import. Patrones comunes que romperán incluyen:

    • Mutar propiedades de clase dentro de subscribe() esperando que la vista se refresque automáticamente.
    • Dependencia implícita en NgZone.run() para actualizaciones desde callbacks externos.
    • Componentes sin ChangeDetectionStrategy.OnPush que confían en comprobaciones globales.

    Identifica y refactoriza estos puntos antes de desactivar Zone.js.

    Estrategia práctica de migración

    Pasos prácticos recomendados para una migración segura y por fases.

    Checklist rápida

    1. Establece ChangeDetectionStrategy.OnPush en toda la base de componentes.
    2. Migrar estado de componentes a signal() y computed(). Empieza por componentes hoja.
    3. Reemplaza suscripciones RxJS que mutan estado por toSignal() (@angular/core/rxjs-interop) o por flujos que actualicen signals explícitamente.
    4. Ejecuta pruebas E2E y de accesibilidad; valida rendimiento en escenarios reales.
    5. Activa el modo zoneless (p. ej. provideExperimentalZonelessChangeDetection() o su equivalente estable) cuando el 100% del estado dependiente de la vista esté en signals.

    Checklist rápida para auditoría antes de activar zoneless:

    • Todos los componentes críticos usan OnPush.
    • No existen mutaciones de estado implícitas en subscribes.
    • CI ejecuta pruebas de integración que cubren flujos asíncronos.
    • Observabilidad en producción: trazas y métricas para comparar comportamiento pre/post migración.

    Conclusión: menos magia, más control

    Sin Zone.js — Native Async/Await no es una moda; es la materialización de un principio: reactividad explícita y alineada con las plataformas. Para equipos que priorizan rendimiento, claridad y una depuración más rápida, Angular 21 ofrece un modelo más predecible y eficiente.

    Empieza la migración por componentes críticos, automatiza las pruebas de integración y planifica la adopción en fases: la reducción de complejidad será visible y medible. En la próxima entrega publicaremos una checklist automatizable y scripts de migración para acelerar este proceso en proyectos reales.

    FAQ

    ¿Qué es exactamente lo que reemplaza a Zone.js en Angular 21?

    Signals y el uso de async/await nativo. Signals actúan como la fuente de verdad para dependencias y actualizaciones de vista.

    ¿Por qué las trazas de error mejoran sin Zone.js?

    Porque se elimina el monkey‑patching y las trampas que insertan callbacks intermedios en las stack traces; las trazas muestran código de la aplicación y no callbacks internos.

    ¿Qué patrones rompen al desactivar Zone.js?

    Patrones que mutan estado dentro de subscribe(), dependencia en NgZone.run() y componentes que esperan comprobaciones globales en lugar de OnPush y signals.

    ¿Cuánto se reduce el bundle al quitar Zone.js?

    Eliminar Zone.js suele suponer aproximadamente 100 KB minificados menos en el bundle inicial, según la observación mencionada en el artículo.

    ¿Cuál es el primer paso práctico para migrar?

    Establecer ChangeDetectionStrategy.OnPush en los componentes y mover estado a signal() y computed(), empezando por componentes hoja.

    ¿Debo activar el modo zoneless inmediatamente en producción?

    No. Activarlo cuando el 100% del estado dependiente de la vista esté en signals y después de haber ejecutado pruebas E2E y de integración que validen flujos asíncronos.
  • Cómo establecer una conexión eficiente en DuckDB para análisis de datos

    Cómo establecer una conexión eficiente en DuckDB para análisis de datos

    ¿Quieres procesar terabytes sin montar una base de datos gigante? Entonces escucha esto

    Tiempo estimado de lectura: 6 min

    • Conexión es arquitectura: la forma en que abres DuckDB define persistencia, permisos y paralelismo.
    • Memoria vs archivo: usa :memory: para ETL temporal y archivo.duckdb para persistencia compartida con control de escrituras.
    • No muevas datos innecesariamente: DuckDB puede leer Parquet/CSV directo desde disco o S3.
    • Concurrencia: lectura concurrente OK; escritura concurrente NO — diseña un writer único o locking.
    • Práctico: patrones para notebooks, CI, microservicios y agentes LLM.

    Introducción

    ¿Quieres procesar terabytes sin montar una base de datos gigante? Entonces escucha esto: you must first create a connection to a database. Sí, suena obvio. Pero en DuckDB esa línea es la puerta de entrada y la que decide si tu proyecto será veloz o un desastre con estilo.

    Voy a contarte cómo usar DuckDB desde esa perspectiva: la conexión no es un paso más. Es la arquitectura. Es la diferencia entre “funciona” y “funciona rápido y sin dramas”. Poca gente habla así. Aquí no hay postureo: te doy lo que sirve, cómo usarlo y qué evitar.

    Resumen rápido (lectores con prisa)

    DuckDB es un motor OLAP embebido similar a SQLite pero optimizado para análisis. Decide si usas :memory: (veloz, efímero) o un archivo .duckdb (persistente, cuidado con escrituras concurrentes). Lee Parquet/CSV directo desde disco y evita mover terabytes innecesariamente.

    Qué es DuckDB y por qué la conexión importa

    DuckDB es un motor OLAP embebido. Es como SQLite, pero para análisis masivo. Corre dentro del proceso, lee Parquet/CSV directo desde disco y procesa columnas en bloques vectorizados. No hablas por red con un servidor; abres una instancia dentro de tu app y le preguntas cosas.

    Por eso la regla número uno (deletréalo): you must first create a connection to a database. Esa conexión define:

    • si los datos sobreviven al reinicio (archivo .duckdb vs :memory:),
    • quién puede escribir y quién leer,
    • cuánto paralelismo y memoria va a usar tu proceso.

    Cómo crear esa conexión (rápido y práctico)

    Te doy recetas útiles. Párate un segundo en la que mejor encaje con tu stack.

    Python

    Instalación: pip install duckdb

    Conexión en memoria y persistente:

    import duckdb
    
    # Conexión en memoria (efímera)
    con = duckdb.connect(database=':memory:')
    
    # Conexión persistente (archivo en disco)
    con = duckdb.connect(database='mi_base.duckdb')

    Node.js

    Instalación: npm install duckdb

    Conexión:

    const duckdb = require('duckdb');
    
    // Conexión a archivo (o ':memory:')
    const db = new duckdb.Database(':memory:');
    
    // Ejecutar SQL
    db.run("CREATE TABLE prueba(a INTEGER, b VARCHAR)");

    CLI (rápido para pruebas)

    Desde terminal:

    duckdb mi_base.duckdb

    Y luego puedes correr SQL directo: SELECT * FROM read_parquet('datos.parquet');

    Nota: hay bindings para Rust, Go, R y otros. La idea es la misma: you must first create a connection to a database y decidir si será en memoria o persistente.

    Memoria vs persistencia: la decisión que cambia todo

    No es trivia. Es estrategia.

    :memory:

    • Ventaja: velocidad máxima, útil para ETL temporal, análisis ad-hoc, notebooks y agentes LLM que generan SQL y necesitan respuesta inmediata.
    • Desventaja: se pierde todo al cerrar el proceso.

    archivo.duckdb

    • Ventaja: persistencia; comparte estado entre ejecuciones (si lo permites).
    • Desventaja: concurrencia de escritura limitada; hay locking si varios procesos escriben.

    Regla práctica: si vas a procesar datos y luego descartarlos, usa memoria. Si necesitas guardar resultados para consultas posteriores, usa archivo. Pero si varios servicios escriben al mismo archivo, tendrás que diseñar una capa para evitar colisiones.

    Leer archivos sin importar tablas

    Aquí está la magia: no necesitas INSERTs. DuckDB puede consultar Parquet/CSV directamente desde la conexión.

    SELECT category, SUM(sales) total
    FROM read_parquet('s3://bucket/datos.parquet')
    GROUP BY category;

    Eso te salva horas y espacio. No muevas terabytes solo para filtrar una columna.

    Patrones de uso según casos reales

    • Notebook o análisis ad-hoc: conexión en memoria. Carga Parquet y explora con SQL.
    • ETL temporal en CI: conexión en memoria dentro del runner. Resultado exportado a CSV o Parquet.
    • Dashboard local o app de escritorio: archivo.duckdb para persistencia.
    • Microservicio que solo lee: muchos procesos pueden leer el mismo archivo sin problema.
    • Escrituras concurrentes: no pongas múltiples instancias a escribir en el mismo archivo. Mejor un microservicio responsable de escribir, o sincronización via colas.

    Concurrencia: dónde pica y cómo evitar sangre

    DuckDB está diseñado para lectura concurrente y un único escritor. Si dos procesos intentan escribir al mismo .duckdb, obtendrás locking y fallos.

    Estrategias:

    • Arquitectura con un escritor único: un servicio que reciba las operaciones de escritura y haga commits.
    • Locking a nivel de aplicación: si no puedes, usa un lock distribuido (Redis, etcd) antes de abrir la conexión de escritura.
    • Escribe a archivos temporales y luego mergea: cada proceso escribe su parquet y un job batch hace el merge en la base persistente.

    Performance y ajustes que realmente importan

    • Usa Parquet para entrada masiva: DuckDB lee columnas y se aprovecha de la compresión y esquema.
    • Paralelismo: DuckDB puede usar múltiples threads. Ajusta duckdb.set_config('threads', 'N') si tu binding lo permite.
    • PRAGMA options: hay settings para controlar memoria y spill-to-disk behavior.
    • Evita SELECT * en tablas enormes; especifica columnas.
    • Usa índices? DuckDB no usa índices tradicionales como OLTP; piensa en particionado y ordenamiento de input para lecturas eficientes.

    Integración con flujos de automatización y agentes LLM

    Si tu stack tiene n8n, Airbyte, o agentes que generan SQL dinámicamente, DuckDB es ideal.

    Ejemplo de flujo:

    1. Nodo descarga CSV desde S3.
    2. Nodo invoca DuckDB en memoria y ejecuta SQL para join y agregación.
    3. Nodo exporta resultado a Parquet y sube a S3.

    Para agentes LLM que responden preguntas sobre datos locales: el agente genera SQL, se ejecuta contra DuckDB en memoria, devuelve un resultado y el agente responde en lenguaje natural. Rápido, barato y reproducible.

    Errores comunes que verás (y cómo arreglarlos)

    • “Database is locked” → intentaste escribir desde dos procesos. Solución: un unico writer o locking.
    • Permisos de archivo → chequea permisos OS; el proceso necesita RW.
    • Tropiezos con dependencias nativas en containers → usa la imagen oficial o instala dependencias del sistema.
    • Diferencias entre entornos (dev vs prod) → recuerda que :memory: no persiste; prueba en un archivo en staging.

    Checklist de buenas prácticas (hazlo ya)

    • Decide: memoria o persistente.
    • Si persistente, asegúrate de tener backup y control de versiones del archivo.
    • Para producción, centraliza escrituras o usa locking.
    • Mide: tiempos de query, uso de memoria y duración de tasks en CI.
    • Usa Parquet para input masivo.
    • Añade tests E2E que incluyan consultas típicas.
    • Documenta el esquema emergente: DuckDB permite crear tablas, pero si tu pipeline escribe Parquet, documenta columnas y tipos.

    Un par de ejemplos prácticos (copy-paste listos)

    Python: consulta Parquet en memoria y guarda resultado

    import duckdb
    
    con = duckdb.connect(':memory:')
    res = con.execute("""
        SELECT user_id, SUM(amount) AS total
        FROM read_parquet('datos.parquet')
        WHERE date >= '2024-01-01'
        GROUP BY user_id
    """).fetchdf()
    
    res.to_parquet('resultados.parquet')
    con.close()

    Node.js: abrir archivo y ejecutar query

    const duckdb = require('duckdb');
    const db = new duckdb.Database('analitica.duckdb');
    
    db.all("SELECT COUNT(*) as c FROM read_csv_auto('grande.csv')", (err, rows) => {
      if (err) throw err;
      console.log(rows);
    });

    Metáfora que ayuda a decidir

    Piensa en DuckDB como una navaja suiza para análisis: pequeña, compacta, infinitamente útil en el bolsillo. Pero no pretendas que sea la planta de acero que alimenta toda la ciudad. Para eso hay sistemas distribuidos. DuckDB es la herramienta que te permite resolver 80% de problemas analíticos sin desplegar infraestructura industrial.

    ¿Quieres ponerlo en producción sin dramas?

    Haz esto primero:

    1. Prototipa en memoria.
    2. Pasa a archivo en staging.
    3. Simula carga concurrente de lectura/escritura.
    4. Implementa el patrón de writer único si hace falta.
    5. Monitorea latencia y uso de disco/CPU.

    Cierre y llamada a la acción: You must first create a connection to a database. Pero crearla no basta: decide bien, prueba y diseña para concurrencia.

    Si quieres, te escribo un script que:

    • comprueba Node/Python environment,
    • crea un archivo .duckdb de ejemplo,
    • carga un Parquet y corre consultas de benchmarking,
    • y genera un reporte JSON con tiempos y memoria.

    Respóndeme con “QUIERO DUCK” y te lo preparo en Python o Node.js en 5 minutos. No es magia, es evitar noches de deploy. Y si quieres que lo haga en tu repo, pásame el enlace y te doy un plan de 3 commits para poner DuckDB sin romper nada.

    Dominicode Labs

    Para flujos de automatización, agentes y workflows aplicados al manejo de datos y DuckDB, revisa recursos y experimentos en Dominicode Labs. Es una continuación práctica para integrar patrones de escritor único, locking y pruebas de concurrencia en pipelines reales.

    FAQ

    Respuesta:

    Es la instrucción que abre el motor dentro de tu proceso. Define si la sesión es efímera (:memory:) o persistente (archivo), quién puede leer/escribir y qué recursos usará.

    Respuesta:

    Usa :memory: para análisis temporales, notebooks y CI donde no necesitas persistencia. Usa un archivo .duckdb si necesitas conservar resultados entre ejecuciones o compartir estado con control de accesos.

    Respuesta:

    No directamente. DuckDB permite lectura concurrente, pero la escritura concurrente al mismo archivo provoca locking. Diseña un writer único o un mecanismo de locking en la aplicación.

    Respuesta:

    Sí. DuckDB puede leer Parquet/CSV desde rutas S3 usando funciones como read_parquet('s3://bucket/datos.parquet'), evitando mover datos innecesariamente.

    Respuesta:

    Ajusta el número de threads si tu binding lo soporta, por ejemplo duckdb.set_config('threads', 'N'), y usa PRAGMA/settings para controlar memoria y comportamiento de spill-to-disk.

    Respuesta:

    Significa que hubo un intento de escritura concurrente. Solución: evita múltiples escritores, implementa un writer único o usa un lock distribuido antes de escribir.

    Respuesta:

    Sí, para dashboards locales o apps de escritorio es una buena opción. Para entornos con múltiples escritores simultáneos en producción, diseña un patrón de escritura centralizado.

  • Cómo construir un SaaS con IA resiliente y escalable

    Cómo construir un SaaS con IA resiliente y escalable

    ¿Quieres un SaaS con IA que sobreviva seis meses en producción o solo un demo viral de 48 horas?

    Tiempo estimado de lectura: 5 min

    • Ideas clave:
    • La diferencia entre un demo viral y un SaaS real no es la idea ni el modelo, sino la arquitectura.
    • Diseña asumiendo que la IA falla: latencia, rate limits y respuestas inconsistentes son inevitables.
    • Separación clara de responsabilidades (frontend, backend, orquestador, workers, persistencia) y asincronía por defecto salvan proyectos.
    • Contratos estrictos (JSON + validación) y observabilidad desde el inicio son imprescindibles para producción.

    ¿Quieres un SaaS con IA que sobreviva seis meses en producción o solo un demo viral de 48 horas?

    La diferencia no es la idea ni el modelo que uses. Es la arquitectura. Y sí: la IA te hace sentir productivo en minutos. También te hace pagar por la reescritura en semanas.

    Voy al grano. Si tu apuesta es “poner un prompt en un endpoint y ver qué pasa”, estás construyendo un wrapper, no un producto. Un wrapper se rompe cuando el proveedor cambia precios, cuando la latencia sube o cuando un caso borde que nunca imaginaste llega a producción. Un SaaS real convierte la IA en un componente confiable dentro de un sistema diseñado para fallar sin morir.

    Aquí tienes un plan práctico y sin postureo para construir un SaaS real con IA sin improvisar.

    Resumen rápido (lectores con prisa)

    Qué es: Buenas prácticas de arquitectura para convertir modelos de IA en componentes fiables dentro de un SaaS.

    Cuándo usarlo: Desde el MVP que pretende escalar hasta productos en producción con múltiples tenants y requisitos de coste y seguridad.

    Por qué importa: Evita que un prototipo se convierta en deuda técnica cara y en incidentes de producción.

    Cómo funciona, en pocas palabras: Separación de responsabilidades, asincronía por defecto, contratos estrictos (JSON + validación), observabilidad y pruebas que incluyan fallos de IA.

    Primera regla: asume que la IA falla… constantemente

    No es pesimismo. Es ingeniería. Latencia, rate limits, respuestas inconsistentes, cambios en la API: todo será parte de la vida diaria. Diseña para eso.

    Qué separar desde el minuto uno

    No mezcles interfaz, orquestación y persistencia. Divide responsabilidades claras:

    • Frontend: experiencia, manejo de latencia, feedback al usuario.
    • Backend de negocio: validaciones, reglas, monetización, seguridad.
    • Orquestador de IA: flujos, reintentos, parsing y almacenamiento de resultados.
    • Workers: procesamiento asíncrono, reintentos idempotentes.
    • Persistence: PostgreSQL (con pgvector), logs, metadatos.

    Si todo está en el mismo contenedor, el sistema se romperá bonito y rápido.

    Patrón que salva proyectos: asincronía por defecto

    Olvida la llamada síncrona “cliente→server→LLM→cliente”. Es la receta del timeout.

    • Client envía tarea → server registra job (estado: pending) → responde 202.
    • Worker (o n8n) toma job, hace llamadas a LLMs, actualiza estado.
    • Notifica por WebSocket/SSE o el cliente hace polling leve.

    Resultado: interfaz reactiva, control de reintentos y mejor experiencia cuando la IA tarda.

    Orquestación: usa n8n, no code spaghetti

    Sí, puedes encadenar prompts en código. También puedes terminar con funciones de mil líneas. Usa un orquestador (n8n o equivalente) para:

    • Encadenar pasos (call LLM → transformación → persistencia).
    • Ejecutar retries con backoff.
    • Manejar errores y circuit breakers visualmente.
    • Mantener logs de cada ejecución.

    El día que la API de IA se ponga inestable, agradecerás no tener que rastrear todo en un repo lleno de lambdas.

    Contratos > Prompts

    No pidas “texto bonito”. Pide JSON estricto. No hay excusas.

    • Define interfaces TypeScript o OpenAPI.
    • Obliga a la IA a devolver un objeto con esquema verificado.
    • Usa Zod o codegen para validar la respuesta y fallar rápido si hay desviaciones.

    Esto convierte a la IA en un microservicio con contrato, no en una caja negra caprichosa.

    RAG = potencia + responsabilidad

    Si vas a permitir que usuarios suban documentos para chatear con ellos, aplica aislamiento absoluto:

    • Cada vector con tenant_id.
    • Filtrado por tenant_id a nivel de consulta (antes de enviar contexto al LLM).
    • Escapa la tentación de “mezclar para mejores embeddings”. Eso rompe privacidad y compliance.

    Si trabajas con datos sensibles, aíslalo, audítalo y documenta quién lo puede ver.

    Costos y medición: que no te coja desprevenido

    Un SaaS con IA vive o muere por el coste por petición.

    • Metering por job: tokens consumidos, llamadas a terceros, tiempo de ejecución.
    • Alerts por coste semanal y por job atípico.
    • Fallbacks: versiones más baratas del modelo para tareas no críticas.
    • Caching inteligente: respuestas deterministas pueden cachearse.

    No hay nada más caro que ejecutar un modelo grande para una operación que podía resolverse con reglas.

    Idempotencia y seguridad en flujos asíncronos

    Reintentos inevitables → diseñalos bien:

    • Usa IDs de correlación.
    • Diseña workers idempotentes: reintentar no debe duplicar registros ni cobrar dos veces.
    • Aplica locks por job cuando haga falta.

    Observabilidad: telemetría desde el minuto cero

    Si no puedes medir, no puedes mejorar. Instrumenta todo:

    • Traces distribuidos (OpenTelemetry).
    • Métricas por endpoint, por modelo y por tenant.
    • Logs estructurados con contexto de job.
    • Dashboards y alertas (latencia, error rates, coste por tenant).

    Tests y contratos automáticos

    Haz que cada contrato tenga tests que fallen en CI si la IA devuelve algo fuera de esquema.

    • Mockea respuestas de LLM (positivas y negativas).
    • Tests de integración que simulen timeouts y retries.
    • Tests de seguridad: inyección de prompt, accesos cruzados entre tenants.

    Checklist MVP vs. Producción

    MVP mínimo viable (rápido, medible):

    • Job queue + worker básico.
    • Interfaces TypeScript + validación Zod.
    • Persistencia en PostgreSQL + pgvector.
    • Orquestación simple (n8n optional).
    • Métricas básicas y alertas de coste.

    Preparación para producción:

    • Observabilidad completa (traces, metrics, logs).
    • Políticas de multi-tenancy estrictas.
    • Circuit breakers, retries con backoff y dead-letter queues.
    • Billing y metering por tokens/calls.
    • Testing de resiliencia y chaos experiments.

    Plantilla rápida de SPEC.md que debes tener ya

    Pon esto en la raíz del repo. Si no lo haces ahora, lo pagarás después.

    • Objetivo del módulo (1 frase).
    • Stack aprobado y versiones.
    • Reglas innegociables (ej.: “No exponer secretos en frontend”, “Todo job idempotente”).
    • Contratos principales: Endpoints + interfaces TS.
    • Criterios de aceptación (tests que deben pasar).
    • Responsable técnico y proceso de cambios.

    Prompt maestro que funciona (ejemplo)

    Contexto + restricciones + output estricto:

    <contexto_negocio>Resumen en 3 frases</contexto_negocio>
    <stack>Next.js, Node 20, Postgres + pgvector</stack>
    <restricciones>No usar microservicios, respuesta JSON valida Zod</restricciones>
    <output_esperado>JSON { result: string, score: number, metadata: { sourceId: string } }</output_esperado>

    No es glamour. Es ingeniería que evita tickets nocturnos.

    El nuevo rol del equipo: menos héroes, más guardias

    Con IA el que más aporta no es el que teclea más rápido. Es el que fija límites, define contratos y establece el ritmo de iteración. El senior deja de ser “code god” para ser “arquitecto de fronteras”. Eso es lo que realmente escala.

    CTA corto y útil

    Si quieres, te doy ahora:

    • Una SPEC.md lista para pegar en tu repo.
    • Un prompt maestro para Claude + ejemplos de Zod.
    • Un .n8n workflow básico para encadenar llamadas a modelos con retries.

    Respóndeme con “Plantilla SaaS” y te lo envío. Hazlo ahora: crea el SPEC.md en la raíz antes del próximo commit generado por IA.

    Dominicode Labs

    Si quieres continuidad práctica y recursos relacionados con orquestación, workflows y automatización para productos de IA, visita Dominicode Labs. Es una continuación lógica para poner en práctica los enfoques descritos en este artículo.

    FAQ

    ¿Por qué no debería hacer llamadas síncronas al LLM desde el cliente?

    Porque los timeouts, latencias y rate limits hacen que la experiencia sea impredecible. La arquitectura asíncrona (jobs + workers) permite reintentos, control de costes y una interfaz más robusta.

    ¿Qué es un orquestador y por qué usar n8n?

    Un orquestador encadena pasos: llamadas a LLM, transformaciones, persistencia y retries. n8n ofrece visualización de flujos, gestión de errores y menos código espagueti en repositorios complejos.

    ¿Cómo obligo a la IA a devolver JSON válido?

    Define un contrato (TypeScript/OpenAPI) y valida con Zod u otro validador. Rechaza respuestas que no cumplan el esquema y trata esos casos en tus retries o dead-letter queues.

    ¿Qué medidas tomar para multi-tenancy en RAG?

    Aislamiento absoluto: cada vector con tenant_id, filtrado por tenant_id antes de consultas y auditoría de accesos. No mezclar datos entre tenants.

    ¿Qué métricas debo medir desde el primer día?

    Tokens consumidos por job, latencia por endpoint, error rate por modelo y coste por tenant. También traces distribuidos y logs estructurados por job.

    ¿Qué debe incluir mi SPEC.md mínimo?

    Objetivo del módulo, stack y versiones, reglas innegociables, contratos principales (endpoints + interfaces TS), criterios de aceptación y responsable técnico.

  • Implementando light-dark() para imágenes en CSS: guía técnica

    Implementando light-dark() para imágenes en CSS: guía técnica

    light-dark() para imágenes: guía práctica

    Tiempo estimado de lectura: 4 min

    • light-dark() ahora soporta <image>, permitiendo alternar assets (logos, fondos, máscaras) según esquema de color sin media queries.
    • Los dos argumentos de light-dark() deben ser del mismo tipo: ambos <color> o ambos <image>; mezclar tipos invalida la regla.
    • Usa la cascada para fallbacks: declara primero un background estándar y luego light-dark() para motores modernos.
    • Beneficios: cohesión del componente, respuesta local al color-scheme y menor deuda técnica frente a variables globales dispersas.

    Introducción

    Back in 2023, I wrote about the future of CSS color switching using the then-novel light-dark() function. It was a game-changer for colors, allowing us to ditch the repetitive @media (prefers-color-scheme: …) blocks for simple property declarations.

    But there was one glaring limitation: it only works for colors. If you wanted to swap out a background image, a mask, or a logo based on the user’s color scheme, you were stuck doing things the “old” way.

    Well, I have good news. The spec has been updated, and light-dark() is being extended to support images.

    Back in 2023, I wrote about the future of CSS color switching using the then-novel light-dark() function. It was a game-changer for colors, allowing us to ditch the repetitive @media (prefers-color-scheme: …) blocks for simple property declarations.

    Pero había una limitación clara: light-dark() solo aceptaba <color>. Si querías alternar una imagen de fondo, una máscara o un logo según el esquema de color, todavía te tocaba escribir variables globales y media queries dispersas. La buena noticia: la especificación ha avanzado y ahora light-dark() acepta <image>. Esto cambia la arquitectura de temas en CSS y merece una guía práctica con criterio.

    Resumen rápido (lectores con prisa)

    Qué es: Una extensión de light-dark() que acepta <image>, permitiendo elegir assets según esquema de color.

    Cuándo usarlo: Cuando necesites alternar logos, fondos o máscaras sin depender de variables globales o JS.

    Por qué importa: Reduce fragmentación de estilos y permite decisiones locales basadas en color-scheme.

    Cómo funciona: Declara light-dark(url(light.png), url(dark.png)) en la propiedad que espera un <image>; ambos argumentos deben ser <image>.

    Qué cambia — ejemplo y patrón recomendado

    Antes (patrón clásico con variables y media queries)

    :root { --bg-image: url(light-pattern.png); }
    
    @media (prefers-color-scheme: dark) {
      :root { --bg-image: url(dark-pattern.png); }
    }
    
    .element { background-image: var(--bg-image); }

    Ahora (light-dark() para imágenes)

    .element {
      color-scheme: dark; /* override local si hace falta */
      background-image: light-dark(url(light-pattern.png), url(dark-pattern.png));
    }

    Ventajas prácticas

    • Cohesión: la decisión de qué asset usar vive en el mismo selector que lo consume.
    • Respuesta local: respeta color-scheme aplicado al nodo o a sus ancestros.
    • Portabilidad: el componente se mueve sin necesidad de variables globales.

    Restricciones técnicas y por qué importan

    Regla crítica: los dos argumentos de light-dark() deben ser del mismo tipo. Es decir, o dos <color> o dos <image>. No mezcles tipos:

    /* válido */
    color: light-dark(#000, #fff);
    background-image: light-dark(url(light.png), url(dark.png));
    
    /* inválido — el parser lo rechazará */
    background-image: light-dark(url(light.png), #1a1a2e);

    ¿Por qué? El parser de CSS valida la sintaxis frente al tipo que la propiedad espera. background-image espera un <image>. Si uno de los argumentos es un <color>, la declaración crea ambigüedad en el AST y el motor invalida la regla. Esta restricción protege rendimiento y predictibilidad del renderizado.

    Progressive enhancement y fallbacks reales

    Soporte por navegador aún está en despliegue. La estrategia correcta es usar la cascada y un fallback explícito:

    .element {
      /* Fallback básico */
      background-image: url(light-pattern.png);
    
      /* Moderno: sobreescribe si se soporta */
      background-image: light-dark(url(light-pattern.png), url(dark-pattern.png));
    }

    Si necesitas cobertura adicional para navegadores antiguos, combina con @media (prefers-color-scheme: dark) como fallback secundario, pero reserva light-dark() como la fuente de verdad cuando el motor lo soporte.

    Casos de uso donde esto realmente aporta valor

    • Logos adaptativos (SVG/PNG): menos JS, menos duplicación de DOM.
    • Patrones de fondo decorativos que deben cambiar con el tema.
    • mask-image o -webkit-mask-image donde el comportamiento depende del contraste.
    • Sistemas de diseño a escala: reduce la deuda técnica y facilita la revisión de cambios de tema.

    Criterio para equipos

    • Documenta la convención: cuándo usar light-dark(), cuándo preferir variables.
    • Revisa en code reviews la homogeneidad de tipos (no mezclar color/imagen).
    • Añade un test visual de regresión (navegación entre temas) en CI si el producto necesita alta fiabilidad.
    • Implementa el fallback mostrado y degrada con gracia: el objetivo es evitar roturas visuales en navegadores sin soporte.

    Conclusión

    La evolución de light-dark() hacia <image> es un paso práctico hacia interfaces menos dependientes de JS y con menos “bolsas” de estilos dispersos. No es una revolución espectacular, pero sí una mejora arquitectónica que reduce deuda técnica y hace los componentes más robustos y portables. Implementa con criterio, añade fallbacks y actualiza tus patrones de diseño: el cambio trae limpieza, y eso en producción siempre paga.

    FAQ

    Respuesta:

    Es la actualización de la especificación que permite pasar <image> como argumentos a light-dark(), de modo que propiedades que esperan imágenes (por ejemplo, background-image) puedan alternar assets según el esquema de color.

    Respuesta:

    No. Los dos argumentos deben ser del mismo tipo. Mezclar un <color> con un <image> es sintácticamente inválido y el parser rechazará la regla.

    Respuesta:

    Usa la cascada: declara primero un fallback (por ejemplo, background-image: url(light-pattern.png);) y luego sobreescribe con light-dark(). Como fallback secundario puedes mantener @media (prefers-color-scheme: dark) si necesitas más compatibilidad.

    Respuesta:

    Cualquier propiedad que espere un <image> puede aprovechar la extensión, por ejemplo background-image, mask-image y sus prefijos relevantes como -webkit-mask-image.

    Respuesta:

    Sí, en escenarios donde la compatibilidad es crítica o donde la convención del equipo requiere variables globales. Pero para componentes portables y locales, light-dark() ofrece una alternativa más cohesionada.

    Respuesta:

    La restricción de tipo evita ambigüedades en el AST y ayuda al motor a validar y optimizar reglas. El impacto en rendimiento es mínimo si se usa correctamente; la ventaja es mayor predictibilidad del renderizado.

    Respuesta:

    La especificación relevante es CSS Color Level 5 (W3C). Para diagnóstico visual en Chrome, referencia Chrome DevTools (sección Memory para diagnóstico visual, no confundir con rendimiento).