[{"content":"","date":"12 mayo 2026","externalUrl":null,"permalink":"/posts/","section":"Artículos","summary":"","title":"Artículos","type":"posts"},{"content":" Desarrollo Lenguajes, tooling, pruebas, decisiones que envejecen mal. Ver cuaderno Tecnología El stack del momento, mirado de reojo. LLMs, WebGPU, lo que se queda. Ver cuaderno Matemáticas Intuiciones visuales para cosas que parecían áridas. Aplicada, no académica. Ver cuaderno Personas Lo aprendido (y lo arruinado) gestionando equipos pequeños. Ver cuaderno Personal Métodos, sistemas, hábitos y la sospecha de que casi todos procrastinan. Ver cuaderno Misc Notas sueltas que no entran en ninguna otra carpeta. Ver cuaderno ","date":"12 mayo 2026","externalUrl":null,"permalink":"/","section":"Código, números y las personas detrás del software.","summary":"","title":"Código, números y las personas detrás del software.","type":"page"},{"content":"","date":"12 mayo 2026","externalUrl":null,"permalink":"/categories/","section":"Cuadernos","summary":"","title":"Cuadernos","type":"categories"},{"content":"","date":"12 mayo 2026","externalUrl":null,"permalink":"/categories/desarrollo/","section":"Cuadernos","summary":"","title":"Desarrollo","type":"categories"},{"content":"No es la velocidad. Tampoco el sistema de tipos. Lo que se queda contigo es la pregunta que borrow() hace por ti cada vez:\n¿Quién es dueño de este dato, exactamente?\nY de pronto miras tu código Python, ese mismo dict que pasas a tres funciones, y entiendes por qué el lunes algo dejó de funcionar.\nEl préstamo como modelo mental # Cuando llevas tiempo en lenguajes con garbage collector, dejas de pensar en propiedad. El intérprete se encarga. Y eso está bien hasta que no lo está.\ndef normalize(items): items.sort() # (1)! return [x.lower() for x in items] users = [\u0026#34;Beatriz\u0026#34;, \u0026#34;Ana\u0026#34;, \u0026#34;Carlos\u0026#34;] result = normalize(users) print(users) # [\u0026#39;Ana\u0026#39;, \u0026#39;Beatriz\u0026#39;, \u0026#39;Carlos\u0026#39;] ← sorpresa Mutar el parámetro de entrada es un préstamo mutable. En Rust ni compilaría sin marcarlo. En Python compila, corre, y te muerde en producción. En Rust el equivalente no compila:\nfn normalize(items: \u0026amp;Vec\u0026lt;String\u0026gt;) -\u0026gt; Vec\u0026lt;String\u0026gt; { items.sort(); // ❌ cannot borrow immutable items.iter().map(|s| s.to_lowercase()).collect() } El compilador te obliga a decidir: ¿lo necesitas mutable o no? Esa decisión, llevada de vuelta a Python, mejora cómo escribes funciones.\nTres heurísticas que me llevé # Acepta Iterable, devuelve list. Es el equivalente cultural de \u0026amp;[T] → Vec\u0026lt;T\u0026gt;. Anota los tipos como si fueran un contrato. No para el type checker; para el siguiente humano. Si una función muta su entrada, dilo en el nombre. sort_in_place, no sort. Próxima entrada. En la siguiente parte hablo de Result\u0026lt;T, E\u0026gt; y cómo me hizo dejar de usar excepciones para flujo de control en Python. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"12 mayo 2026","externalUrl":null,"permalink":"/posts/rust-me-hizo-mejor-python/","section":"Artículos","summary":"","title":"Por qué Rust me hizo mejor programador en Python","type":"posts"},{"content":"","date":"12 mayo 2026","externalUrl":null,"permalink":"/tags/python/","section":"Tags","summary":"","title":"Python","type":"tags"},{"content":"","date":"12 mayo 2026","externalUrl":null,"permalink":"/tags/rust/","section":"Tags","summary":"","title":"Rust","type":"tags"},{"content":"","date":"12 mayo 2026","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"A finales de 2024 escribí que ejecutar un LLM en local era \u0026ldquo;técnicamente posible, prácticamente miserable\u0026rdquo;. Año y medio después, la frase se ha quedado obsoleta por la mitad. Sigue siendo técnicamente posible. Ya no es miserable.\nUn Mac mini M4 Pro con 64 GB corre hoy modelos que en 2023 requerían un cluster.\nLo que sigue es lo que probé personalmente entre febrero y abril de 2026. Sin afiliaciones, sin sponsors.\nLo que ha cambiado de verdad # Tres cosas, en orden de impacto:\nCuantización 4-bit deja de doler. Los modelos de 30-70B parámetros corren con calidad indistinguible del FP16 para el 90% de las tareas que les pido (resumir, refactor, draft de texto). El otro 10% son tareas de razonamiento largo donde sí se nota. Memoria unificada en Apple Silicon escala. 96 GB en un M3 Max te dan más VRAM efectiva que cualquier consumer GPU. La latencia ya no es el bottleneck. Runtime maduro. llama.cpp, ollama y lmstudio se han estabilizado en formato (GGUF) y API. Cambiar de modelo es ollama pull qwen2.5:32b y nada más. Mi setup ahora mismo # Pieza Qué uso Por qué Hardware Mac Studio M3 Ultra, 192 GB Memoria unificada absurda; ruido cero Runtime Ollama Tres comandos, vida tranquila Modelo daily qwen2.5-coder:32b-q4 Mejor coding model open en mi prueba Modelo razonamiento deepseek-r1:70b-q4 Chain-of-thought serio, 25 tok/s Cliente mods (CLI) + Continue (VS Code) Sin GUI pesada Lo que no uso:\nWeb UI tipo open-webui. Añade fricción para el caso CLI/IDE. Modelos de 405B+. Mi caso de uso no justifica el coste. Embeddings locales (de momento). El RAG sigue siendo más barato en cloud. Para qué sirve hoy y para qué no # Sirve sin discusión:\nRefactor mecánico de código (renombrar, extraer función, traducir entre lenguajes). Primer draft de tests sobre función existente. Resumir documentos largos que no quiero subir a un servicio. Brainstorming en off-line (avión, sitio sin conexión). Editar textos en español sin enviar a un tercero. No sirve todavía, en mi experiencia:\nTareas que requieren contexto del repositorio entero. Sin tooling tipo Cursor / Claude Projects local, sigue siendo torpe. Razonamiento de varios pasos sobre código complejo. R1 ayuda, pero no llega. Generación creativa larga (\u0026gt;2000 palabras coherentes). El argumento real para tenerlo # No es el coste. Por mi volumen, una suscripción cloud sale más barata que la electricidad y el hardware.\nEs privacidad y disponibilidad. Hay cosas que no quiero mandar a un tercero: contratos, conversaciones con cliente, código bajo NDA. Tenerlo local resuelve el problema sin pensarlo. Y cuando vuelo o cuando una API se cae, sigo trabajando.\nLo que viene # Lo que veo en el horizonte 2026:\nModelos de 7B con calidad de 70B del año pasado. La compresión semántica avanza más rápido que el hardware. MoE local viable. Mixtral y Qwen MoE empiezan a correr decentemente en consumer. Tooling estandarizado. Algo tipo MCP (Model Context Protocol) generalizado para que el LLM local hable con tu sistema de notas, tu editor y tu shell sin pegamento custom. Si las tres se cumplen, en 2027 el LLM local deja de ser una opción técnica y pasa a ser la opción por defecto. No para todos los casos. Para más casos.\nPróxima entrada. Mi configuración exacta de Continue + Ollama para que el code completion local no sea una experiencia frustrante. En la siguiente entrega. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"3 mayo 2026","externalUrl":null,"permalink":"/posts/llm-local-2026/","section":"Artículos","summary":"","title":"El estado del LLM local en 2026","type":"posts"},{"content":"","date":"3 mayo 2026","externalUrl":null,"permalink":"/categories/tecnologia/","section":"Cuadernos","summary":"","title":"Tecnología","type":"categories"},{"content":"Llevo años revisando pull requests como si revisar fuera encontrar errores. Cada uno terminaba con una lista de comentarios y la sensación de haber sido útil.\nEl problema es que esa sensación es muy mala señal de utilidad real.\nEste año empecé a hacer code reviews de otra forma. Tres cambios pequeños, un cambio grande de mentalidad.\nEl cambio de mentalidad # Antes pensaba: \u0026ldquo;mi trabajo es que este PR no rompa nada\u0026rdquo;. Ahora pienso: \u0026ldquo;mi trabajo es que la siguiente persona que toque este código pueda entenderlo\u0026rdquo;.\nSuena parecido. No lo es. El primero te lleva a leer línea a línea buscando defectos. El segundo te lleva a leer el diff entero como si fuera un texto: ¿se entiende el porqué? ¿queda claro qué hace? ¿qué decisiones se tomaron y por qué?\nEl 90% de los defectos puntuales los caza el linter, el type checker o los tests. El 100% de las decisiones mal documentadas se mantienen como deuda hasta que alguien se cruza con ellas a las dos de la mañana.\nLos tres cambios prácticos # 1. Empezar por leer la descripción # Suena obvio. No lo hacía. Iba directo al Files changed.\nAhora empiezo por el cuerpo del PR. Si la descripción no me dice qué problema resuelve y por qué este enfoque, ese es el primer comentario. No reviso código hasta que esto está claro.\nBeneficio secundario: el autor mejora sus descripciones después de un par de iteraciones. La calidad del histórico del proyecto sube.\n2. Distinguir tres tipos de comentario # Cada comentario lleva ahora un prefijo, robado a Google internamente:\nnit: preferencia, no bloqueante. \u0026ldquo;Yo lo nombraría así\u0026rdquo;. question: no sé si esto es un bug o yo no entiendo. Pregunta de verdad. blocker: esto tiene que cambiar antes de merge. Y explico por qué. Sin el prefijo, todo comentario se lee como una orden. El autor pierde dos horas atendiendo nits porque no sabe que puede ignorarlos. Con el prefijo, el contrato es explícito.\n3. Aprobar con condiciones # Antes solo aprobaba cuando todo me parecía bien. Lo que pasaba: el PR esperaba mi siguiente revisión por dos comentarios menores.\nAhora apruebo en el momento si los blocker: están resueltos, aunque queden nit: o question:. El autor decide. Mi review no es el cuello de botella del merge.\nEl cambio más caro: revisar menos # Antes revisaba todos los PRs del equipo. Sentía que tenía que. Era el ingeniero senior, era mi responsabilidad.\nResultado real: bottleneck, gente esperando, y revisiones cada vez más superficiales porque no daba tiempo.\nAhora reviso solo cuando:\nEl PR toca un área que conozco mejor que el resto. El autor lo pide explícitamente. Hay decisiones de diseño que afectan más allá del PR. Para el resto, el código es del equipo. Que se revisen entre ellos. Mi trabajo es haberles dado el contexto suficiente para que esa revisión sea buena.\nMétrica que me importa ahora # No es \u0026ldquo;PRs revisados por semana\u0026rdquo;. Es \u0026ldquo;tiempo medio entre que se abre un PR y se mergea\u0026rdquo;.\nSi ese número baja sin que la calidad caiga, algo está funcionando. Si sube, hay un cuello de botella que arreglar, y muchas veces ese cuello de botella era yo.\nPróxima entrada. Las descripciones de PR tienen una plantilla mínima que me ha funcionado para que el autor escriba más y mejor sin sentir burocracia. La comparto en la siguiente. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"28 abril 2026","externalUrl":null,"permalink":"/posts/repensando-pull-request/","section":"Artículos","summary":"","title":"Repensando el pull request","type":"posts"},{"content":"Llevo doce años dando feedback a gente que trabajaba conmigo. Los primeros ocho lo hacía mal y no lo sabía. Los últimos cuatro lo hago menos mal porque dos personas me dijeron, en momentos distintos, lo mismo:\n\u0026ldquo;No me estás dando feedback. Me estás contando lo que tú harías\u0026rdquo;.\nY tenían razón. Esta es la entrada que me hubiera gustado leer hace doce años.\nEl error más caro # Confundía dos cosas:\nFeedback es información sobre la brecha entre lo que hiciste y un objetivo compartido. Opinión es lo que yo habría hecho en tu lugar. Casi todo lo que daba como \u0026ldquo;feedback\u0026rdquo; era opinión. Como yo era el manager, mi opinión sonaba a feedback. Pero la persona al otro lado oía: \u0026ldquo;no es como tú lo hiciste, es como yo lo habría hecho\u0026rdquo;, y reaccionaba a la defensiva con razón.\nLa pregunta que ahora me hago antes de abrir la boca: ¿esto que voy a decir mide una distancia a un objetivo que ya acordamos, o es mi preferencia disfrazada?\nSi es lo segundo, me la callo. O la presento explícitamente como opinión: \u0026ldquo;voy a darte una opinión personal, no un feedback\u0026rdquo;.\nLo que sigo haciendo mal # A pesar de saber lo anterior, sigo cayendo en tres patrones. Los anoto porque verbalizarlos los hace menos frecuentes.\n1. Feedback en cascada # Cuando algo me molesta pero no lo digo en su momento, se acumula. Luego, en la review semestral, suelto tres meses de cosas pequeñas como si fuera un brief de problemas. La persona se queda noqueada porque nada de eso fue dicho cuando ocurrió.\nCómo intento corregirlo: si tengo que esperar más de una semana para mencionarlo, asumo que ya no merece mencionarse. O lo digo ahora o lo dejo ir.\n2. Sandwich de cumplido-crítica-cumplido # Lo aprendí en algún libro y lo usé durante años. No funciona. La gente espera la crítica desde la primera frase y ya no oye los cumplidos, que terminan sonando a peaje.\nCómo intento corregirlo: separar. Si hay algo positivo que decir, lo digo en otro momento. Cuando hay crítica, voy al grano sin envoltorio.\n3. \u0026ldquo;Te explico cómo lo haría yo\u0026rdquo; # Sigue siendo mi tentación favorita. Cuando alguien me trae un problema, mi instinto es resolverlo en voz alta. Es satisfactorio para mí y desempodera al otro.\nCómo intento corregirlo: pregunta primero, opinión después. \u0026ldquo;¿Qué opciones has barajado?\u0026rdquo; antes que \u0026ldquo;yo haría X\u0026rdquo;.\nLa estructura que sí me funciona # Cuando tengo que dar feedback que sé que va a costar:\nHecho concreto. \u0026ldquo;En la reunión del martes, cuando te pregunté por el deploy, dijiste que estaba listo y luego resultó que faltaba el rollback.\u0026rdquo; Impacto observable. \u0026ldquo;Eso hizo que el lunes nos pillara sin plan B y perdiéramos dos horas.\u0026rdquo; Lo que esperaba. \u0026ldquo;Cuando dices que algo está listo, asumo que también está testado y con rollback.\u0026rdquo; Pregunta. \u0026ldquo;¿Cómo te suena? ¿Hay algo del contexto que se me escape?\u0026rdquo; Cuatro pasos. El cuarto es el más importante: abrir conversación, no cerrar con sentencia. Si me equivoqué en la lectura de los hechos, prefiero descubrirlo ahí que en una review.\nLo que tardé en aceptar # Que dar feedback no es un rasgo de carácter. Es una habilidad. Que se hace mal por defecto y se mejora con repetición consciente.\nY que, sobre todo, lo más útil que puedo hacer por alguien no es darle mi opinión. Es ayudarle a tener la suya con mejor información.\nEsto va contra mi instinto y contra la mitología del \u0026ldquo;líder con visión\u0026rdquo;. Pero los equipos donde la gente crece de verdad son los que tienen un manager que les da contexto y se calla, no uno que les explica el mundo.\nPróxima entrada. El feedback hacia arriba (del IC al manager) tiene su propia caja de espinas. En la siguiente parte hablo de cómo le pido a mi equipo que me critiquen sin que me digan que todo va bien. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"22 abril 2026","externalUrl":null,"permalink":"/posts/feedback/","section":"Artículos","summary":"","title":"Cómo doy (mal) feedback","type":"posts"},{"content":"","date":"22 abril 2026","externalUrl":null,"permalink":"/categories/personas/","section":"Cuadernos","summary":"","title":"Personas","type":"categories"},{"content":"Son los tres errores que llevo dos años viendo en code reviews y cometiendo yo mismo en los días malos. Ninguno es exótico. Los tres se cuelan en producción con regularidad porque el código parece correcto y los tests pasan.\nAsync no es paralelismo. Async es cooperación. La mayoría de bugs vienen de tratarlo como lo primero.\nVoy con Python (asyncio) pero los tres patrones se traducen 1:1 a JS, Rust y cualquier runtime con tareas concurrentes.\n1. await en bucle cuando podría ir en gather # El más frecuente. Lo veo en cada base de código que toco:\nasync def descargar_usuarios(ids): resultados = [] for id in ids: u = await fetch(id) # serial resultados.append(u) return resultados Si las descargas son independientes (y casi siempre lo son), esto es serial sin razón. Cien usuarios a 200ms son veinte segundos. La versión que aprovecha asyncio:\nasync def descargar_usuarios(ids): return await asyncio.gather(*(fetch(id) for id in ids)) Veinte segundos pasan a doscientos milisegundos. Cuando el bucle es independiente, gather. Cuando depende del anterior, deja el await en el bucle.\nAviso: gather sin límite contra una API ajena es una receta para 429. Usa un Semaphore o asyncio.TaskGroup con control de concurrencia:\nsem = asyncio.Semaphore(10) async def fetch_limitado(id): async with sem: return await fetch(id) 2. Mezclar código bloqueante con async # async def procesar(archivo): contenido = open(archivo).read() # bloquea el loop hash = hashlib.sha256(contenido).hexdigest() # también, si es grande return hash open().read() no es awaitable. Bloquea el event loop entero. Una request lenta puede congelar a todos los demás clientes del servicio.\nSoluciones, en orden de preferencia:\nUsar una librería async nativa: aiofiles, httpx, asyncpg. Para llamadas síncronas inevitables, asyncio.to_thread(...) aísla el bloqueo en un thread del pool: contenido = await asyncio.to_thread(_leer_sync, archivo) Para CPU pesada (no I/O): ProcessPoolExecutor. Threads no ayudan con el GIL. La regla: dentro de un async def, cada llamada o es await, o tarda microsegundos. Si tarda milisegundos y no es await, es un bug latente.\n3. Exception swallowing en create_task # asyncio.create_task(enviar_email(u)) # fire-and-forget Si enviar_email lanza una excepción y nadie hace await de esa task, la excepción muere silenciosa al recolectarse la task por el garbage collector. A veces sale un warning. A veces no.\nEl patrón seguro desde Python 3.11 es asyncio.TaskGroup:\nasync with asyncio.TaskGroup() as tg: for u in usuarios: tg.create_task(enviar_email(u)) # si alguna falló, salta aquí con ExceptionGroup Si necesitas fire-and-forget de verdad (job en background que no debe romper el flujo), al menos engánchate al resultado:\ntask = asyncio.create_task(enviar_email(u)) task.add_done_callback(lambda t: log_si_falla(t.exception())) Nunca dejes una task huérfana sin nadie observando su excepción.\nPróxima entrada. Los timeouts en async tienen su propia familia de errores: asyncio.wait_for vs asyncio.timeout, qué pasa cuando el cancel llega a mitad de un try/finally. En la siguiente parte los desentraño. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"15 abril 2026","externalUrl":null,"permalink":"/posts/errores-async/","section":"Artículos","summary":"","title":"Tres errores típicos con async","type":"posts"},{"content":"","date":"14 abril 2026","externalUrl":null,"permalink":"/categories/matematicas/","section":"Cuadernos","summary":"","title":"Matemáticas","type":"categories"},{"content":"La ecuación de Bellman tiene fama de árida. Aparece en reinforcement learning, en optimización dinámica, en finanzas, y cada disciplina la presenta con su propia notación incomprensible.\nLo que la ecuación dice es trivial: el valor de estar en un sitio es el premio inmediato más el valor descontado de estar en el sitio siguiente.\nY aun así, cuando uno la ve por primera vez en forma de operador T, pierde cualquier intuición. Voy a intentar devolverla.\nLa idea, sin matemáticas # Imagínate un laberinto con celdas. Cada celda tiene un valor: cuánto premio esperas obtener si empiezas ahí y juegas óptimamente.\nPara calcular ese valor, hay un proceso iterativo:\nEmpiezas adivinando. Todas las celdas valen cero. Es una adivinanza terrible, pero da igual. Mejoras la adivinanza mirando a los vecinos: \u0026ldquo;el valor de esta celda es el premio que recibo aquí + el mejor valor de la celda a la que me podría mover\u0026rdquo;. Repites hasta que los valores dejan de cambiar. Ese proceso de \u0026ldquo;mejorar la adivinanza\u0026rdquo; es exactamente el operador de Bellman. Aplicarlo una vez. Otra. Otra. Hasta converger.\nLo geométrico # Pensar el operador como una función T(v) → v' te lleva a este dibujo mental:\nTienes un espacio donde cada punto es una posible función de valor (todas las celdas asignadas a un número). T mueve puntos a otros puntos en ese espacio. Hay un punto fijo único v* que satisface T(v*) = v*. Ese punto fijo es la solución óptima. Cada aplicación de T te acerca al punto fijo en un factor γ (el descuento), constante. Esto último es lo que se llama contracción. Y es lo que garantiza que el proceso iterativo converge: cada paso reduce la distancia al óptimo al menos en γ.\nVisualízalo como una espiral hacia un centro. No te acercas igual de rápido todo el rato, pero te acercas siempre.\nEl operador T como contracción: la trayectoria de estimaciones espiralea hacia el punto fijo v*, acercándose un factor γ en cada aplicación. Por qué importa para tu código # Si has implementado value iteration en un agente de RL, esto es lo que estás haciendo:\ndef value_iteration(estados, transiciones, premio, gamma=0.95, tol=1e-6): V = {s: 0.0 for s in estados} while True: V_nuevo = {} for s in estados: V_nuevo[s] = max( premio(s, a) + gamma * sum(p * V[s_] for s_, p in transiciones(s, a)) for a in acciones(s) ) if max(abs(V_nuevo[s] - V[s]) for s in estados) \u0026lt; tol: return V_nuevo V = V_nuevo El V_nuevo = T(V) ese es el operador. El bucle es la contracción.\nTres cosas se entienden mejor con la imagen geométrica:\nPor qué siempre converge. Estás en un espacio métrico completo y T es contracción. Banach garantiza el punto fijo. No hay misticismo. Por qué γ cercano a 1 converge despacio. El factor de contracción es γ. Si γ = 0.99, cada paso reduce la distancia un 1%. Cien pasos para reducir un 63%. Por qué cambiar la política no rompe el método. Si en lugar de max haces sum_a π(a|s) * (...), el operador resultante también es contracción. Eso es el operador de Bellman para una política fija. Mismo dibujo, otro punto fijo. El truco que pocas veces te cuentan # El operador tiene dos formas distintas que conviene no confundir:\nT* (operador óptimo): usa max sobre acciones. Su punto fijo es V*, la función de valor óptima. T^π (operador de evaluación): usa la política π fija. Su punto fijo es V^π, el valor de seguir esa política. Métodos como policy iteration alternan: evalúan con T^π hasta converger (calculan V^π para una π fija), y luego mejoran la política mirando ese V^π. Es dos puntos fijos anidados en lugar de uno.\nSaber esto cambia cómo lees cualquier paper de RL. Casi todos los algoritmos son una variación sobre cuándo aplicar qué operador y qué aproximar con redes.\nPróxima entrada. El siguiente paso natural es Q-learning, donde el operador deja de mover funciones de valor y empieza a mover funciones de acción-valor. Mismo principio, otra superficie. En la próxima. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"14 abril 2026","externalUrl":null,"permalink":"/posts/bellman/","section":"Artículos","summary":"","title":"Operadores de Bellman, visualmente","type":"posts"},{"content":"WebGPU lleva años \u0026ldquo;a punto de estar listo\u0026rdquo;. En 2026 está, por fin, casi listo. Chrome y Safari lo soportan en estable; Firefox detrás pero avanza. Lo que queda son las esquinas: WGSL, tooling y debugging.\nWebGPU no es \u0026ldquo;WebGL pero mejor\u0026rdquo;. Es una abstracción más cercana a Vulkan/Metal con todo lo bueno y todo lo doloroso.\nLo que sigue son las notas que me hubiera gustado leer antes de gastar tres semanas en mi primer proyecto serio.\nEl cambio mental respecto a WebGL # WebGL se diseñó como abstracción sobre OpenGL ES 2.0. Esto significa: estado global mutable, drivers compensando por ti, optimizaciones implícitas.\nWebGPU asume que tú sabes lo que haces:\nPipelines explícitos. Defines un GPURenderPipeline o GPUComputePipeline con todo el estado de antemano. Cambiar de pipeline es caro; intentas no hacerlo. Bind groups en lugar de uniformes sueltos. Los recursos van en grupos predeclarados con un layout. Es más verboso. Es también predecible. Command encoders. Construyes una lista de comandos y los envías de golpe. Sin draw calls aislados. La primera semana lo odias. La segunda empiezas a apreciar que el código sea más predecible. La tercera te das cuenta de que las optimizaciones que en WebGL eran magia oculta, en WebGPU son explícitas y por eso las puedes razonar.\nWGSL: el lenguaje que te toca aprender # WebGPU no usa GLSL. Usa WGSL, un lenguaje propio. Es como Rust + HLSL en sintaxis. Razonable, escueto, sin sorpresas. Pero es otro lenguaje, y eso significa:\nTu vertex/fragment shader hay que reescribirlo. Hay convertidores GLSL → WGSL pero no son perfectos. El tooling de syntax highlight + LSP está naciendo. Espera fricción. Documentación oficial es decente pero ejemplos completos escasean. @vertex fn vs_main(@location(0) pos: vec3f) -\u0026gt; @builtin(position) vec4f { return vec4f(pos, 1.0); } @fragment fn fs_main() -\u0026gt; @location(0) vec4f { return vec4f(1.0, 0.5, 0.0, 1.0); } Si vienes de GLSL, lo lees del tirón. Si vienes de cero, también.\nCómputo: aquí está el dinero # Lo más interesante de WebGPU no es renderizado. Es cómputo de propósito general en GPU desde el navegador. Cosas que en 2024 requerían WebAssembly + SIMD para acercarse, ahora se hacen en compute shaders sin esfuerzo.\nCasos reales que probé:\nInferencia ONNX en GPU. onnxruntime-web con backend WebGPU da factores 10x sobre WASM en modelos chicos. Procesamiento de imágenes. Convoluciones, filtros, debruido. Las hago en compute shader; latencia interactiva con webcam de 4K. Simulaciones físicas / partículas. Cien mil partículas a 60fps en un MacBook Air no es tan distinto a un demo nativo. El cuello de botella ya no es \u0026ldquo;el navegador no llega\u0026rdquo;. Es \u0026ldquo;no sé escribir buen código WGSL\u0026rdquo;.\nLas esquinas que duelen # Debugging. No hay equivalente a gl_FragColor = vec4(uv, 0, 1) que sea trivial. Hay que sacar buffers, leerlos en JS y console.log. Renderdoc + Chrome funciona, pero requiere setup. Soporte móvil. Android va. iOS estable solo desde Safari 17. Antes de eso, pantalla negra. Memoria. Crear buffers/textures por frame mata el rendimiento. Hay que reusar agresivamente. WebGPU no recolecta como WebGL. Compatibility mode. WebGPU \u0026ldquo;ligero\u0026rdquo; para dispositivos sin GPU moderna está en discusión. Mientras tanto, fallback a WebGL es obligado para producción seria. Para qué empezaría hoy en WebGPU # Cualquier cosa con compute pesado en GPU: ML, simulación, image processing. Visualización de datos grande (millones de puntos) donde WebGL ya cruje. Demos / prototipos donde el control fino justifica la curva. Para qué seguiría con WebGL # Compatibilidad con dispositivos viejos y mercado masivo. Stack ya escrito y funcionando. Migrar por migrar es caro y aporta poco si no aprovechas compute. Librerías 3D maduras (three.js, babylon.js) donde WebGPU como backend aún no está al 100%. Próxima entrada. Mi setup mínimo de proyecto WebGPU con Vite, TypeScript y hot reload de WGSL. En la siguiente. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"10 abril 2026","externalUrl":null,"permalink":"/posts/webgpu/","section":"Artículos","summary":"","title":"Notas sobre WebGPU","type":"posts"},{"content":"Click es una librería preciosa. La he usado durante años. Pero el setup mental de Click (decoradores, contextos, grupos) solo se rentabiliza si la CLI va a vivir mucho tiempo y crecer.\nPara el 80% de los scripts internos, sys.argv y un match resuelven mejor que cualquier framework.\nLo que sigue es lo que uso ahora, después de un año limpiando CLIs propias y ajenas.\nLa heurística # Antes de instalar nada, me hago tres preguntas:\n¿Va a tener más de tres subcomandos? ¿Va a tenerlos subcomandos anidados (git remote add ...)? ¿Va a ser distribuido a gente que no lo escribió? Si la respuesta a las tres es no, no necesitas Click. Probablemente tampoco necesites argparse con todas sus secciones.\nNivel 0: sys.argv y nada más # import sys def main(): args = sys.argv[1:] if not args or args[0] in (\u0026#34;-h\u0026#34;, \u0026#34;--help\u0026#34;): print(\u0026#34;uso: limpiar \u0026lt;ruta\u0026gt; [--dry-run]\u0026#34;) return 0 ruta = args[0] dry_run = \u0026#34;--dry-run\u0026#34; in args return ejecutar(ruta, dry_run=dry_run) if __name__ == \u0026#34;__main__\u0026#34;: raise SystemExit(main()) Cuatro líneas de \u0026ldquo;parsing\u0026rdquo;. Suficiente para el 60% de los scripts que tengo en ~/bin.\nNivel 1: argparse minimalista # Cuando aparece un flag con valor (--retries=3) o más de una posicional, paso a argparse pero sin subcomandos:\nimport argparse def main(): p = argparse.ArgumentParser(description=\u0026#34;Limpia builds viejos\u0026#34;) p.add_argument(\u0026#34;ruta\u0026#34;) p.add_argument(\u0026#34;--retries\u0026#34;, type=int, default=3) p.add_argument(\u0026#34;--dry-run\u0026#34;, action=\u0026#34;store_true\u0026#34;) a = p.parse_args() return ejecutar(a.ruta, retries=a.retries, dry_run=a.dry_run) Sigue siendo legible de un vistazo. Sigue siendo cero dependencias.\nNivel 2: subcomandos con match # Cuando aparece el primer subcomando, no salto a Click. Salto a un match sobre el primer argumento:\ndef main(): cmd, *rest = sys.argv[1:] or [\u0026#34;help\u0026#34;] match cmd: case \u0026#34;build\u0026#34;: return cmd_build(rest) case \u0026#34;clean\u0026#34;: return cmd_clean(rest) case \u0026#34;deploy\u0026#34;: return cmd_deploy(rest) case \u0026#34;help\u0026#34; | \u0026#34;-h\u0026#34; | \u0026#34;--help\u0026#34;: print(__doc__) return 0 case _: print(f\u0026#34;comando desconocido: {cmd}\u0026#34;, file=sys.stderr) return 2 Cada cmd_* parsea sus propios flags con argparse local. La CLI crece sin que el archivo principal se convierta en un grafo de decoradores.\nCuándo sí usar Click # Cuando el comando es público y la documentación generada importa. Cuando hay completion en shell que el usuario debe instalar. Cuando hay grupos anidados de verdad, no por adelantarse al futuro. Click resuelve esos tres problemas mejor que nada. Solo que no son los problemas que tenía mi script de cinco minutos.\nTres heurísticas # Empieza por sys.argv. Si te pica, sube un nivel. Casi nunca pica. Subcomandos = match, no decoradores. El tipo case es una herramienta de routing tan buena como cualquier router. No tomes la decisión a priori. Empieza pequeño y migra cuando el dolor sea real. Próxima entrada. Los flags --dry-run y --verbose tienen patrones de implementación que merece la pena estandarizar. En la siguiente parte cuento los míos. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"2 abril 2026","externalUrl":null,"permalink":"/posts/cli-sin-frameworks/","section":"Artículos","summary":"","title":"CLI: argumentos sin frameworks","type":"posts"},{"content":"Cada año reescribo cómo tomo notas. Cada año pienso que esta vez es definitivo. Cada año me equivoco. La novedad de 2026 es que esta vez sí parece estable, llevamos doce meses con el mismo sistema y la única razón es que el sistema es mucho más aburrido que los anteriores.\nCuanto más vistoso es tu sistema de notas, menos lo usas.\nHe pasado por Notion, por Obsidian con doscientos plugins, por Logseq, por Tana, por Roam. Cada herramienta brillante. Y cada vez gastaba más tiempo manteniendo el sistema que escribiendo notas.\nEl sistema actual, en una pantalla # Markdown plano en una carpeta notas/ versionada con git. Tres tipos de fichero: diario/, proyectos/, referencia/. VS Code como editor por defecto. Sin plugins exóticos. ripgrep para buscar. Cero linking automático. Cero base de datos. Cero plugins. Eso es todo. Está deliberadamente desnudo y por eso sigue vivo.\nLo que llamo \u0026ldquo;diario\u0026rdquo; # Un fichero por día en diario/YYYY/MM/YYYY-MM-DD.md. Lo abro a primera hora y lo cierro a última. Estructura mínima:\n# 2026-04-02 ## hilos abiertos - [ ] revisar PR #142 - [x] llamar a M. ## notas - charla con J.: piensa que el ramp del feature está demasiado rápido. apuntar. ## salud / fuera de trabajo - 7h dormidas, corrí 30\u0026#39;. cansado. No es un journal, no es una agenda, no es un task tracker. Es un sitio donde dejo el cerebro al final del día y lo recojo a la mañana siguiente. Si quiero revisar qué pasó hace dos meses un viernes, hago rg por la fecha.\nLo que llamo \u0026ldquo;proyectos\u0026rdquo; # Un fichero por proyecto activo, en proyectos/. Vivos mientras el proyecto lo está. Cuando termina, se mueve a proyectos/archivo/.\nCada fichero tiene cuatro secciones fijas:\n# proyecto X ## objetivo una frase, lo más concreta posible. ## estado actual qué está pasando ahora. máximo cinco líneas. ## próximas decisiones qué tengo que resolver y cuándo. ## bitácora [2026-04-01] decisión: vamos con la opción B porque ... [2026-03-28] reunión con M., quedamos en que ... La sección de bitácora es la única que crece. Las otras tres se reescriben cuando cambian. Es deliberado: leer el archivo siempre debe darte el estado actual en los primeros tres bloques.\nLo que llamo \u0026ldquo;referencia\u0026rdquo; # Cosas que vuelvo a consultar: snippets de comandos, configuraciones que olvido, lecturas que quiero recordar.\nEstructura libre. La única regla: si no he abierto un fichero en seis meses, se va a referencia/archivo/. Reducir la cantidad de cosas a las que mirar es más valioso que reordenarlas.\nLas reglas que me funcionan # Una nota = un fichero. No subcarpetas profundas. No bases de datos. find y rg resuelven todo. El presente vivo es siempre poco. Si tengo más de cinco proyectos abiertos, alguno se duplica con otro. Auditar mensualmente. Búsqueda como interfaz principal. Linking elaborado es un proxy para \u0026ldquo;no sé buscar\u0026rdquo;. rg -i ha resuelto el 95% de los problemas que un grafo prometía resolver. Nada de etiquetas. El pasado intento de tags fue una piscina de basura. Las palabras del texto son suficientes. Backup ≠ sistema. El backup es git push diario. El sistema es cómo escribes, no dónde lo guardas. Por qué importa la aburrición # Cada sistema brillante que probé tenía una promesa: \u0026ldquo;esta vez no se va a romper\u0026rdquo;. Y siempre se rompía cuando llegaba una semana intensa, porque mantener el sistema requería atención que no tenía.\nMarkdown plano + git + VS Code no requiere atención. Está ahí, no se rompe, no exige nada. Es lo mínimo que se puede hacer y por eso es lo que sobrevive a los meses malos.\nTu sistema de notas tiene que aguantar tu peor semana, no tu mejor sábado por la mañana.\nPróxima entrada. El paso a Obsidian / Logseq / Tana siempre falla por la misma razón: confundes \u0026ldquo;tomar notas\u0026rdquo; con \u0026ldquo;modelar conocimiento\u0026rdquo;. En la siguiente entrada lo desarrollo. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"2 abril 2026","externalUrl":null,"permalink":"/posts/notas-2026/","section":"Artículos","summary":"","title":"Mi sistema de notas en 2026","type":"posts"},{"content":"","date":"2 abril 2026","externalUrl":null,"permalink":"/categories/personal/","section":"Cuadernos","summary":"","title":"Personal","type":"categories"},{"content":"La pregunta sale en cada arranque de equipo: \u0026ldquo;buscamos un senior de React\u0026rdquo; o \u0026ldquo;buscamos un buen ingeniero, da igual el stack\u0026rdquo;. Las dos posturas tienen razones razonables. Y las dos, llevadas al extremo, fallan.\nContratar por stack escala mal. Contratar por talento abstracto sube el coste de onboarding.\nHe probado las dos en momentos distintos. Lo que sigue es lo que aprendí del coste real de cada una.\nLa trampa del puesto exacto # \u0026ldquo;Necesitamos a alguien que sepa React 18, Next.js, Tailwind, tRPC, Prisma y Postgres.\u0026rdquo;\nSuena específico, parece eficiente. En la práctica:\nEl pool de candidatos con esa intersección exacta es diez veces más pequeño que el de candidatos con cuatro de los seis. Esa especificación se queda obsoleta en dieciocho meses. La persona contratada para \u0026ldquo;React 18 + tRPC\u0026rdquo; puede acabar manteniendo otra stack distinta en dos años. Quien encaja al 100% en el job description suele tener menos margen para aprender. Es un proxy de \u0026ldquo;ya sabe lo que va a tener que hacer\u0026rdquo;, no de \u0026ldquo;puede crecer en lo que venga\u0026rdquo;. He visto equipos atascados durante meses buscando el perfil exacto. Cuando por fin contratan, han pagado tres meses de retraso para reducir el onboarding una semana.\nLa trampa del \u0026ldquo;buen ingeniero abstracto\u0026rdquo; # El extremo opuesto: \u0026ldquo;no nos importa el stack, contratamos cabeza\u0026rdquo;.\nEs lo que Google y compañía hicieron durante años. Algoritmos en la pizarra, candidatos brillantes que llegaban sin haber tocado el stack interno.\nEl coste oculto:\nOnboarding técnico real: seis a doce semanas hasta productividad neta. En equipos pequeños no te lo puedes permitir. Coste de oportunidad de los seniors actuales que dedican esas semanas a explicar la stack en lugar de ejecutar. Riesgo de mismatch cultural con el lenguaje: \u0026ldquo;voy a reescribirlo en Rust\u0026rdquo; después de tres semanas no es lo que necesitabas oír. Y otro problema sutil: el \u0026ldquo;buen ingeniero abstracto\u0026rdquo; suele tener su propia opinión sobre arquitectura, tooling y procesos. Si tu equipo es chico y ya está formado, esa opinión es valiosa pero también potencialmente disruptiva.\nLa heurística que uso ahora # Para cada hire, antes de abrir el puesto, respondo dos preguntas:\n¿En cuánto tiempo necesito contribución neta? ¿Cuánto va a cambiar la stack en dos años? Y según las respuestas:\nPlazo / Cambio de stack Stack cambia poco Stack cambia mucho Necesito ya (1-2 meses) Por stack Por stack adaptable (lenguajes próximos, ecosistema similar) Tengo tiempo (3-6 meses) Por talento + experiencia en problema parecido Por talento puro Lo importante es que rara vez estoy en la diagonal cómoda. Casi siempre es un trade-off. La heurística obliga a hacerlo explícito.\nLo que importa más que el debate # En la mayoría de los hires que hice, ni \u0026ldquo;stack\u0026rdquo; ni \u0026ldquo;talento abstracto\u0026rdquo; eran el factor decisivo. Lo era:\nComunicación escrita. Cómo escribe el candidato un email, una review, una propuesta. Predice 70% del trabajo que va a hacer en remoto. Capacidad de decir \u0026ldquo;no sé\u0026rdquo;. Tres veces en una entrevista técnica. Si no aparece nunca, es bandera roja, especialmente en seniors. Curiosidad genuina por qué hacemos y no solo por cómo. Las mejores preguntas que me han hecho candidatos eran sobre el negocio, no sobre el stack. Esos tres ejes correlacionan mejor con \u0026ldquo;esta persona va a funcionar aquí\u0026rdquo; que la suma del stack y el talento.\nLa regla práctica # Si tienes que escoger entre dos candidatos donde uno sabe el stack pero te incomoda algo de su forma de comunicar, y el otro no sabe el stack pero comunicarías con él sin esfuerzo:\nContrata al segundo. El stack se aprende en seis semanas. La forma de pensar y comunicar, no.\nEsa regla ha sido la más cara de seguir y la más rentable de las que he aplicado.\nPróxima entrada. Las primeras dos semanas de onboarding tienen un patrón que sí escala. En la siguiente parte cuento la plantilla que uso, escrito como si fuera para alguien que aún no llegó. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"30 marzo 2026","externalUrl":null,"permalink":"/posts/stack-vs-talento/","section":"Artículos","summary":"","title":"Stack vs. talento: qué contratar primero","type":"posts"},{"content":"El verano pasado pasé tres horas en una mesa junto a un piloto de aerolínea jubilado. No le pregunté por aviones. Me preguntó por software. Acabé tomando notas en el móvil.\n\u0026ldquo;El error nunca es lo que ves. Es lo que dejaste de mirar mientras pasaba lo otro.\u0026rdquo;\nEsto y otras cinco cosas que se quedaron conmigo, traducidas como pude al lenguaje del software.\n1. \u0026ldquo;El error nunca es lo que ves\u0026rdquo; # En aviación el modelo mental que llaman checklist culture asume que cuando algo va mal, lo siguiente que va a fallar no es lo que ya estás mirando: es la cosa contigua que dejas de revisar porque tu atención se la lleva el incidente.\nEn código es idéntico. La cascada de errores en producción no empieza con la base de datos caída. Empieza con el alert mal calibrado tres semanas atrás, que pasaste por alto porque tenías la cabeza en otra cosa. Cuando viene el incidente real, esa señal débil ya estaba ahí, ignorada.\nConclusión que me llevé: los problemas pequeños desatendidos no se quedan pequeños. Se enganchan al siguiente grande.\n2. \u0026ldquo;Nadie cae por una decisión. Caen por la última de muchas.\u0026rdquo; # Me contó el caso del Air France 447. No hubo un fallo. Hubo una congelación de sondas, una desconexión del piloto automático, una transición a control manual sin entrenamiento adecuado, un pull-up sostenido por nervios, y la negación a leer correctamente unos instrumentos. Cualquier rotura aislada habría sido recuperable. Las cinco juntas, no.\n\u0026ldquo;Ningún profesional cae por una decisión equivocada. Cae por la última de una secuencia de decisiones razonables.\u0026rdquo;\nEn post-mortems de software es lo mismo. Buscar al culpable de \u0026ldquo;la decisión que rompió\u0026rdquo; siempre falla. La decisión que rompió fue razonable en su contexto. Lo que falló es la cadena de decisiones que llevó a ese contexto. Es lo que en aviación llaman Swiss cheese model: cada capa tiene agujeros, y ocasionalmente los agujeros se alinean.\n3. \u0026ldquo;El procedimiento se sigue cuando todo va bien\u0026rdquo; # Le dije que en software a veces nos saltamos procesos cuando ya conocemos bien el sistema. Sonrió.\n\u0026ldquo;Si te saltas el procedimiento cuando todo va bien, lo vas a saltar cuando va mal, que es exactamente cuando lo necesitas.\u0026rdquo;\nLos pilotos hacen el checklist incluso después de diez mil horas. No porque no se acuerden. Porque la disciplina del checklist es lo que protege cuando tu memoria está nublada por adrenalina.\nLo apliqué a deploys. Mi script de pre-deploy es ridículo de simple y verifico cosas que siempre están bien. Justo por eso me protege el día que algo no esté.\n4. \u0026ldquo;El co-piloto que no contradice es un peligro\u0026rdquo; # En aerolíneas existe el concepto de Crew Resource Management: el co-piloto debe tener autoridad real para decirle al capitán que se equivoca. Hubo desastres concretos en los 70 y 80 donde el co-piloto sabía que algo estaba mal y no lo dijo claro por jerarquía. Se rediseñó la cultura de cockpit en consecuencia.\n\u0026ldquo;Si tu junior no te discute, es porque tiene miedo. Y entonces tú vas a chocar.\u0026rdquo;\nEn equipos de ingeniería, una persona junior que nunca te discute es bandera roja, no señal de respeto. La autoridad real del senior no se mide por lo que la gente le obedece. Se mide por lo que la gente se atreve a contradecirle.\n5. \u0026ldquo;La planificación es la mitad del vuelo\u0026rdquo; # Me explicó que un vuelo comercial de tres horas tiene atrás varias horas de briefing: ruta, alternates, fuel, NOTAMs, clima. Es trabajo invisible y obligatorio. Si lo saltas, vuelas con menos margen y no lo sabes hasta que el margen importa.\nEn software lo despreciamos. \u0026ldquo;Less talk, more code\u0026rdquo;. El piloto se rió cuando se lo conté.\n\u0026ldquo;Eso es muy bonito cuando todo es predecible. Pero todo lo importante no lo es.\u0026rdquo;\n6. \u0026ldquo;Hay que aterrizar el avión en el que estás\u0026rdquo; # La frase con la que cerró la conversación. Yo le había contado que estaba pensando dejar un proyecto que ya no me motivaba.\n\u0026ldquo;El problema de saltar de aviones a media altura es que no eres bueno en ninguno. Hay que aterrizar el avión en el que estás antes de subirse a otro.\u0026rdquo;\nNo es generalización válida. A veces hay que saltar. Pero como contrapeso a la cultura de quitar lo aburrido, ahí estaba.\nPróxima entrada. Las analogías de aviación con software dan para mucho más. En la siguiente hablo del sterile cockpit (la regla de no hablar de nada que no sea relevante durante despegue y aterrizaje) y cómo la traduzco a deploys. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"22 marzo 2026","externalUrl":null,"permalink":"/posts/piloto/","section":"Artículos","summary":"","title":"Cosas que me dijo un piloto","type":"posts"},{"content":"","date":"22 marzo 2026","externalUrl":null,"permalink":"/categories/misc/","section":"Cuadernos","summary":"","title":"Misc","type":"categories"},{"content":"Esto va a sonar herético. Llevo cuatro años defendiendo TypeScript en cada proyecto donde he podido. Y aun así, este año saqué TS de tres bases de código y no quiero ponerlo de vuelta.\nTypeScript no es gratis. La factura llega tarde, pero llega.\nNo es una postura. Es una observación de dónde el coste superó al beneficio.\nEl coste que no se ve # El argumento clásico es: \u0026ldquo;TS te da seguridad, te ahorra bugs en producción\u0026rdquo;. Verdadero. Lo que no se cuenta:\nMantener @types/* actualizados cuando una dependencia cambia. tsc --watch y el lag perceptible al guardar. Tipos que mienten porque la librería externa no se actualizó. Refactors que se atascan en errores de tipos en cascada de archivos que tú no querías tocar. El propio diseño de tu API condicionado por qué tipos sabes expresar. Ninguno de esos costes es enorme. Sumados sí.\nLas tres bases de código donde lo quité # 1. Un script de cron en Node # Cuarenta líneas que corren cada noche y leen una API. El tipado de la respuesta es un unknown enmascarado. El parsing real lo hace Zod. ¿Qué me daba TS sobre JS + Zod? Nada que no estuviera ya en el z.infer\u0026lt;typeof Schema\u0026gt;.\nLo pasé a JS + JSDoc para los handlers y z.infer para los DTOs. CI bajó un minuto.\n2. Un build script de un monorepo # esbuild + plumbing. Cero lógica de negocio, mucha manipulación de strings y rutas. El value de los tipos era marginal. La fricción al cambiarlo, alta.\nJS + // @ts-check con JSDoc en las funciones de utilidad. Lo mejor de los dos mundos: aviso del editor sin compilación adicional.\n3. Una librería pública # Esta sigue en TypeScript. Aquí TS sigue valiendo cada euro. Una librería que otros consumen necesita .d.ts legibles, autocompletado fiel y un contrato verificable. Es para lo que TS fue diseñado.\nLa heurística # Me quedo con TypeScript cuando:\nEl código lo va a consumir alguien que no lo escribió. Una librería, una API pública, un SDK. El dominio tiene tipos no triviales. Reducers, ASTs, máquinas de estados. El equipo tiene más de tres personas. Los tipos son documentación ejecutable. Lo saco cuando:\nEs un script personal o un job nocturno. Zod / Valibot ya hace el trabajo donde importa (el borde externo). El tiempo de iteración importa más que la solidez (prototipo, experimento, glue code). El compromiso intermedio # Para los casos del medio, // @ts-check + JSDoc en JavaScript da el 80% del valor de TS sin el coste de tsc:\n// @ts-check /** * @param {string} ruta * @param {{ dryRun?: boolean }} [opciones] * @returns {Promise\u0026lt;number\u0026gt;} */ export async function limpiar(ruta, opciones = {}) { const dryRun = opciones.dryRun ?? false; // ... } El editor te avisa. No hay paso de build. No hay @types/* que mantener. El día que el archivo crece, lo renombras a .ts y ya.\nPróxima entrada. // @ts-check tiene esquinas raras (imports CJS, tipos genéricos en JSDoc) que merece la pena conocer antes de adoptarlo. En la siguiente parte las cuento. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"20 marzo 2026","externalUrl":null,"permalink":"/posts/dejar-typescript/","section":"Artículos","summary":"","title":"Cuándo dejar de usar TypeScript","type":"posts"},{"content":"El año pasado migré tres servicios de Postgres a SQLite. Dos siguen en producción y son los más estables que tengo. El tercero volvió a Postgres a los dos meses. Aquí va el porqué de los tres.\nSQLite no es \u0026ldquo;Postgres pero más pequeño\u0026rdquo;. Es una arquitectura distinta con un trade-off muy explícito.\nEl trade-off, en una línea # SQLite te da operación trivial (un archivo) a cambio de aceptar que solo escribes desde un único proceso a la vez.\nSi tu carga real cabe en ese trato, ganas mucho. Si no, no hay magia que valga.\nLos tres servicios # 1. Generador de informes nocturno → sigue en SQLite # Lee de varias fuentes, agrega, escribe un informe por cliente. Una sola escritura nocturna. Lecturas concurrentes durante el día.\nFue donde el cambio fue más obvio. Eliminé el Postgres dedicado, su pgbouncer, sus backups custom y su monitorización. Ahora es un archivo informes.db versionado con litestream a S3. Recuperación: bajar el archivo y arrancar. Coste mensual: cero infraestructura.\nPor qué funcionó: el patrón de carga era literalmente para lo que SQLite está diseñado.\n2. API interna con cachés → sigue en SQLite # Servicio HTTP que sirve catálogo (lectura masiva) y se actualiza una vez por hora con un job. Treinta mil requests/min en pico.\nWAL mode + PRAGMA mmap_size=30000000000 y SQLite atiende esa carga sin sudar. Con --threads 4 en el binary, el bottleneck eran las CPUs de la VM, no la DB.\nDetalle clave: writes solo desde el job nocturno → cero contención de escritura.\n3. Servicio de gestión de pedidos → volvió a Postgres # Multi-tenant. Cada pedido es una escritura. Diez tenants concurrentes. Pareció ir bien dos semanas.\nCuando un tenant se puso pesado (300 escrituras/min), las escrituras de los otros tenants se ralentizaban. El lock global de SQLite es de toda la base de datos, no de la fila ni de la tabla. BEGIN IMMEDIATE resolvía las llamadas más sucias pero no la cola.\nLección: \u0026ldquo;una única conexión de escritura\u0026rdquo; significa exactamente eso. Si tu modelo asume escrituras concurrentes de verdad, SQLite no es el sitio.\nLa heurística que uso ahora # Para cada servicio nuevo, antes de decidir DB:\n¿Cuántas escrituras/segundo en pico? Si \u0026lt;50, SQLite es default. ¿Hay un único escritor o varios? Si hay varios genuinos, Postgres. ¿Necesito replicación lógica para HA? Si sí, Postgres. Si me basta con recovery, litestream + SQLite. ¿Hay tipos avanzados (JSONB con índices GIN, geo)? Si los necesito de verdad, Postgres. Es asombrosa la cantidad de servicios que pasan los cuatro filtros a SQLite.\nLo que se gana operacionalmente # Cero infraestructura. No hay un servicio que mantener separado, no hay credenciales que rotar, no hay pgbouncer. Backups triviales. Copiar el archivo (en WAL + checkpoint) o litestream para continuo. Test isolation perfecto. Cada test crea su DB en memoria. Sin docker-compose de Postgres en CI. Migraciones simples. alembic o equivalentes funcionan; pero como no hay carga concurrente, son menos arriesgadas. Deploy sin coordinación de DB. Versión nueva del código + archivo viejo = funciona. Lo que se pierde # Lecturas paralelas son perfectas; escrituras paralelas no existen. EXPLAIN de SQLite es honesto pero modesto. Extensiones (PostGIS, pgvector serio) no tienen equivalente. Tooling de observabilidad es escaso. No hay pg_stat_statements. El error que cometí # Lo conté mal a mi equipo al principio. Lo vendí como \u0026ldquo;más simple, igual de capaz\u0026rdquo;. No es igual de capaz. Es más simple para casos concretos. Cuando salió el caso del servicio multi-tenant, gasté tres semanas peleando con SQLite cuando debí migrar a Postgres en una tarde.\nSi fuera a venderlo a un equipo otra vez:\n\u0026ldquo;SQLite es la mejor base de datos del mundo para una sola escritura concurrente. Postgres es la mejor para múltiples. Elige según ese eje, no según moda.\u0026rdquo;\nPróxima entrada. Mi configuración exacta de WAL + mmap + litestream para producción, con los pragmas que uso y los que evito. En la próxima. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"15 marzo 2026","externalUrl":null,"permalink":"/posts/sqlite-prod/","section":"Artículos","summary":"","title":"Sustituyendo Postgres por SQLite","type":"posts"},{"content":"\u0026ldquo;La entropía mide el desorden.\u0026rdquo; La frase es bonita, sale en los libros de divulgación y es falsa en cuanto te apartas un milímetro del intuitivo.\nLa entropía no mide desorden. Mide cuánta sorpresa esperas de un sistema.\nLa palabra \u0026ldquo;desorden\u0026rdquo; arrastra connotaciones (caos, suciedad, dispersión) que no son lo que el formalismo dice. Cambia \u0026ldquo;desorden\u0026rdquo; por \u0026ldquo;incertidumbre\u0026rdquo; y de pronto todas las paradojas desaparecen.\nLa definición sin grima # Shannon, 1948. Si tienes una variable que puede tomar valores x_i con probabilidad p_i:\nH = -Σ p_i · log₂(p_i)\nTres lecturas distintas de esa fórmula, todas correctas:\nSorpresa media. -log₂(p_i) es lo \u0026ldquo;sorprendido\u0026rdquo; que estás cuando sale x_i. La entropía es la sorpresa que esperas en promedio. Bits necesarios. Cuántos bits hacen falta, en media, para describir un resultado con codificación óptima. Incertidumbre. Cuánto te falta saber antes de observar el resultado. Las tres son la misma cosa contada distinto. Y ninguna habla de \u0026ldquo;desorden\u0026rdquo;.\nEl experimento que clarifica # Una moneda truncada: cae cara 99% de las veces, cruz 1%.\n¿Es desordenada? Intuitivamente no: es muy predecible. ¿Tiene alta entropía? Hagamos cuentas: H = -0.99·log₂(0.99) - 0.01·log₂(0.01) ≈ 0.08 bits. Comparado con una moneda justa (H = 1 bit), la moneda trucada tiene entropía bajísima. Coincide con la intuición de \u0026ldquo;predecible\u0026rdquo;. No coincide con la intuición de \u0026ldquo;desorden\u0026rdquo;.\nAhora el caso contrario. Imagina una cadena ADN aleatoria:\nATGCATTGCAGCAGAGCAGTAGCAT...\nVisualmente parece desordenada. Cualquiera diría \u0026ldquo;está hecha un caos\u0026rdquo;. Y sin embargo, si los cuatro símbolos son equiprobables, su entropía es exactamente 2 bits/símbolo. Es alta porque cada símbolo es realmente impredecible, no porque esté visualmente revuelto.\nEl malentendido termodinámico # La entropía de Boltzmann en física es:\nS = k · ln(W)\ndonde W es el número de microestados consistentes con el macroestado observado. Si interpretas \u0026ldquo;más microestados\u0026rdquo; como \u0026ldquo;más maneras de estar revuelto\u0026rdquo;, llegas al cliché del desorden.\nPero la lectura correcta es: cuanta más incertidumbre tengo sobre el estado microscópico cuando solo veo el macroestado, más entropía. Es exactamente la misma noción de Shannon, vestida de física.\nUn cristal a 0K tiene baja entropía no porque esté \u0026ldquo;ordenado\u0026rdquo; en sentido estético, sino porque conocido el macroestado, conoces el microestado. Cero incertidumbre. Cero entropía.\nPor qué importa para programadores # Cuando dices que un fichero tiene \u0026ldquo;alta entropía\u0026rdquo; en compresión, no quieres decir que esté revuelto. Quieres decir que cada byte es difícil de predecir desde los anteriores.\nTexto en español: baja entropía. La \u0026lsquo;u\u0026rsquo; después de \u0026lsquo;q\u0026rsquo; es casi segura. ZIP de ese mismo texto: alta entropía. Cada byte parece independiente. Ruido aleatorio: máxima entropía. Cero predictibilidad → cero compresión posible. Comprimir bien = bajar la entropía aparente de tus datos haciendo explícita la predictibilidad. Y por eso un fichero ya comprimido no se vuelve a comprimir: su entropía está saturada.\nTres relecturas que ayudan # \u0026ldquo;Hay mucho desorden\u0026rdquo; → \u0026ldquo;hay mucha incertidumbre\u0026rdquo;. Cambia esto en cada texto técnico y rara vez pierdes precisión. \u0026ldquo;Entropía alta = caos\u0026rdquo; → \u0026ldquo;entropía alta = difícil de predecir\u0026rdquo;. Caos suena malo, predicción difícil suena lo que es. \u0026ldquo;Entropía sube\u0026rdquo; → \u0026ldquo;tu modelo del sistema empeora\u0026rdquo;. En termodinámica, en información, y en machine learning, la dirección de la entropía es la dirección en que tu modelo se queda corto. Próxima entrada. La entropía cruzada y la divergencia KL aparecen en todos los loss functions de ML. Se entienden mejor una vez aceptado lo de arriba: son medidas de \u0026ldquo;cuánto te equivocas en tus predicciones\u0026rdquo;. En la próxima. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"8 marzo 2026","externalUrl":null,"permalink":"/posts/entropia/","section":"Artículos","summary":"","title":"Por qué la entropía no es desorden","type":"posts"},{"content":"Llevas tres entrevistas pintando bien. Cuatro personas asienten. La quinta dice \u0026ldquo;tengo dudas pero no son bloqueantes\u0026rdquo;. Y la oferta no sale.\nEl comité que diseñas para reducir el sesgo de un único entrevistador termina siendo más conservador que el peor de ellos.\nEso es la paradoja. Y este año la pisé tres veces hasta entender qué estaba pasando.\nEl supuesto que falla # El argumento intuitivo: \u0026ldquo;varios entrevistadores votan, la decisión es más robusta que un único juicio\u0026rdquo;. Es democracia aplicada a hiring.\nEl problema: si cualquier voto negativo bloquea, el comité está optimizando para no contratar al malo, no para contratar al bueno. Es asimétrico.\nImagínate cinco entrevistadores con 80% de precisión cada uno (alta, para ser realista). Si exiges unanimidad para aprobar:\nProbabilidad de que un buen candidato pase los cinco: 0.8^5 = 33%. Probabilidad de que un candidato malo sea rechazado por al menos uno: 1 - 0.2^5 = 99.97%. Filtras casi todos los malos. Tiras dos tercios de los buenos. Si el supuesto inicial era \u0026ldquo;los buenos son escasos\u0026rdquo;, acabas de hacer escasos a los buenos en tu pipeline también.\nEl sesgo del \u0026ldquo;no estoy seguro\u0026rdquo; # Hay una segunda capa, social. Decir \u0026ldquo;sí\u0026rdquo; en un comité te expone: si el candidato resulta ser mediocre, queda el rastro de que tú lo aprobaste. Decir \u0026ldquo;no\u0026rdquo; es seguro: si resulta bueno, nadie te recuerda como el que lo bloqueó, porque ese candidato terminó en otra parte.\nEl equilibrio incentiva el \u0026ldquo;no\u0026rdquo; defensivo. Especialmente en los entrevistadores junior, que no quieren parecer poco rigurosos.\nHe visto un comité rechazar a alguien por \u0026ldquo;señales no concluyentes\u0026rdquo;. Si tres semanas después ese mismo candidato es contratado en otra empresa similar y funciona, nadie te lo dice. La señal de \u0026ldquo;rechazo erróneo\u0026rdquo; es invisible.\nLo que cambié en mi proceso # Cuatro decisiones, después de tres rondas malas:\n1. Un único decisor con veto de seguridad # Una persona toma la decisión final. Los demás entrevistadores aportan información, no votos. Solo el hiring manager (o un nivel por encima) puede vetar, y debe escribir por qué.\nEsto pone el coste de \u0026ldquo;no contratar\u0026rdquo; donde corresponde. La responsabilidad es de quien dice no, no de quien dice sí.\n2. Calibración explícita antes # Antes de la ronda, todos los entrevistadores hacen el ejercicio que va a hacer el candidato y discuten qué respuestas considerarían fuerte, decente y débil. No para \u0026ldquo;preparar la pregunta\u0026rdquo;, sino para alinear qué medimos.\nEl sesgo grande del comité es que cada uno tiene un estándar implícito distinto. Sacarlo explícito reduce un tercio de los falsos negativos.\n3. Distinguir señales de preferencias # En el debrief, separamos:\n\u0026ldquo;No es buen candidato\u0026rdquo;: riesgo identificable, con ejemplo. \u0026ldquo;No es mi tipo de candidato\u0026rdquo;: preferencia personal sobre estilo, no es un veto. El segundo tipo lo dejamos hablar pero no pesa en la decisión. Suele aflorar cuando alguien tiene una experiencia distinta a la del entrevistador.\n4. Track de las decisiones contra resultados # Cada seis meses miro los candidatos que entrevistamos (los que entraron y los que no) y comparo con lo que pasó después. Los que rechazamos y luego triunfaron en otra parte son señal de paranoia. Los que entraron y no funcionaron son señal de relajación.\nSin esa mirada hacia atrás, nunca calibras. Y la mayoría de procesos no la hacen.\nLa regla que me dejó claro lo anterior # Una manager que conocí decía:\n\u0026ldquo;Si tu proceso solo es bueno cuando filtra muchos candidatos, no tienes un proceso. Tienes una cola.\u0026rdquo;\nFiltrar es fácil. Filtrar bien, dejar pasar a los buenos sin pasar a los malos, es el trabajo. Y los comités unánimes son malos en la segunda mitad, aunque parezcan ejemplares en la primera.\nPróxima entrada. El debrief de hiring es la pieza más infravalorada del proceso. En la siguiente parte cuento mi plantilla y por qué nadie debería hablar antes de escribir su nota individual. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"5 marzo 2026","externalUrl":null,"permalink":"/posts/paradoja-comite/","section":"Artículos","summary":"","title":"La paradoja del comité de selección","type":"posts"},{"content":"El año 2022 me quemé. No fue dramático en lo externo (seguí yendo a trabajar, las reviews eran decentes, los proyectos salían), pero por dentro había un agotamiento que no se iba con vacaciones. Lo conté tarde, mal y a poca gente.\nEl burnout no es trabajar mucho. Es trabajar mucho sobre cosas que no te interesan, con poco control y sin ver el efecto.\nLo que sigue es lo que he construido desde entonces. No es una metodología. Son cuatro cosas que, juntas, han hecho que tres años después siga teniendo ganas de abrir el editor por la mañana.\n1. Una hora intocable cada día # La primera hora del día es mía. No es para email, no es para reuniones, no es para \u0026ldquo;responder rápido a J.\u0026rdquo;. Es para trabajar en lo más importante que tengo abierto.\nAntes pensaba: \u0026ldquo;voy a aprovechar la primera hora despejada para limpiar la bandeja\u0026rdquo;. Y luego nunca tenía tiempo para lo importante. Ahora la regla está al revés: si la primera hora no fue para algo que avance lo importante, el día ya no se recupera.\nLa hora exacta varía. Las cuatro reglas que la protegen no:\nSin notificaciones de ningún tipo. Sin email ni Slack abierto. Sin reuniones agendadas que se la coman. Si la pierdo por una urgencia real, la recupero a la tarde, no la salto. Suena rígido. En la práctica es lo único rígido del día. El resto puede ser caótico.\n2. Una semana \u0026ldquo;sin reuniones\u0026rdquo; cada trimestre # Cuatro veces al año bloqueo cinco días seguidos en el calendario y no acepto ninguna reunión. La gente lo sabe con antelación y lo planeamos.\nEsa semana hago una sola cosa. La que llevo meses queriendo hacer y nunca tengo profundidad para. Refactor grande, escritura larga, aprendizaje serio. Cosas que requieren 80 horas de atención continuada y que en una semana normal mueren por mil cortes.\nEl efecto secundario más valioso: la semana sirve como recordatorio de cuánto puedo producir cuando no estoy fragmentado. Eso me da la energía para defender la primera hora durante el siguiente trimestre.\n3. Cosas que hago fuera del ordenador # Esto es lo que más tarde aprendí. Durante años trabajé con la noción ingenua de que \u0026ldquo;el descanso es no trabajar\u0026rdquo;. Y descansaba no trabajando. Resultado: descansaba mal.\nLo que descansa de verdad es hacer algo que requiera atención pero no en pantalla:\nCorrer (mínimo tres veces por semana, hora y media en total). Tocar piano, mal pero a diario. Cocinar despacio, sin podcast. Una caminata sin auriculares. No son hobbies. Son la otra mitad del trabajo. Sin ellas, las ocho horas frente al editor no rinden.\n4. Decir \u0026ldquo;no\u0026rdquo; más temprano # En 2022 lo que me quemó fue acumular compromisos pequeños que cada uno parecía gestionable y que sumados eran imposibles. Cuando me daba cuenta, ya estaba dentro.\nAhora tengo una regla: antes de aceptar nada nuevo, identifico qué deja de hacerse. No mentalmente. Por escrito.\nSi no sé qué deja de hacerse, no tengo capacidad real para lo nuevo, solo el optimismo de creer que sí. Decir \u0026ldquo;no\u0026rdquo; en este caso no es defensivo, es honesto.\nLa versión literal que uso:\n\u0026ldquo;Puedo hacerlo, pero significaría posponer X dos semanas. ¿Es ese el trade-off que quieres?\u0026rdquo;\nEl 60% de las veces, quien me lo pedía no había hecho ese cálculo y replantea. El 40% sí, y entonces lo asumo con los ojos abiertos.\nLo que sigue sin estar resuelto # Tres cosas que siguen costándome:\nLos proyectos sin final claro. No tengo método. Mi instinto sigue siendo \u0026ldquo;darle más\u0026rdquo;, incluso cuando el proyecto se ha vuelto un agujero. La culpa por descansar bien. Sigue ahí, aunque mucho más callada que en 2022. Reconocer las señales pronto. Vuelvo a leer mis notas de hace tres años y veo que las señales estaban anotadas. Las leía pero no actuaba. Para esas tres no tengo todavía un método. Sigo con la sospecha de que cuando lo tenga, será también aburrido y obvio, como los cuatro de arriba.\nPróxima entrada. La semana trimestral sin reuniones requiere infraestructura social que tarda en cuajar. En la siguiente cuento cómo conseguí que el equipo la aceptara sin sentirla como abandono. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"28 febrero 2026","externalUrl":null,"permalink":"/posts/quemarse/","section":"Artículos","summary":"","title":"El método que uso para no quemarme","type":"posts"},{"content":"Los autómatas finitos suelen aparecer en la carrera, asustar, y desaparecer. Quedan en la memoria como un ejercicio académico sin uso práctico fuera de un compilador.\nEl problema no es que los autómatas sean inútiles. El problema es que ya los usas a diario y no lo sabes.\nCada regex que escribes es un autómata. Cada parser de protocolo. Cada validador de input. La idea de \u0026ldquo;estoy en un estado, leo un símbolo, transiciono\u0026rdquo; describe la mitad del código que escribimos.\nLa intuición sin formalismo # Un autómata finito es:\nUn conjunto pequeño de estados (sí / no / esperando / leyendo). Un alfabeto de símbolos que recibe uno a uno. Una tabla de transiciones que dice: \u0026ldquo;si estoy en A y leo x, paso a B\u0026rdquo;. Uno o más estados de aceptación que significan \u0026ldquo;OK\u0026rdquo;. No hay memoria. No puedes contar. Solo dónde estás y qué leíste.\nEsa restricción suena enorme. Lo es. Pero lo que sí cabe en ese cajón es sorprendente.\nTres ejemplos cotidianos # 1. Validar contraseña # inicio --letra--\u0026gt; visto_letra inicio --digito--\u0026gt; visto_digito visto_letra --digito--\u0026gt; ok visto_digito --letra--\u0026gt; ok ok --*--\u0026gt; ok \u0026ldquo;Empieza con letra o dígito y antes de acabar tiene de los dos\u0026rdquo;. Si llegas a ok antes de fin de cadena, la contraseña es válida. Esto es un autómata de cuatro estados que cabe en una función de quince líneas.\n2. Parser de URL simple # inicio --letra--\u0026gt; esquema esquema --letra--\u0026gt; esquema esquema --:--\u0026gt; dos_puntos dos_puntos --/--\u0026gt; slash1 slash1 --/--\u0026gt; slash2 slash2 --*--\u0026gt; host host --/--\u0026gt; path Te das cuenta de que estás escribiendo el flujo cada vez que parseas un protocolo. Lo que en libros se llama \u0026ldquo;tabla de transición\u0026rdquo; es lo que en tu código serían anidados if y match.\n3. Detector de palabra clave en un stream # ¿Cómo detectas la cadena ERROR en un log de bytes que llega por chunks? Si lo haces con substring, fallas en los bordes de chunk.\nLa forma robusta: autómata de seis estados (_, E, ER, ERR, ERRO, ERROR). En cualquier momento sabes \u0026ldquo;cuánto llevo de la palabra\u0026rdquo;. Cuando un byte rompe la secuencia, retrocedes al estado correcto. Esto es Knuth-Morris-Pratt desnudo y es exactamente un DFA.\nEl truco mental que da poder # Cuando te pierdes en un trozo de código con flags y banderas:\nif buscando_inicio and not encontrado: if c == \u0026#39;\u0026lt;\u0026#39;: buscando_inicio = False if c2 == \u0026#39;/\u0026#39;: cerrando = True ... Para. Dibuja un autómata. Quince segundos en un papel y la maraña se vuelve un diagrama de cuatro burbujas y cinco flechas. El código que escribes después es la implementación literal del diagrama.\nestado = \u0026#34;inicio\u0026#34; for c in stream: match (estado, c): case (\u0026#34;inicio\u0026#34;, \u0026#34;\u0026lt;\u0026#34;): estado = \u0026#34;abierto\u0026#34; case (\u0026#34;abierto\u0026#34;, \u0026#34;/\u0026#34;): estado = \u0026#34;cerrando\u0026#34; case (\u0026#34;abierto\u0026#34;, _): estado = \u0026#34;abriendo\u0026#34; case (\u0026#34;cerrando\u0026#34;, \u0026#34;\u0026gt;\u0026#34;): emitir_cierre(); estado = \u0026#34;inicio\u0026#34; ... Lo bonito: el match es el autómata. Los case son las flechas. Si añades un estado, añades dos casos. Sin banderas escondidas.\nPor qué te enseñaron mal # El bloque académico empieza por la definición formal (⟨Q, Σ, δ, q₀, F⟩), prueba teoremas sobre lenguajes regulares, y solo en el último capítulo aparece \u0026ldquo;y esto se usa para regex\u0026rdquo;.\nSi lo empiezas por el final (\u0026ldquo;toma este código asqueroso, lo voy a rescribir como autómata\u0026rdquo;), el formalismo cae solo y con sentido. El orden importa.\nPróxima entrada. El siguiente paso natural es el autómata con pila, que mete una pila y de pronto puede contar paréntesis. Y de ahí, el mundo entero de los parsers. En la próxima. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"19 febrero 2026","externalUrl":null,"permalink":"/posts/automatas/","section":"Artículos","summary":"","title":"Autómatas finitos para no creyentes","type":"posts"},{"content":"Tengo más libros sin leer que libros leídos. Llevo años con culpa intermitente por eso. Hace un año me reconcilié con la pila no leída y dejé de pensar que era un fracaso. Esta es la entrada que me hubiera ahorrado los doce años de culpa.\nLos libros no son una lista de tareas. Son una biblioteca de respuestas a preguntas que aún no te has hecho.\nLa estantería de no leídos (el antibibliothèque del que hablaba Umberto Eco vía Taleb) no es deuda. Es opción.\nLa métrica que cambió todo # Durante años medí mi consumo de libros por libros terminados al año. Era una métrica honesta y mala. Me hacía:\nEvitar libros gordos. Forzarme a terminar libros que no me estaban dando nada. Saltar de un libro a otro hasta acabar uno para \u0026ldquo;marcarlo\u0026rdquo;. Hace año y medio cambié la métrica a libros consultados con utilidad este mes. Y la composición cambió por completo. Empecé a tener libros abiertos por una página, marcados con un post-it, vueltos a guardar, recuperados seis meses después por una pregunta concreta.\nCasi ninguno está terminado. Casi todos están vivos.\nLa distinción que se me escapaba # Hay libros para leer linealmente y libros para consultar puntualmente. Mezclarlos te lleva a fallar en los dos:\nNovelas / ensayo narrativo: lectura lineal. Lo que vale es el viaje. Manual técnico / referencia / ensayo enciclopédico: consulta puntual. Lo que vale es estar ahí cuando lo necesitas. Si tratas un manual como una novela, te aburres y abandonas. Si tratas una novela como un manual, no entiendes nada.\nLa estantería de no leídos del segundo tipo es infraestructura intelectual. No la lees toda. Está ahí.\nTres categorías que uso # Tengo los libros agrupados (mentalmente, no físicamente) en tres usos:\n1. Estoy leyendo (≤ 4 a la vez) # Activos, los toco al menos una vez por semana. Si paso un mes sin abrir uno, lo bajo a la siguiente categoría sin culpa.\n2. Tengo abierto pero no estoy leyendo # Libros que toqué y dejé. No los considero \u0026ldquo;abandonados\u0026rdquo;. Son consultables. Cuando un tema surge, vuelven al grupo 1 espontáneamente. O no. Las dos están bien.\n3. No los he abierto y quizá nunca lo haga # La mayoría de la estantería. Comprados porque algo me llamó (una reseña, una recomendación, una intuición) y aún no me llegó el momento. Algunos llevan diez años esperando y los he usado dos veces, cada una valió el precio entero.\nLa justificación económica # Un libro técnico cuesta 30€. Si lo usas una sola vez para una decisión que ahorra un día de trabajo, ya pagaste por veinte libros.\nLa idea de \u0026ldquo;deuda\u0026rdquo; en la estantería no leída asume que cada libro tiene que ser leído entero para amortizarse. Es una herencia de cómo nos enseñaron a tratar los libros en el colegio: tarea con corrector. Pero un libro no es un examen. Es una herramienta opcional.\nLa heurística para comprar # Compro un libro cuando se cumple al menos una de estas tres:\nEstá conectado a una pregunta que tengo ahora. Es recomendación específica de alguien que conoce mis intereses. Lo voy a tener difícil de recordar / encontrar / comprar si paso de él. Nunca compro por:\n\u0026ldquo;Por si acaso lo necesito en general.\u0026rdquo; \u0026ldquo;Lo dice todo el mundo.\u0026rdquo; \u0026ldquo;Aprovechar una oferta.\u0026rdquo; Esas tres siempre acaban en libros no leídos sin razón, que son distintos de los no leídos con razón.\nEl cambio mental que importa # Pasar de \u0026ldquo;libros que tengo que leer\u0026rdquo; a \u0026ldquo;libros que tengo cerca por si los necesito\u0026rdquo; suena pequeño. No lo es. Quita un peso, ordena la mente sobre la estantería y, paradójicamente, me ha hecho leer más porque ya no leo por obligación.\nY cuando alguien me dice, mirando la pila, \u0026ldquo;¡tienes muchos libros sin leer!\u0026rdquo; como si fuera un reproche, la respuesta limpia ahora es: \u0026ldquo;claro, es una biblioteca\u0026rdquo;.\nPróxima entrada. Hay un grupo de libros que sí merece leerse completos y despacio, deliberadamente. En la próxima cuento cómo distingo cuáles y mi pequeña lista de los que vuelvo cada año. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"14 febrero 2026","externalUrl":null,"permalink":"/posts/libros/","section":"Artículos","summary":"","title":"Comprar libros nunca es perder","type":"posts"},{"content":"Pasé seis años intentando llegar a cero correos cada noche. Lo conseguía algunas semanas. La sensación duraba veinte minutos hasta que llegaba el siguiente.\nEl problema no era no llegar a cero. El problema era haber asumido que cero significa algo.\nInbox zero es una métrica vanidosa. Un correo procesado es uno que ya no está en bandeja. Pero \u0026ldquo;procesado\u0026rdquo; no significa \u0026ldquo;resuelto\u0026rdquo;. Significa \u0026ldquo;archivado, archivado en otra carpeta, marcado para luego, o respondido con un acuse de recibo que crea otro hilo\u0026rdquo;.\nLo que cambió # Hace año y medio dejé de pelearme con el contador. En su lugar adopté dos reglas, robadas a una persona que había trabajado en seguridad informática y veía el correo como un sistema de detección de incidentes:\nEl correo es para ti cuando alguien necesita que respondas tú específicamente. En el resto de casos (notificaciones, hilos en CC, newsletters) es un feed. Un feed no se procesa. Se mira. Ese cambio mental, sin tocar nada técnico, redujo mi tiempo en el correo a la mitad la primera semana.\nCómo organizo ahora # Tres bandejas, en orden de prioridad:\n1. Bandeja \u0026ldquo;yo\u0026rdquo; # Solo entran correos donde:\nEstoy en To: (no en CC). El remitente no es una lista o un sistema. Esta bandeja sí la proceso. Es la única que tiene que llegar a cero al final del día. Suele tener cuatro a diez correos diarios.\n2. Bandeja \u0026ldquo;equipo\u0026rdquo; # Entra todo lo que tiene en CC a mi equipo o donde estoy en BCC. La miro dos veces al día (mañana y tarde). No se procesa. Se ojea.\nSi encuentro algo que necesita acción mía, lo muevo a la bandeja \u0026ldquo;yo\u0026rdquo;. El resto desaparece de mi atención cuando cierro la pestaña.\n3. Bandeja \u0026ldquo;feed\u0026rdquo; # Newsletters, notificaciones de GitHub, Linear, Slack-to-email. La abro una vez al día. La mayoría se borra sin abrir. Algunos correos van a Read Later si tienen contenido largo.\nSi una newsletter genera más de tres \u0026ldquo;no la voy a leer\u0026rdquo; seguidos, me desuscribo. Sin culpa.\nEl truco técnico # Reglas de Gmail / Outlook / lo que uses para dirigir cada correo a la bandeja correcta automáticamente. Es trabajo de una tarde y se mantiene poco. Lo demás es disciplina, no tooling.\nNo uso Superhuman, Hey, ni clientes raros. Solo Gmail nativo + tres etiquetas + un par de filtros.\nLo que dejé de hacer # \u0026ldquo;Snooze\u0026rdquo; para mañana. Era patear el problema. Si no tengo nada que hacer con un correo hoy, no lo voy a tener mañana. Archivar como ritual. Si solo voy a buscarlo por search, no necesita carpeta. Marcar como no leído. Era el equivalente moderno de cubrir una mancha con un pañuelo. Responder fuera de horario. Crea expectativa de respuesta inmediata que luego no puedo sostener. La nueva métrica # No mido correos procesados. Mido:\nTiempo total en bandeja por día. Objetivo: menos de 45 minutos. Correos a los que respondí en más de 48 horas. Si pasa de tres a la semana, hay un problema en mis bandejas. Las dos métricas reflejan algo real: gasto y deuda. Inbox zero solo reflejaba esfuerzo invisible.\nEl cambio cultural pendiente # Esto solo funciona porque mi entorno tolera respuestas con horas de delay. Si tu trabajo es responder rápido (soporte, ventas, on-call), el sistema de arriba no se sostiene.\nPero la mayoría no somos eso. Solo lo creemos porque la cultura de oficina premia la velocidad de respuesta como proxy de productividad. Mientras esa cultura no se rompa, gastaremos décadas optimizando una métrica que no significa nada.\nPróxima entrada. Slack tiene el mismo problema con peor reputación. En la siguiente cuento cómo lo trato: como un canal de información, no como una conversación. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"10 febrero 2026","externalUrl":null,"permalink":"/posts/inbox-zero/","section":"Artículos","summary":"","title":"Inbox zero ya no es una meta","type":"posts"},{"content":"El año en el que dejé de pelearme con tres cosas y empecé a pelearme con otras dos. Lo escribo en diciembre, con la honestidad que da no tener que mantener una opinión más de doce meses.\nNo hay tecnología nueva en esta lista. Hay decisiones viejas que finalmente entendí por qué.\nLo que se quedó # uv en lugar de pip + venv + pip-tools. Un único binario que resuelve, instala y lockea en segundos. Después de cuatro meses sin tocar requirements.in ni pyproject.toml a mano, ya no veo vuelta atrás.\nRuff como linter y como formateador. El día que migré de black + isort + flake8 a un único ruff format \u0026amp;\u0026amp; ruff check el CI se acortó treinta segundos y la conversación sobre estilo desapareció del equipo.\nSQLite para cualquier cosa que no requiera escritura concurrente real. Empezó como experimento (ver Sustituyendo Postgres por SQLite) y se quedó como default.\nLo que se fue # Frameworks de CLI. Empecé el año con Click, terminé con argparse y a veces ni eso. La entrada CLI: argumentos sin frameworks cuenta cómo y por qué.\nTypeScript en proyectos sin frontend. En un script de nodo que corre una vez al día, anotar tipos era más fricción que valor. Vuelta a JS plano con JSDoc para los casos donde el tipado ayuda de verdad. Más en Cuándo dejar de usar TypeScript.\nHacer code reviews como si estuviera revisando código y no decisiones. Una mala costumbre que arrastraba desde hace años. Lo cuento en Repensando el pull request.\nLo que sigo sin resolver # Cuándo extraer una abstracción sigue siendo el problema más caro del año, sin método claro. Sigo trabajando con \u0026ldquo;tres usos y entonces sí\u0026rdquo;. Cómo medir si un refactor mereció la pena sin engañarme con métricas verdes. Cómo mantener tres lenguajes a la vez sin que cada vuelta a Python después de Rust me cueste un día. Próximo cuaderno. El archivo de Tecnología cierra el año mirando al stack desde otro ángulo: lo que el ecosistema empuja y lo que termina siendo ruido. ¿Comentarios o correcciones? info@encodigo.es.\n","date":"31 diciembre 2025","externalUrl":null,"permalink":"/posts/desarrollo-2025/","section":"Artículos","summary":"","title":"Archivo 2025: Desarrollo","type":"posts"},{"content":"Hecho con poco. Si te interesa cómo está construido este sitio:\nStack # Pieza Qué uso Generador Hugo con el tema Blowfish Tipografía Geist \u0026amp; Geist Mono · Instrument Serif para itálicas Acento Azul #60a5fa + rosa #f472b6 Hosting Un VPS pequeño Dominio encodigo.es desde marzo 2018 Búsqueda Fuse.js, integrada en el tema Comentarios Ninguno. A propósito. Analítica Ninguna. A propósito. Sobre el logo # El logotipo es un guiño: \u0026lt;/e\u0026gt; es cómo cerraríamos esto en HTML si fuera una etiqueta. Y debería serlo.\nLicencia # Todo el contenido está bajo CC BY-NC 4.0. El código de ejemplo, salvo que se indique lo contrario, está bajo MIT.\nSuscripción # RSS Newsletter mensual: escríbeme a info@encodigo.es con asunto suscribir. ","externalUrl":null,"permalink":"/colofon/","section":"Código, números y las personas detrás del software.","summary":"","title":"Colofón y stack","type":"page"},{"content":"Este sitio está pensado para lectura larga, no para barrido rápido. Tres maneras de moverte por él:\n1. Por cuaderno # Las entradas se agrupan en seis cuadernos temáticos, en el menú Cuadernos. Si vas en frío, prueba con Desarrollo o Personal.\n2. Cronológicamente # Artículos lista todo por fecha, agrupado por año. Útil si recuerdas cuándo leíste algo pero no dónde.\n3. Por etiquetas # Algunas entradas llevan etiquetas más finas; están todas en Etiquetas. Y el buscador (la lupa de la cabecera) busca en el texto de todas las entradas.\n","externalUrl":null,"permalink":"/como-leer/","section":"Código, números y las personas detrás del software.","summary":"","title":"Cómo leer este blog","type":"page"},{"content":"Un cuaderno no es un libro: no tiene índice, no tiene final, y casi nunca se relee. Pero está ahí cuando lo necesitas.\nencodigo.es es eso: una libreta pública donde anoto lo que aprendo construyendo software, leyendo y gestionando equipos. No es un manual, no es un curso, no es un newsletter.\nPor qué escribo aquí # Porque escribir es la forma más barata de pensar despacio. Si una idea no sobrevive a un párrafo, probablemente no era una idea.\nPara quién # Para mí, sobre todo. Si te sirve a ti también, mejor.\nQué no es encodigo # No es un blog técnico tipo cómo configurar nginx. No es un newsletter de actualidad. No tiene comentarios. Si quieres responder algo, escríbeme. ","externalUrl":null,"permalink":"/sobre/","section":"Código, números y las personas detrás del software.","summary":"","title":"Sobre encodigo","type":"page"}]