Tag: Programación

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

  • Cómo gestionar la gobernanza de IA para evitar la deuda técnica

    Cómo gestionar la gobernanza de IA para evitar la deuda técnica

    ¿Te vas a mirar el correo mientras la IA reescribe tu código… y luego vuelves a casa de locos?

    Tiempo estimado de lectura: 6 min

    • La delegación ciega a agentes que ejecutan cambios produce deuda técnica documentada fuera del repo.
    • La solución práctica es gobernar decisiones: exigir artefactos versionables con autoría y timestamp.
    • Plum: herramienta que intercepta commits, extrae decisiones y fuerza aprobaciones antes del commit.

    Poca gente lo dice en voz alta: dejar que un agente “lo corra y volvemos” es exactamente la forma más rápida de cavar una trampa de deuda técnica. Sales cinco minutos. Vuelves y el LLM te dejó un “decision” que grita: eso es una locura. No lo hagas.

    Esto no es un problema de postureo. Es práctico. Es urgente.

    Resumen rápido (lectores con prisa)

    Un agente que ejecuta cambios sin gobernanza introduce decisiones sin autoría en el repo. Necesitas un mecanismo externo que intercepte commits, extraiga decisiones y requiera aprobación, convirtiéndolas en artefactos versionables y auditable—eso reduce deuda técnica y mejora trazabilidad.

    1) Lo que pasa cuando “lo dejamos correr”

    Un agente se queda ejecutando tareas. Encuentra ambigüedades. Encuentra dependencias rotas. Para avanzar, toma atajos. Guarda esos atajos en su chat. Tú haces commit y pum: el código llega a la rama, los tests pasan y la intención se evapora.

    Resultado: hacks documentados en conversaciones privadas, no en el repo. Atajos que nadie planeó. Deuda técnica que aparece al lado del despliegue.

    2) Por qué esto es peor que un bug cualquiera

    Un bug puedes rastrearlo. Una decisión sin autoría es un agujero negro. Nadie recuerda por qué cambó la fórmula de impuestos a las 3 AM. Nadie puede auditar la razón. Y cuando el problema explota en producción, el “blame” no sirve: no hay decisión firmada, solo un commit huérfano y un chat que nadie va a revisar.

    3) La solución no es prohibir la IA. Es gobernarla.

    No más fe ciega. No más prompts que funcionan en beta pero rompen en prod. Necesitamos que cada decisión que importe deje rastro formal. Que sea un artefacto. Que sea buscable. Que tenga autoría y timestamp. Que puedas preguntar: “¿por qué esto existe?” y obtener una respuesta concreta.

    4) Plum: la plomada que obliga a decidir en serio

    Imagina una herramienta que corre al lado de Git y te fuerza a responder. No genera código por ti. No es una skill adentro del LLM. Es un checkpoint.

    Básicos del flujo

    • plum init → crea .plum y .plumignore, añade hooks.
    • Cambias código con un agente.
    • Intentas git commit. Plum compara diffs y scans de traces.
    • Si hay decisiones, el commit falla hasta que apruebes, edites o rechaces.
    • Si apruebas, plum actualiza la spec (Markdown) y agrega una entrada .jsonl con: pregunta, decisión, autor, branch y timestamps.

    ¿La ventaja? Cuando vuelvas del mail, no te encuentras sorpresas sin contexto. Te encuentras una decisión con nombre y apellido.

    5) No puede ser una “skill” del agente —y punto

    Una skill dentro del LLM es una sugerencia. Las sugerencias se ignoran. La gobernanza debe estar fuera. Tiene que poder bloquear commits, integrarse en CI y ser determinista. Si es opcional, no sirve.

    6) ¿Qué hay dentro del .jsonl y por qué importa?

    Ese archivo no es solo logs. Es la historia de la intención del proyecto. Cada entrada contiene:

    • El dilema técnico.
    • La decisión tomada.
    • Quién aprobó.
    • Si fue propuesto por el LLM o por un humano.
    • Vínculo a la diff/PR.
    • Marcas de tiempo.

    Eso convierte la intención en dato: indexable, auditable, útil para auditorías y forensics.

    7) Problemas reales —sin romanticismos

    • Deduping de decisiones es fuzzy. Detectar “la misma decisión” entre conversaciones distintas no es trivial. Requiere heurísticas y ajuste repo-específico.
    • Rollbacks automáticos: si rechazas la decisión, idealmente el sistema revierte el cambio o pide al agente rehacerlo. Hoy eso es work-in-progress.
    • Ruido: si cada hotfix dispara cinco decisiones, la herramienta es odiada. Necesitas umbrales configurables.
    • Specs crecen como malas hierbas. Hay que shardearlas en requerimientos atómicos, y sí: un LLM puede ayudar a fragmentarlas, pero diseña el flujo.

    8) Umbrales: sensibilidad y contexto

    La clave práctica es permitir tolerancias dinámicas:

    • Modo strict: todo pasa por aprobación (fintech, salud).
    • Modo sane: decisiones no críticas se agrupan y se presentan en lote.
    • Modo fast-lane: “dangerously approve all” para prototipos.
    • Filtros por carpeta: core = strict; ui-experiments = lenient.

    Hazlo configurable por módulo y por rama. No es capricho: es supervivencia.

    9) Integración con DSPy y el determinismo

    Cuando puedas validar con código, hazlo. Usa parsers, tests y reglas. Donde necesites LLMs (p.ej. parse semántico del spec), estructura las llamadas con DSPy: inputs y outputs tipados. Menos alucinaciones, más predictibilidad. Enrutamiento por velocidad: dedupe puede ir a modelos OSS rápidos; parsing pesado a modelos más potentes.

    10) ¿Qué debería cambiar en GitHub?

    Markdown no es solo texto. Debe ser ciudadano de primera clase. Tu spec tiene que ser operable, con vínculos directos a decisiones, código y tests. Visualizar esa malla en GitHub (decisiones ↔ requisitos ↔ tests ↔ diffs) debería ser trivial. Imagina abrir un diff de markdown y ver “este requisito cambia X líneas de código” con enlaces directos. Eso es la próxima generación de repositorios.

    11) Cultura y proceso: lo que no puedes automatizar

    No automatices la cultura. Exige que cada PR responda:

    • ¿Qué decisión justificó este cambio?
    • ¿Qué requirement se actualiza?
    • ¿Qué test cubre el cambio?

    Haz que la herramienta extraiga esos metadatos y los convierta en entradas .jsonl. Convierte la disciplina en hábito.

    12) Checklist mínimo para empezar hoy (15–30 minutos)

    1. Versiona tu spec en Markdown en la raíz del repo.
    2. Asegura tests automatizados (si eres Python, Pytest; si no, prepara adapter).
    3. pip install plum-dev
    4. plum init → apunta specs.md y carpeta de tests.
    5. Añade .plumignore (README, docs, assets).
    6. Configura umbrales: prod = strict; feature branches = lenient.
    7. Prueba: haz un cambio via agente, intenta commit, observa el fail y aprueba la decisión.
    8. Ejecuta plum sync -> revisa gaps spec↔tests↔code.

    13) Si no lo haces: la factura llegará

    Velocidad hoy = caos mañana. Cuando explote algo crítico a las 2 AM, nadie sabrá por qué la regla cambió. El time-to-fix se multiplicará. La deuda técnica se vuelve refractorable, y cada refactor cuesta más que el ahorro inicial de haber delegado.

    14) Beneficios reales (sí, más allá del miedo)

    • Auditoría real para compliance.
    • Onboarding más rápido: nuevos devs leen el árbol de decisiones.
    • Menos debates eternos en PRs: la intención está documentada.
    • Productividad con control: velocidad sin descontrol.

    15) Cierre y acción concreta

    No es sexy. Es necesario. Instala la plomada. Prueba en una rama. No por postureo: por supervivencia técnica.

    Quiero ayudarte a empezar ya. ¿Quieres que te mande:

    • el template de .jsonl listo para copiar,
    • el flujo de PR + configuración de CI que bloquea merges hasta sync exitoso,
    • y un checklist de integración de Plum en 15 minutos?

    Respóndeme “Mándame el template” y te lo doy ahora mismo.
    Y mientras lo instalas, recuerda esto: velocidad sin plomada es solo una forma elegante de cavar tu propia trampa.

    Esto no acaba aquí.

    Si quieres profundizar en prácticas de gobernanza y automatización que encajan con este enfoque, revisa Dominicode Labs para recursos y experimentos relacionados.

    FAQ

    ¿Qué hace exactamente Plum cuando detecta una decisión?

    Intercepta el commit, extrae decisiones desde los traces del agente y falla el commit hasta que alguien apruebe, edite o rechace la decisión.

    ¿Plum bloquea commits automáticamente?

    Sí: si detecta decisiones relevantes, el commit falla hasta que se resuelva la aprobación o edición de esa decisión.

    ¿Cómo se almacena la autoría y los timestamps?

    Se agregan entradas .jsonl con campos como pregunta, decisión, autor, branch y timestamps; además la spec en Markdown se actualiza para reflejar la decisión.

    ¿Cómo evito que la herramienta genere ruido?

    Configura umbrales y filtros por carpeta, agrupa decisiones no críticas en lotes y ajusta sensibilidad por rama o módulo.

    ¿Se puede integrar Plum en CI/CD?

    Sí. La gobernanza debe integrarse en CI para ser efectiva; Plum puede bloquear merges hasta que el sync y las aprobaciones sean exitosas.

    ¿Qué contiene una entrada .jsonl?

    Cada entrada incluye el dilema técnico, la decisión, quién aprobó, si fue propuesto por LLM o humano, vínculo a la diff/PR y marcas de tiempo.

    ¿Qué pasa si rechazo una decisión detectada?

    Idealmente el sistema revierte el cambio o solicita al agente rehacerlo; hoy ese comportamiento es work-in-progress y depende de la configuración del repositorio.

    ¿Cómo empezar en 15 minutos?

    Versiona la spec en Markdown, asegura tests automatizados, instala plum-dev, ejecuta plum init, configura .plumignore y umbrales, y prueba el flujo con un cambio vía agente.

  • Cómo implementar CLAUDE.md para agentes de código automatizados

    Cómo implementar CLAUDE.md para agentes de código automatizados

    CLAUDE.md: el contrato entre tú y el agente

    Tiempo estimado de lectura: 6 min

    • Define reglas operativas claras para evitar que agentes automaticen o “improvisen” sobre código base.
    • Estandariza stack, comandos y convenciones para que el agente genere cambios compatibles con el repositorio.
    • Mantén el archivo actualizado y versiónalo junto con cambios en el stack para evitar divergencias.

    Introducción

    CLAUDE.md: el contrato entre tú y el agente es el punto de control que evita que Claude Code “improvise” sobre tu base de código. Colocar ese archivo en la raíz del repo cambia la relación: el agente deja de adivinar y empieza a obedecer reglas explícitas antes de generar cualquier cambio.

    Claude Code incorpora los CLAUDE.md al contexto inicial de la sesión. Si quieres que el agente respete decisiones de arquitectura, convenciones y restricciones operativas, no hay atajo: ponlas por escrito en un CLAUDE.md que Claude pueda leer. Para entender el diseño técnico del protocolo, revisa la documentación oficial (Anthropic — claude-code) y la página de Claude en Anthropic.

    ¿Qué debe contener un CLAUDE.md?

    Un CLAUDE.md no es un README para humanos ni un changelog. Es un contrato operativo para máquinas. Limítalo a lo esencial que el agente necesita conocer para no romper invariantes del sistema. Divide el archivo en cuatro bloques obligatorios:

    • 1. Stack técnico y decisiones no negociables.
    • 2. Comandos operativos (build, test, lint).
    • 3. Convenciones de código que el linter no puede forzar.
    • 4. Patrones prohibidos y límites de seguridad.

    A continuación, una plantilla mínima que puedes adaptar.

    Plantilla mínima

    ## Stack
    - Next.js 14 (App Router). No Pages Router.
    - TypeScript (strict: true).
    - Tailwind CSS. No CSS Modules ni styled-components.
    - Prisma como ORM. No SQL raw salvo migraciones.
    
    ## Comandos
    - `pnpm dev` — dev server
    - `pnpm test` — Jest (coverage >= 80%)
    - `pnpm lint` — ESLint (project config)
    - `pnpm build` — build prod (sin warnings)
    
    ## Convenciones
    - Componentes: PascalCase en `/src/components`.
    - Hooks: prefijo `use` en `/src/hooks`.
    - Errores: usar tipos, no `any`. `catch(e: unknown)` -> narrow.
    - No llamadas a APIs desde componentes: usar `/src/services`.
    
    ## Patrones prohibidos
    - No introducir dependencias sin PR aprobado.
    - No usar `useEffect` para sincronización derivada.
    - No crear contextos globales nuevos; usar Zustand.
    

    ¿Dónde colocarlo en monorepos?

    Claude Code respeta jerarquías: lee CLAUDE.md en la raíz y en submódulos relevantes. En monorepos, combina reglas globales con reglas locales:

    • /CLAUDE.md — reglas globales (tooling, CI, linters)
    • /apps/web/CLAUDE.md — reglas específicas del frontend
    • /packages/ui/CLAUDE.md — reglas del diseño compartido

    El agente fusiona las reglas aplicables según el contexto del archivo que edita. Esto te permite mantener consistencia sin replicar todo el contrato en cada paquete.

    Cómo cambia el flujo de trabajo con un CLAUDE.md

    Sin CLAUDE.md, cada sesión de Claude Code es una pizarra limpia: el agente infiere patrones, instala dependencias y propone cambios que “funcionan” pero rompen la coherencia del repo. Con CLAUDE.md:

    • El agente conoce el tooling exacto (ej. pnpm vs npm) y no ejecuta comandos incorrectos.
    • No propone o instala librerías fuera del stack definido.
    • Genera código que cumple las convenciones locales: ubicación de archivos, nombres, patrones de error.
    • Respeta las reglas de seguridad y de auditoría (por ejemplo, rutas que requieren autorización).

    El resultado práctico: menos PRs de corrección, menos reescrituras y menos deuda técnica introducida por el agente.

    Mantenimiento: el contrato debe evolucionar con el código

    Un CLAUDE.md desactualizado es peor que no tenerlo. El agente aplicará reglas obsoletas con tanta disciplina como aplicaría las correctas.

    Reglas simples de mantenimiento:

    • Actualiza CLAUDE.md en el mismo PR que cambia el stack o introduce un patrón nuevo.
    • Versiona las secciones críticas (ej. Stack v2) si el cambio es migratorio.
    • Añade ejemplos de I/O cuando la interacción es ambigua (ej. formatos JSON esperados).
    • No uses el archivo para documentación extensa de negocio; su propósito es técnico y operativo.

    Casos prácticos y límites

    – Si tu agente debe interactuar con la UI (acciones del DOM), documenta los casos en CLAUDE.md pero combina con una especificación de UI semántica (p. ej., WebMCP).
    – No almacenes secretos ni variables de entorno en CLAUDE.md. Usa vaults y referencias a los secretos gestionados por CI/CD.
    – Si tu repositorio permite múltiples stacks (ej. experimentos), usa CLAUDE.md por carpeta para evitar ambigüedades.

    Conclusión

    CLAUDE.md: el contrato entre tú y el agente es una inversión pequeña con retorno inmediato. Poner las reglas por escrito antes de pedirle a Claude Code que haga cambios transforma una relación de adivinanza en una colaboración predecible. Si tu objetivo es integrar agentes de forma práctica y segura, escribir y mantener CLAUDE.md debería ser parte del proceso de desarrollo —actualizado en el mismo PR que cambia tu arquitectura— no una tarea opcional.

    Dominicode Labs

    Para equipos que trabajan con agentes, automatización y flujos de trabajo técnicos, la práctica de formalizar contratos operativos como CLAUDE.md encaja con iniciativas de investigación aplicada. Más recursos y experimentos relacionados están disponibles en Dominicode Labs.

    FAQ

    Respuesta: ¿Qué es un CLAUDE.md y para qué sirve?

    Un CLAUDE.md es un archivo de reglas operativas que define cómo un agente (ej. Claude Code) debe comportarse respecto al repositorio. Sirve para evitar que el agente realice cambios incompatibles o no autorizados.

    Respuesta: ¿Dónde debe ubicarse en un monorepo?

    Colócalo en la raíz del repositorio para reglas globales y en subcarpetas relevantes para reglas locales (por ejemplo, /apps/web/CLAUDE.md). Claude Code combinará las reglas aplicables.

    Respuesta: ¿Qué pasa si no lo actualizo?

    Si está desactualizado, el agente aplicará reglas obsoletas, lo que puede introducir errores o incoherencias. Actualízalo en el mismo PR que modifica el stack o las convenciones.

    Respuesta: ¿Puedo incluir secretos en CLAUDE.md?

    No. No almacenes secretos ni variables de entorno en CLAUDE.md. Usa vaults o referencias gestionadas por CI/CD.

    Respuesta: ¿Cómo integro cambios en el contrato durante una migración del stack?

    Versiona secciones críticas (ej. “Stack v2”) y añade la actualización en el mismo PR que realiza la migración para mantener coherencia entre el código y el contrato.