Variable not found

Enlaces interesantes 623

octubre 14, 2025 06:05

Desarrollador con dudas sobre cuál de las 42 unidades de medida de CSS debe utilizar

Te propongo un experimento: abre un documento en blanco con el block de notas y anota todas las unidades de medida de CSS que se te ocurran (por ejemplo, px, em, etc.) Luego, cuéntalas... ¿cuántas has recordado? Yo nueve, entre las que había una inventada 😕 Por eso me ha llamado la atención el artículo de Nikolaus Gebhardt en el que recopila las ¡42! unidades de medida que define CSS.

Una lectura de interés también el artículo Ricardo Peres explica cómo restringir el acceso a un método de acción en ASP.NET Core MVC utilizando el filtro [Authorize], atributos de autorización personalizados, políticas y otros mecanismos proporcionados por el framework.

Por último, Juan Irigoyen nos muestra una metodología práctica para generar código con IA usando prompts estructurados en formato Markdown.

El resto de contenidos interesantes recopilados durante la semana pasada, a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Arragonán

Viviendo en el péndulo. Hasta la vista Genially

octubre 13, 2025 12:00

Tras casi 4 años, esta fue mi última semana en Genially. Una decisión que me costó mucho tomar tanto por motivos personales, con un ambiente de compañerismo en la compañía que creo que no es habitual en otros lugares que tienen cierta escala y haber construido lazos con un buen puñado de personas; como por motivos profesionales, con unos cuantos desafíos que se venían a corto y medio plazo en los que sé que hubiera disfrutado y aprendido.

En este tiempo, mi rol en la empresa se fue transformando poco a poco casi sin darnos cuenta, primero influenciando solo a nivel de tecnología y terminando haciéndolo a nivel de producto, negocio y operaciones. Hubo algo que fue una constante: vivir en el péndulo entre individual/team contributor y el management.

Incorporación

Entré casi por casualidad. Conocí a Chema, CTO de Genially, unos años antes en una Pamplona Crafters y mantuvimos el contacto vía redes sociales (cuando tuiter aún molaba). Tiempo después, hablamos por otro tema y me propuso explorar si tenía sentido incorporarme. La compañía venía de crecer mucho y vieron que podía asumir un rol que resultase complementario.

Justo era un momento en el que empezaba a sentir que necesitaba un cambio profesional, pero quería estar seguro de mi siguiente paso. Así que tras haber hecho el proceso de selección, nos tomamos un par de meses y tuvimos varias conversaciones hasta que finalmente me incorporé a la compañía.

Plataforma y organización

Entré con un rol de Platform Lead, por lo que mi responsabilidad iba a ser ayudar a los equipos de producto a que pudieran enfocarse en tener impacto, tratando de mejorar la experiencia de desarrollo y la efectividad de los equipos. Como bonus, también iba a echar una mano en cuestiones más organizativas.

Aunque este era un rol de liderazgo, no había un equipo dedicado, de modo que iba a tener que influir, colaborar con distintos equipos y empujar algunas iniciativas por mi cuenta.

Sobre el trabajo de plataforma

Una vez hecho el onboarding, empezar a tener algunas iniciativas ya lanzadas y conociendo más la compañía, traté de aterrizar una serie de metas:

Las iniciativas y actividades de plataforma deben facilitar la Developer Experience y ayudar a los equipos en distintos aspectos:

  • Reducir su carga cognitiva, que se enfoquen en complejidad intrínseca y no en la extrínseca, potenciando la germana (generando aprendizaje, construyendo esquemas o automatizando su uso)
  • Habilitar actitudes data-informed en la toma de decisiones
  • Aportar la mayor autonomía y empoderamiento posibles
  • Tratar de ser lo más eficientes en cuestiones de costes económicos
  • Dar fiabilidad, que las herramientas apenas fallen

Metas que traté de tener en cuenta, definiendo e implantando prácticas, procesos y herramientas, automatizando tareas, creando documentación… Y darles seguimiento unas veces de manera cuantitativa y otras de forma cualitativa.

Algunas de las iniciativas más relevantes fueron estas:

  • Actuar como puente entre el equipo de infraestructura y los equipos de producto para acelerar la implantación del stack de observabilidad de Grafana (Loki, Mimir, Tempo).
  • Formalizar la redacción de postmortems blameless tras incidencias que afecten al servicio junto a CTO y VP de ingeniería, para fomentar el aprendizaje sistémico.
  • Apoyar al equipo de Design System con distintas actividades.
  • Ayudar en la introducción de Snowplow + Amplitude para la instrumentación de producto colaborando con el equipo de data y los primeros equipos de producto involucrados.
  • Introducir el uso de Turborepo en el monorepo para simplificar el proceso de build.

A nivel organizativo

Durante esos primeros meses se estaba planteando una reorganización del área de desarrollo de producto bastante importante. Básicamente era pasar de una estructura de squads que se organizaba de forma recurrente a una basada en líneas de trabajo que tuvieran continuidad en el tiempo.

Esto fue algo en el que me involucraron aún sin tener mucho contexto junto al resto de managers de tech y producto, en parte venía bien tener a alguien con mirada limpia aunque en ocasiones pudiera pecar de ingenuo, para cuestionar supuestos y detectar disonancias.

En aquel entonces me resultó muy útil que alguna de la gente involucrada en la reorganización estuviera hablando en términos de Team Topologies, porque nos ayudaba a tener un lenguaje común. Eso lo terminamos enfrentando con un ejercicio de Context Mapping (en un par de eventos charlé sobre ello), donde representamos buena parte del funcionamiento de la compañía para ver el encaje de equipos respecto a la situación actual.

Este ejercicio sirvió para completar la foto real a la que íbamos, e incluso permitió detectar potenciales problemas de bounded contexts en los que había muchas manos y algunas en las que había muy pocas. Así que cuando con el paso del tiempo surgieron algunos problemas relacionados, no nos pilló tan por sorpresa.

Además, institucionalizamos el dar seguimiento a lo que surgía de las retros de los equipos, ya que se estableció como práctica que se documentase por escrito al menos lo más destacado de ellas y las acciones que surgieran. Esta práctica se mantiene a día de hoy, y resulta muy útil a la capa de management como complemento a los 1:1s para detectar fricciones y puntos de mejora.

Mucho de esto y algunas cosas más, las compartí en la charla Desarrollo de producto en una Scale-Up precisamente en la Pamplona Crafters 2024.

Foto donde hay algo más de 100 personas de pie posando para la foto, fondo con árboles y suelo adoquinado

Volviendo a hacer producto (interno)

Tras un año, mi foco cambió a ser algo más todoterreno. Por un lado, ya tenía bastante controladas las dinámicas en el área de desarrollo de producto y a nivel de plataforma había aún puntos de mejora en los que iba trabajando, pero estábamos ya más en una fase de mejora continua y constante, además el equipo de infraestructura seguía introduciendo grandes mejoras. Mientras tanto, en otras áreas de la compañía se identificaban problemas mayores con los que creíamos que podía aportar más.

Ventas

Uno que terminó surgiendo fue en el equipo de ventas, al que había que buscarle solución de forma prioritaria, ya que ninguna de las líneas de trabajo a priori tenía foco en ese tema. Teníamos un proceso interno ineficiente y propenso a errores que terminaban sufriendo clientes High Touch de gran tamaño: muchos emails y llamadas de coordinación, uso de spreadsheets de gestión, tiempos de espera por parte de clientes y account managers, etc.

Para solventar el problema se decidió montar un squad específico moviendo a algunos perfiles técnicos desde algunas líneas de trabajo, y como no queríamos sacar de foco a product managers terminé involucrado en la fase de discovery para abordarlo.

Así que tocó entender los problemas que el equipo de ventas se estaba encontrando y, junto a compañeras de customer experience, entrevistarnos con clientes y gente del área de ventas. A partir de ahí, documentar el impacto que estaba teniendo, definir un primer alcance del MVP usando un user story map y preparar un kick-off para tener tanto al squad, stakeholders y el resto de la compañía alineados.

El squad trabajó en una herramienta que diera mayor autonomía a esos clientes de gran tamaño, permitiendo a account managers mantener el control y darles seguimiento. Y en mi caso, aunque no tiré ni una línea de código y que luego me quedase acompañando al squad de lejos casi como un stakeholder más, me lo pasé como un enano volviendo a participar en hacer producto en fases iniciales.

Creativo

Tiempo más tarde, nos encontrarnos otro problema importante en el área de creativo. Uno de los valores que ofrece Genially a las personas usuarias son las plantillas diseñadas por este equipo. Nos encontramos con un problema de atasco en la publicación de nuevas plantillas y cambios en las existentes que antaño no ocurría, el producto había evolucionado de un modo en el que terminó afectando en la operativa diaria de este equipo.

Esto es porque una vez diseñadas, existía un cuello de botella en el proceso de publicación tanto en el producto como en la web pública. Esta ineficiencia en el Go to Market provocaba tardar más tiempo en recuperar la inversión, era propenso a errores y frustraba a las personas de ese equipo.

Al final era un problema para el que la perspectiva Lean encajaba como anillo al dedo: Identificar el valor, mapear el flujo de trabajo, mantener un flujo continuo, aplicar sistema pull y buscar la mejora continua. Para lo cual se decidió crear de nuevo un squad que se enfocara en esta área.

Una vez analizado y mapeado el journey principal que queríamos resolver, habiendo identificado los distintos hand-offs y limitaciones de las herramientas, planteamos crear un nuevo backoffice diseñado para habilitar mayor autonomía y que simplificase su proceso. De ese modo podríamos sustituir de forma incremental el backoffice legacy, un CMS y un par de spreadsheets de gestión.

Para acelerar el proceso de publicación introdujimos: soporte i18n, gestión de estados, uso de IA generativa, mejoras en las validaciones… Además de crear un servicio que pudiera consumirse desde el producto y la web, cosa que evitaba el uso de herramientas externas y, a nivel técnico, simplificaba la infraestructura y la mantenibilidad futura.

Una vez eliminado ese cuello de botella, que con la combinación del trabajo del squad con el equipo creativo pasó de un retraso de 4 meses a estar al día, nos centramos en mover y mejorar el resto de procesos que se soportaban aún en el legacy en esta nueva herramienta, y en el camino colaborar con uno de los equipos de producto para introducir algunas mejoras conjuntamente.

Operaciones y tech

Aproximadamente el último año en Genially mi rol terminó pivotando de manera oficial a trabajar con foco en las operaciones internas de la compañía. Esto significaba estar en un punto medio entre tecnología, producto, negocio, organización y su potencial impacto en la operativa diaria de cualquier área de la compañía. Esto implicaba mucha amplitud y normalmente menor profundidad, mucha comunicación, intentar promover iniciativas que tuvieran sentido o frenar las que aparentemente necesitasen reflexionarse más, identificar posibles problemas entre áreas de forma prematura, moverme todavía más habitualmente entre diferentes grados de abstracción, etc.

Siempre con una perspectiva de desarrollo de producto, durante ese tiempo tuve oportunidad de trabajar de nuevo y esta vez mucho más involucrado con el área de ventas y desarrollo de negocio; además de empezar a hacerlo también con las de financiero, personas, soporte y comunidad.

Trabajando con más áreas

Con el área de ventas empezamos a trabajar en hacer crecer el MVP que arrancamos antaño, soportando nuevas casuísticas primero y luego acercando e integrando esta herramienta con el CRM que se usa en la compañía. En mi caso había estado involucrado en el día a día de la línea de trabajo que lo evoluciona, con un rol de facilitador y ocasionalmente de desatascador.

Junto a las áreas de financiero y personas hicimos pequeñas iniciativas coordinadas con líneas de trabajo de producto, aunque algunas más ambiciosas se quedaron en el tintero porque el coste de llevarlas a cabo las hacían inviables al menos en el medio plazo.

Con soporte empecé a trabajar muy de cerca, ya que había una tormenta perfecta de cambios en el producto, iteraciones de negocio, atasco en los tiempos de respuesta y degradación de la experiencia de cliente.

Lanzamos varias acciones para solventar la situación: mejorar flujos en el chatbot, rehacer la integración con la herramienta de soporte para enriquecer la información de clientes y mejorar la gestión de colas, introducir automatizaciones, contratar e integrar a un proveedor para pasar a tener un chat basado en agentes de IA que escalase sólo casos complejos, etc. Una vez recuperada una situación de normalidad pudimos entrar en un modo de mejora continua en soporte, y poder dedicar más tiempo a iniciativas relacionadas con comunidad.

Y en medio de todo esto apoyar en ajustes organizacionales, tanto a nivel de desarrollo de producto como en el resto de áreas; y en iniciativas transversales, por ejemplo y para sorpresa de nadie, últimamente con foco en un par relacionadas con IA generativa.

Foto donde aparecemos 12 personas posando de pie, sucios y con barro en el suelo tras haber estado limpiando una casa en las afueras de Álora, Málaga, tras la DANA 2025 que también afectó a esa zona

Conclusión

Aunque al inicio costó un poco ver cómo gestionar ese tipo de rol pendular en el que a veces era difícil manejar la cantidad de cambios de foco y de niveles de abstracción, se me dio siempre confianza y autonomía. Finalmente me sentí muy cómodo con ese tipo de responsabilidades.

Me permitió poder influir a nivel organizativo como un manager primero a nivel de área y luego tratar de hacerlo a nivel de compañía, aunque sin las responsabilidades de gestión de personas. Y de vez en cuando poder ejecutar trabajo directamente por ejemplo programando, documentando o investigando de forma autónoma o colaborando con más personas en equipo.

Tras tener la oportunidad de trabajar con tanta gente diferente y variedad de áreas en el contexto de una scale-up, cierro esta etapa en Genially habiendo crecido mucho profesionalmente y con un montón de aprendizajes: técnicos, organizativos, de personas, de negocio… y siendo un fan de la marca y la compañía.

Y ahora ¿qué?

Toca arrancar una aventura nueva que no esperaba, uno de esos trenes que sientes que son una gran oportunidad que tienes que tomar, pero eso lo cuento un poco más adelante en otro post.

» Leer más, comentarios, etc...

Variable not found

8 cosas que (quizás) no conocías sobre la clase Random de .NET

octubre 08, 2025 07:49

Brazo robótico llamado Random usando una ruleta de casino para obtener números aleatorios

Llevamos utilizando la clase Random de .NET para generar números aleatorios desde hace mucho tiempo, en su aparición con la versión 1.1 de .NET Framework (año 2002). Probablemente por eso, muchos de nosotros la usamos de forma automática, sin pararnos a estudiar todas sus posibilidades y las novedades que le han ido añadiendo a lo largo del tiempo.

En este artículo vamos a ver algunas características de la clase Random que quizás no conocías... o quizás sí, pero que no está de más refrescarlas:

  • Los números generados no son realmente aleatorios.
  • En versiones antiguas de .NET, era más probable que Random generara números impares.
  • No se debe usar Random para operaciones criptográficas.
  • No es buena idea crear un objeto Random cada vez que necesitemos un número aleatorio.
  • Si estamos en .NET 6+, mejor usar Random.Shared.
  • El método Shuffle() desordena arrays.
  • El método GetItems() permite obtener elementos aleatorios de una colección.
  • Podemos heredar de Random.

¡Vamos a ello!

Los números generados son pseudoaleatorios, no aleatorios

Aunque su propio nombre suene a aleatoriedad total, en realidad no es así. Los números generados por Random son calculados usando algoritmos matemáticos que simplemente aseguran que los números cumplirán una serie de criterios estadísticos de aleatoriedad. En definitiva, son pseudoaleatorios y siguen patrones predecibles, aunque en en la práctica sean suficientemente aleatorios para la mayoría de los casos.

La clave de su aleatoriedad está en la semilla, un valor que podemos pasar al constructor de Random y que determina la secuencia de números que se generará a continuación.

Por tanto, si creamos instancias de esta clase usando la misma semilla, obtendremos siempre la misma secuencia de números. Podéis comprobarlo fácilmente si ejecutáis este código varias veces en una aplicación consola:

var rnd = new Random(1);

for (int i = 0; i < 5; i++)
{
    Console.WriteLine($"{rnd.Next()}");
}

La salida obtenida será siempre la siguiente:

534011718
237820880
1002897798
1657007234
1412011072

Si no indicamos ninguna semilla al crear la instancia de Random, en .NET Framework se utilizará el número de ticks de la hora actual del sistema. Por tanto, si creásemos varias instancias justo en el mismo instante (por ejemplo, de forma muy seguida o desde hilos distintos), los valores coincidirían, incluso en dispositivos diferentes.

Sin embargo, en .NET Core/.NET 5 y superiores, el seed se obtendrá usando otros mecanismos de aleatoriedad estáticos por hilo. Gracias a ello, las semillas generadas serán diferentes en cada instancia de Random aunque hayan sido creadas en el mismo instante.

En versiones antiguas de .NET era más probable que se generaran números impares

La generación de números aleatorios es un proceso algorítmico y, como tal, está sujeto a errores.

En 2017, Søren Fuglede Jørgensen publicó el post "Bias in the .NET random number generator", donde mostraba que en .NET Framework 4.7 y anteriores había una tendencia estadísticamente significativa a que los números generados por la clase Random fueran impares al obtenerlos usando el método Next(). Esto se debía a errores de redondeo en las operaciones de punto flotante usadas durante el cálculo necesario para obtener los valores aleatorios.

No he podido encontrar referencias directas al momento de la subsanación del problema, aunque la introducción en .NET 6 de un nuevo algoritmo de generación de números aleatorios (Xoshiro256**) parece indicar que el problema fue corregido en esa versión.

No se debe usar Random para operaciones criptográficas

Visto el punto anterior, está claro que la clase Random no es adecuada para operaciones criptográficas, porque para "adivinar" los números generados bastaría con conocer la semilla utilizada en su creación.

Las operaciones criptográficamente seguras requiere una aleatoriedad real (o, al menos, lo más real posible) y sin posibilidad de predecir los valores generados.

Para estos casos, .NET proporciona la clase System.Security.Cryptography.RandomNumberGenerator, que genera números aleatorios de alta calidad y considerados seguros para operaciones criptográficas, aunque es a costa de un rendimiento peor que Random.

No es buena idea crear una instancia de Random cada vez que necesitemos un número aleatorio

Si cada vez que necesitamos un número aleatorio creamos una nueva instancia de Random:

  • Si usamos .NET Framework, y sobre todo si estamos en un entorno multi-hilo, hay muchas posibilidades de que generemos valores repetidos.
  • Si especificamos manualmente la semilla, obtendremos siempre la misma secuencia de números.
  • Estamos introduciendo una penalización de rendimiento, porque la creación de Random es un proceso relativamente costoso debido a la inicialización del generador de números aleatorios.
  • Estamos consumiendo más memoria de la necesaria, ya que cada instancia de Random almacena su propio estado interno.

Por esta razón, es recomendable la creación de un único objeto Random y reutilizarlo en toda la aplicación. Sin embargo, hay que tener en cuenta que Random no es thread-safe: distintos hilos accediendo a la misma instancia podrían dar lugar a condiciones de carrera y corrupción del estado interno del generador. Por tanto, en entornos multi-hilo, habría que sincronizar el acceso a la instancia compartida para que sólo un thread acceda a la instancia en un momento determinado.

Si usamos .NET 6 o superior, mucho mejor usar Random.Shared

En .NET 6 y superiores, se introdujo la propiedad estática Random.Shared, que devuelve una instancia de Random que puede ser compartida y reutilizada de forma segura desde cualquier punto de la aplicación, incluso si necesitamos generar números aleatorios desde distintos hilos concurrentemente, porque la instancia retornada es thread-safe.

De esta forma, de un plumazo evitamos:

  • La repetición de valores debido al uso del mismo seed.
  • La penalización de rendimiento por la creación de múltiples instancias.
  • El consumo innecesario de memoria.
  • La necesidad de sincronizar el acceso a la instancia compartida en entornos multi-hilo.

Y obviamente, su uso es muy sencillo. La referencia a Random.Shared, retornará ya una instancia de Random lista para ser usada, así que basta con llamar directamente a sus métodos de generación de números aleatorios:

var numberBetween0And100 = Random.Shared.Next(0, 101);

El método Shuffle() permite desordenar un array

Hace muuuuchos años vimos por aquí cómo desordenar un array en C# y VB.NET utilizando el algoritmo llamado Fisher-Yates shuffle.

Pues bien, desde .NET 8 no es necesario andarse con esas soluciones manuales que, aunque no son complejas, al final tenemos que dedicarle su tiempo. La necesidad es tan frecuente que la propia clase Random incluye el método Shuffle<T>() que se encarga de desordenar el array de elementos de tipo T que le pasemos como argumento, y además hacerlo de forma eficiente utilizando optimizaciones internas.

Por ejemplo, si tenemos un array de números enteros y queremos desordenarlo, basta con hacer lo siguiente:

int[] numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Random.Shared.Shuffle(numbers);
Console.WriteLine(string.Join(", ", numbers));

string[] texts = ["one", "two", "three", "four", "five"];
Random.Shared.Shuffle(texts);
Console.WriteLine(string.Join(", ", texts));
10, 2, 1, 9, 5, 4, 8, 6, 7, 3
two, three, four, one, five

Fijaos que el método Shuffle() no devuelve nada, sino que modifica el array que le pasamos como argumento. Por tanto, si queremos mantener el array original, tendríamos que hacer una copia antes de llamar a Shuffle().

Nota extra: recientemente se ha valorado la introducción de un método Shuffle() en LINQ de .NET 10, aunque de momento parece que no ha salido adelante.

El método GetItems() permite obtener elementos aleatorios de una colección

Otra necesidad bastante frecuente es, dada una colección de elementos, obtener un número determinado de ellos de forma aleatoria.

Para ello, también en .NET 8 se introdujo el método GetItems<T>(). Este método recibe como argumento una colección de elementos de tipo T y el número de ellos que queremos obtener aleatoriamente, devolviendo un array del mismo tipo con los elementos seleccionados.

Internamente, lo único que hace es construir un array del tamaño del número de elementos solicitados y rellenar cada posición con un elemento aleatorio de la colección original. Ojo, porque esto implica que los elementos obtenidos podrían repetirse.

int[] numbers = [1, 2, 3, 4, 5];
var items = Random.Shared.GetItems(numbers, 5);
Console.WriteLine(string.Join(", ", items));
5, 3, 1, 3, 2

Podemos heredar de Random para crear generadores de números aleatorios personalizados

Por último, es interesante saber que la clase Random no está sellada, como ocurre con muchas otras clases del framework. Por tanto, aunque no es algo que vayamos a usar normalmente, es posible heredar de ella para crear generadores de números aleatorios personalizados.

Esto podría ser interesante para realizar tests unitarios o pruebas en las que queremos tener el control total sobre los números generados. Por ejemplo, el siguiente código muestra una clase MyRandom descendiente de Random donde hemos sobrescrito todos sus métodos para conseguir que devuelvan secuencialmente valores entre 0 y 4:

public class MyRandom: Random
{
    private int _value;

    public MyRandom(int value) => _value = value;

    private int GetNext() => _value++ % 5;
    public override int Next() => GetNext();
    public override int Next(int maxValue) => GetNext();
    public override int Next(int minValue, int maxValue) => GetNext();
    public override double NextDouble() => GetNext();
    protected override double Sample() => GetNext();
    public override long NextInt64() => GetNext();
    public override long NextInt64(long maxValue) => GetNext();
    public override long NextInt64(long minValue, long maxValue) => GetNext();
    public override float NextSingle() => GetNext();

    public override void NextBytes(byte[] buffer) 
        => Array.Fill(buffer, (byte)GetNext());
    public override void NextBytes(Span<byte> buffer) 
        => buffer.Fill((byte)GetNext());
}

Con esto, podríamos reemplazar la clase Random por nuestra implementación a la hora de invocar código que la utilice, por ejemplo, como lo que mostramos a continuación, invocando al método PrintRandom():

var rnd = new MyRandom(3);
for(int i=0; i < 5; i++)
{
    PrintRandom(rnd);
}

void PrintRandom(Random rnd)
{
    Console.WriteLine($"{rnd.Next()}");
}
3
4
0
1
2

¡Espero que os haya resultado interesante!

Publicado en Variable not found.

» Leer más, comentarios, etc...

Picando Código

Reemplazando Clementine - Strawberry

octubre 06, 2025 11:00

StrawberryStrawberry es un reproductor de música multi plataforma y organizador de colección musical. Es software libre publicado bajo la licencia GPL. Está enfocado a coleccionistas de música y audiófilos. En mi caso me considero coleccionista de música, así que me viene al pelo. Después de varios años donde dejé de usar Amarok para pasarme a Clementine, volví a cambiar.

El desarrollo de Clementine estuvo prácticamente abandonado por muchos años, aunque recientemente volvió a tener algo de actividad en su GitHub. Si bien había usado Strawberry antes, creo que la razón principal por la que me quedaba con Clementine era que podía ripear CD's a MP3. Clementine incluye una herramienta nativa para pasar CD's de música a ogg/mp3/flac. Pero ahora que descubrí que puedo ripear CD's directamente en KDE, no lo necesito más.

Clementine también seguía teniendo cada tanto el problema que lo ejecutaba y si bien "reproducía" la música, no sonaba a través de los parlantes. Hay que andar cambiándole cosas en el motor de playback, lo cual me resulta bastante tedioso en el año de nuestro Tux 2025. Por ahora no me ha pasado con Strawberry, espero que se mantenga así...

Strawberry es un fork de Clementine, así que resulta bastante familiar su uso. Soporta colecciones locales, radios y demás.

Navidrome/Subsonic

Algo que me gustó mucho es que tiene compatibilidad con la API Subsonic, que es la que usa Navidrome. Como comenté hace un tiempo, vengo usando Navidrome como servidor de streaming de música. Al ser compatible con Subsonic, en Strawberry podemos agregar nuestro servidor y las credenciales, y acceder a la biblioteca de Navidrome como si fuera un sistema de archivos y agregar música a la lista de reproducción. 

Un detalle a tener en cuenta para aquellos que scrobleamos nuestra música a servicios como Last.fm: Si bien Navidrome nos permite configurar last.fm, cuando agregamos la música a Strawberry, es como que se trae los tracks, no los reproduce en el servidor. Así que Navidrome no los va a scrobblear, y tenemos que configurar last.fm en Strawberry. Es bastante fácil, el menú de preferencias incluye una entrada para scrobbling que nos permite configurar last.fm, libre.fm y listenbrainz.

Para habilitar Subsonic, tenemos que ir a las preferencias, buscar el menu de "Subsonic" en "Streaming", habilitarlo y agregar la información y credenciales para nuestro servidor.

Strawberry reproduciendo música desde Navidrome

Strawberry reproduciendo música desde Navidrome

En la imagen se ve reproduciendo el disco Marrowbone Lane - 2025 remaster de la banda irlandesa CHEWIE. El disco Marrowbone Lane original salió en 2013 y es uno de mis preferidos. Lo tengo bastante memorizado, así que me resultó muy interesante escuchar el remaster y notar la diferencia en la mezcla. Me gustó mucho, se destacan particularmente algunos volúmenes que promueven más las harmonías en las voces, algunas voces adicionales y la parte final de la última canción donde el tema va creciendo hasta los 2:55  donde se siente más "grandioso" que en la versión original dándole un cierre más épico al disco. Es un remaster que no lo hace mejor (eso le quitaría métiro al disco original), es distinto, y eso está bueno. Las buenas canciones son disfrutables en cualquier mezcla (al menos buena).

¡Pero no estamos acá para hablar de música irlandesa! Sigamos con Strawberry...

Error Database schema too old

Al ejecutar Strawberry, me encontré con el siguiente error: Database schema too old.
El esquema de la base de datos de strawberry es demasiado antiguo. Había usado la aplicación en este sistema hace un buen tiempo, así que tiene sentido. No necesitaba mantener configuraciones ni nada, ya que la forma en que escucho música ha cambiado mucho. Así que busqué la base de datos para poder eliminarla:

$ find . -name strawberry
./.local/share/strawberry
./.local/share/strawberry/strawberry
./.config/strawberry

En este directorio estaba el archivo strawberry.db:

$ ls .local/share/strawberry/strawberry/
-rw-r--r-- 1 fernando fernando 928K Feb  7  2020 strawberry.db

Es de febrero de 2020, y eso fue hace como 17 años, así que es seguro borrarlo. Por las dudas, eliminé la base de datos y el archivo de configuración, porque de nuevo, no importaban después de tantos años:

rm .config/strawberry/strawberry.conf
rm .local/share/strawberry/strawberry/strawberry.db*

Y al ejecutar Strawberry de nuevo, todo anduvo bien de bien.

Conclusión

Está bueno esta ventaja del software libre de tener muchas opciones. Y a lo que pasa el tiempo hay proyectos que van perdiendo energía o los responsables pierden interés e invierten su tiempo libre en otras cosas. Por suerte gracias a forks y la comunidad, siempre hay algún otro proyecto en la vuelta que toma la posta.

Un detalle que no se me escapa es que a pesar de haber pasado por varios reproductores de música a través de los años, el esencia sigo usando uno de mis primeros reproductores preferidos, Amarok. Clementine es un fork de Amarok antes que revolucionara toda su interfaz y nos perdiera a algunos de sus usuarios, y Strawberry es un fork de Clementine. Así que sigo usando en esencia Amarok 1.4. He leído sobre Amarok que ya va en su versión 3.x. Las últimas veces que lo probé no me emocionó mucho, pero de repente vuelvo a probarlo en éstos días.

Ahora que me volví a familiarizar con Strawberry, quedó un último paso:

sudo apt-get remove clementine

Ya que estoy debería ver cómo anda Lollypop...

El post Reemplazando Clementine - Strawberry fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 622

octubre 06, 2025 06:05

C# muy afectado por el boxing

Aquí están los enlaces recopilados durante la semana pasada 😊

Entre los contenidos destacables, una pregunta: ¿somos conscientes del coste real de las operaciones de boxing y unboxing en .NET? Normalmente no las tenemos en cuenta, pero pueden llegar a ser importantes, y podemos aprender a detectarlas y evitarlas. Ali Hamza Ansari nos lo explica en su artículo.

También, Anton Martyniuk ha recopilado en este post una serie de paquetes NuGet que pueden mejorar tu productividad y la calidad de tus proyectos si usas Entity Framework Core.

Y Addy Osmani nos cuenta la historia, evolución y futuro de Core Web Vitals, los indicadores de rendimiento web que Google utiliza para medir la experiencia de usuario en las páginas web. Muy interesante.

El resto, a continuación...

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Publicado en Variable not found.

» Leer más, comentarios, etc...

Picando Código

¿Te gusta Huey Lewis and the News?

octubre 02, 2025 10:00

Huey Lewis and The News es una banda de rock de Estados Unidos, muy conocida por varias de sus canciones. Una de las más populares debe ser The Power of Love que suena en la película Back to the Future de 1985. Tuvieron bastante éxito, particularmente en Hollywood y la industria del cine. Pero es un poco injusto que mucha gente se quede simplemente con The Power of Love o I Want a New Drug por la demanda contra Ray Parker Jr. por "inspirar" la canción de Ghostbusters. La banda fue una máquina de hacer hits, y uno se podría estar perdiendo temazos como Do You Believe in Love, The Heart of Rock & Roll o los himnos de la clase trabajadora Couple Days Off y Workin' for a Livin'.

Hip to be Square

Una de las escenas más memorables del cine es la de American Psycho, en la que el psicópata protagonizado por Christian Bale entra en un discurso sobre la banda con la pregunta "¿Te gusta Huey Lewis and The News?":

"Do you like Huey Lewis & The News? Their early work was a little too 'new-wave' for my taste, but when 'Sports' came out in '83, I think they really came into their own – both commercially and artistically. The whole album has a clear, crisp sound, and a new sheen of consummate professionalism that really gives the songs a big boost. He's been compared to Elvis Costello, but I think Huey has a far more bitter, cynical sense of humor. In '87, Huey released this, 'Fore', their most accomplished album. I think their undisputed masterpiece is 'Hip to Be Square', a song so catchy most people probably don't listen to the lyrics – but they should! Because it's not just about the pleasures of conformity, and the importance of trends, it's also a personal statement about the band itself!"

Huey Lewis interpretó una parodia de esta escena junto a Weird Al Yankovic, quien a su vez parodió a Huey Lewis and The News con I want a new duck. Que esto sea una excusa para mirar la película o leer el libro, donde aparentemente también se menciona bastante a la banda. O en el peor de los casos al menos volver a escuchar la canción.

.com for murder

Lo que me inspiró a escribir este post viene por este lado. Además de un excelente músico, Huey Lewis ha tenido una carrera como actor. En el thriller psicológico de 2002 .com for murder, es uno de los protagonistas principales en el papel del agente especial Matheson. Esta semana estuve recordando la película casi todos los días por una escena en particular protagonizada por él:

Cuando las protagonistas se comunican con la policía para reportar un cyber crimen, les dan el número de teléfono 555-2211 para que hablen con este otro tipo, "¡está con el FBI!". El agente especial Matheson dice estas palabras tan sabias: "You know, sometimes I think fuck computers" (que se traduce algo así como Sabes, a veces pienso al carajo con las computadoras)

La semana pasada el sistema operativo de mi computadora del trabajo tenía esa notificación de que el sistema debía ser reiniciado después de una actualización. Ignoré la notificación hasta el viernes, cuando apagué la computadora. Cuando la volví a prender el lunes, estaba todo roto. Los drivers de hardware no andaban, no tenía WiFi, no funcionaba el touchpad, y no funcionaba el monitor conectado por HDMI porque seguramente los drivers de la placa de video estaban rotos también.

Sin entrar en muchos detalles porque me aburro sólo de pensarlo, me llevó bastante tiempo volver a un estado normal con esa computadora. En mi cabeza se repetía constantemente esta escena y lo que dice después:

Incluso hablando en el chat del trabajo sobre este problema, comenté que es el tipo de cosas que me recuerdan al "clásico" high-tech thriller psicológico .com for murder. y cité las palabras de Huey Lewis en estos dos videos (con asteriscos en las malas palabras, porque en el trabajo actúo como profesional™). Las computadoras fueron un error... ¿Realmente nos facilitan la vida? (escrito en una computadora)

Es una película que no es tremendamente buena, pero está entretenido verla como un producto de su época. Hay varios conceptos que se podrían considerar bastante adelantados como el tema de la seguridad/privacidad online, smart homes y la amenaza de incels que pasan demasiado tiempo en la computadora en vez de ir a terapia. El villano en la época se consideraría exagerado, pero es como me imagino a varios C-levels de compañías de tecnología hoy en día.

También están los típicos efectos de lo que entiende como "hacking" en Hollywood, con visualizaciones super gráficas de las interacciones con la computadora y código que cae tipo catarata como si el código fluyera del cerebro al teclado constantemente.

Arrow Video tiene unas ediciones muy lindas y muy completas de películas, con contenido extra interesante. Es uno de los blu-ray que tengo en la colección. Es el tipo de película que seguramente miraríamos un fin de semana aburrido si la pasaran en el cable en su momento. Recomiendo mirarla para ver a Huey Lewis explicando qué es la encriptación y un poco de nostalgia de los chats y lo que era la computación hace unos 20 y pico de años. También recomiendo darle una repasada a la discografía de Huey Lewis and The News.

Y para seguir revolcándose en la nostalgia y este sentimiento de seguir envejeciendo y aferrarse al pasado y la juventud, pero en un video que seguramente apele a la audiencia de este blog, Huey Lewis and The News - While We're Young:

YouTube Video

El post ¿Te gusta Huey Lewis and the News? fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 621

septiembre 29, 2025 06:05

Usuario poniendo a dormir su PC en una pequeña cama

Ya he publicado en mi blog la colección de enlaces a contenidos interesantes de la semana pasada 🙂

Para destacar, Milan Jovanović nos habla sobre el problema que surge al intentar coordinar tareas que se ejecutan en distintos procesos o instancias, algo que escapa al alcance de los mecanismos de sincronización tradicionales como mutex, locks o semáforos de .NET, y algunas fórmulas para resolverlo.

Andrew Lock comparte los detalles del desarrollo de 'sleep-pc', una pequeña herramienta de línea de comandos que permite poner el PC a dormir después de un tiempo determinado. Usa .NET Native AOT y es instalable directamente desde NuGet.

Y por último, David Grace nos explica los distintos atributos que xUnit pone a nuestra disposición para proporcionar datos a los tests, y las diferencias entre ellos.

El resto, a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Variable not found

Atributos .NET para comprobaciones estáticas de valores nulos (NotNullWhen, NotNullIfNotNull y MemberNotNull)

septiembre 26, 2025 06:55

Inspector examinando con lupa un código C#

Hace algún tiempo hablamos sobre el atributo [MemberNotNullWhen] y vimos cómo se podía usar para mejorar la seguridad frente a valores nulos en nuestras aplicaciones.

Como recordaréis, este atributo lo aplicábamos sobre miembros booleanos para asegurar que otro miembro de su misma clase no contendrá null, dependiendo del valor (true o false) del primero. El ejemplo que lo ilustraba era el siguiente:

public class Result<T>
{
    [MemberNotNullWhen(true, nameof(Value))]  // Si Success es true, Value no es null
    [MemberNotNullWhen(false, nameof(Error))] // Si Success es false, Error no es null
    public bool Success { get; init; }

    public string? Error { get; init; }
    public T? Value { get; init; }

    // Constructores, factorías, etc
}

Pero también comentamos que existían muchos otros atributos que el compilador empleaba para afinar en la detección de posibles problemas similares en el código. Al usarlos, el compilador comprende mejor las intenciones de nuestro código respecto a la utilización de nulos, y puede generar advertencias que impiden posteriores errores en tiempo de ejecución, pero sólo cuando son estrictamente necesarias.

En este nuevo post, vamos a echar un vistazo a los siguientes:

  • [NotNullWhen]
  • [NotNullIfNotNull]
  • [MemberNotNull]

[NotNullWhen]: el parámetro out no será nulo cuando el método devuelva un valor específico

Este atributo se aplica a parámetros de salida (out) de un método booleano, y se utiliza para indicar que el valor del parámetro no será nulo dependiendo del valor de retorno del método.

Un ejemplo típico lo encontramos con métodos como TryXXX(), que devuelven un valor booleano que indica si la operación fue exitosa o no, y disponen de un parámetro out que contiene el resultado de la operación. En este caso, el atributo se aplicaría al parámetro out para indicar que su valor no será nulo si el método devuelve true.

Veamos un ejemplo sencillo. El siguiente método TryGetFriend() intenta obtener un amigo a partir de su identificador. Si el amigo existe, devuelve true y el parámetro out contendrá el objeto Friend. Si no existe, devuelve false y el parámetro out será nulo.

bool TryGetFriend(int id, out Friend? value)
{
    if (id == 1)
    {
        value = new Friend("John", 30);
        return true;
    }

    value = null;
    return false;
}

record Friend(string Name, int Age);

Ahora, si intentamos usar el método TryGetFriend() de la siguiente manera, veremos que el compilador nos advierte de que el valor de value puede ser nulo, y nos sugiere usar una declaración if para comprobarlo, o un acceso condicional (?.) para evitar un NullReferenceException:

if (TryGetFriend(1, out var friend))
{
    Console.WriteLine(friend.Name); // CS8602: Dereference of a possibly null reference
}

Es obvio; sin darle más pistas, el compilador no puede saber si friend contendrá un valor nulo en ese punto, porque desconoce que el hecho de que la llamada al método retorne true implica que el valor de friend no será nulo.

Sin embargo, si aplicamos el atributo [NotNullWhen(true)] al parámetro out, el compilador ya tendrá la certeza de que el valor de friend no será nulo si la llamada al método devuelve true, y no generará la advertencia:

bool TryGetFriend(int id, [NotNullWhen(true)] out Friend? value)
{
    ...
}

[NotNullIfNotNull]: el método no devolverá un valor nulo si el parámetro indicado no lo es

Este atributo se aplica sobre el retorno de un método, indicando que éste no será nulo cuando el parámetro cuyo nombre especificamos tampoco lo sea. Por ejemplo, observad el siguiente método ToUpper() que convierte una cadena a mayúsculas:

string? ToUpper(string? inputString) 
{
    return inputString?.ToUpper();
}

El método devolverá un valor nulo si se le envía un nulo, pero retornará una cadena válida en caso contrario. Sin embargo, por su firma, el compilador no puede determinar la relación entre la nulabilidad del parámetro entrante y el valor de retorno. Por lo tanto, si usamos el método de la siguiente manera obtendremos la advertencia de posible acceso a una referencia nula:

var str = ToUpper("Hello world");
Console.WriteLine(str.Length); // CS8602: Dereference of a possibly null reference

Apliquemos el atributo [NotNullIfNotNull] al retorno del método de la siguiente manera:

[return:NotNullIfNotNull(nameof(inputString))]
string? ToUpper2(string? inputString) 
{
    return inputString?.ToUpper();
}

Hecho ese cambio, el compilador ya sabrá que el valor de retorno no será nulo si el parámetro tampoco lo es, y generará la advertencia sólo cuando sea estrictamente necesaria:

var str1 = ToUpper("Hello world");
Console.WriteLine(str1.Length); // Ok, sin advertencias

var str2 = ToUpper(null);
Console.WriteLine(str2.Length); // CS8602: Dereference of a possibly null reference

[MemberNotNull]: el miembro no será nulo después de ejecutar un código

Con este atributo podemos indicar los miembros de una clase que no serán nulos después de invocar un método o acceder a una propiedad.

Observad el siguiente ejemplo. La clase Friend tiene una propiedad Name que puede ser nula, y un método Initialize() que la inicializa. Sin embargo, el compilador no puede saber que después de llamar a Initialize(), la propiedad Name no será nula.

Por tanto, ante la duda, el compilador nos advierte de que la propiedad Name podría ser nula:

var f = new Friend();
Console.WriteLine(f.Name.Length); // CS8602: Dereference of a possibly null reference

public class Friend
{
    public string? Name { get; set; }

    public void Initialize()
    {
        Name = "Anonymous";
    }
}

Para indicarle al compilador que la propiedad Name no será nula después de ejecutar el método Initialize(), aplicamos el atributo [MemberNotNull] a la propiedad:

public class Friend
{
    public string? Name { get; set; }

    [MemberNotNull(nameof(Name))]
    public void Initialize()
    {
        Name = "Anonymous";
    }
}

De esta forma, tras la llamada a Initialize(), el compilador no generará la advertencia:

var f = new Friend();
// Todavía no podemos asegurar que Name no sea nulo:
Console.WriteLine(f.Name.Length); // CS8602: Dereference of a possibly null reference

f.Initialize();
Console.WriteLine(f.Name.Length); // Ok, porque seguro que Name no es nulo

Por último, es importante destacar que el atributo [MemberNotNull] no se puede utilizar en constructores, y permite especificar varios miembros separados por comas, como en el siguiente ejemplo:

[MemberNotNull(nameof(Name), nameof(Address))]

Publicado en Variable not found.

» Leer más, comentarios, etc...

Juanjo Navarro

LinkBridge, gestor de enlaces

septiembre 25, 2025 05:46

¡He creado una extensión para navegador!

Este verano, aprovechando las vacaciones, he creado una extensión para navegador que puedes utilizar si te apetece.

Se llama LinkBridge y es un gestor de enlaces para que puedas acceder fácilmente a las webs que utilizas más frecuentemente.

Te dejo un vídeo de su uso y los enlaces de descarga y después te cuento algo más sobre ella.

Algunos otros navegadores pueden usar las extensiones de Chrome o Firefox.

De dónde viene la idea

Hace tiempo que venía usando como gestor de enlaces frecuentes en el navegador el software Flame.

Software de este tipo hay mucho, algunos muy populares, pero a mi me gusta Flame por su estilo minimalista tanto en lo visual como en lo funcional.

El problema de Flame es que es una aplicación que tienes que instalar en tu propio servidor, lo cual obviamente no está al alcance de muchas personas.

Pensé que estas funciones (especialmente las que yo usaba, que no son todas) se podrían cubrir perfectamente desde una extensión del navegador que no requiriese montar nada en nuestro propio servidor.

Funciones de la extensión

Las funciones que he implementado son las principales (y las que yo uso) del software Flame:

  • Tus enlaces aparecen cada vez que abres una nueva pestaña del navegador
  • Puedes agrupar los enlaces en grupos temáticos con dos niveles: Aplicaciones (de mayor tamaño) y Bookmarks.
  • Soporte de temas, tanto claros como oscuros
  • Exportación e importación de los enlaces

Acceso al código fuente

El código fuente (por si te interesan estas cosas) está disponible en GitHub: LinkBridge en GitHub

¡Espero que te guste y te sirva si te animas a probarla!

» Leer más, comentarios, etc...

Picando Código

Nuevas actualizaciones en Gaucho and the Grasslands

septiembre 22, 2025 11:30

Gaucho and the Grasslands ha recibido unas cuantas actualizaciones desde que lo recomendé en julio. El juego es una aventura mística con simulación de granja inspirado en series como The Legend of Zelda. Cuenta con una ambientación latinoamericana en las pampas de Rio Grande do Sul.

El pasado fin de semana se publicó una actualización especial en celebración del mes de Farroupilha. Esta cuenta con mecánicas nuevas, más personalizaciones y ajustes para mejorar la experiencia en general.

Fue una buena excusa para volver al juego. Lo primero que noté es que la parte gráfica y de rendimiento mejoraron muchísimo desde la última vez. El juego se siente mucho más fluido y los gráficos se ven mejor.

En agosto recibió una actualización en particular que lo refina y optimiza para Steam Deck: "La experiencia es ahora más estable e inmersiva, lista para ser disfrutada en cualquier lado". Como Steam Deck corre con SteamOS, que está basado en Linux, de repente esto se traduce en mejoras para todos los sistemas Linux.

Gaucho and the Grasslands - altar de transporte

Pero esta última actualización de setiembre trae un modo nuevo que recuerda a The Legend Of Zelda: Ocarina of Time. En el juego podemos obtener un acordeón y aprender a tocar distintas canciones. Esto nos permite transportarnos a distintas regiones del mapa, una vez que encontramos y activamos el altar.

Se agregaron 5 canciones nuevas un mini juego con el acordeón en el boliche. Un personaje nuevo, el Patrão, entrega recompensas según nuestra actuación en vivo.

La mecánica para reproducir las canciones, es similar a lo que tenemos que hacer con Link y su Ocarina o la batuta en The Wind Waker.

Gaucho and the Grasslands - Acordeón

La celebración también incluye skins nuevos para los personajes, inspirados en figuras de la cultura gaucha, así como colores de equipos de fútbol icónicos de Rio Grande do Sul. Como si todo esto fuera poco, incluye también refinamientos en la economía, mejoras a la organización de trueque con otros personajes y mejoras a la pantalla de personalización.

Me pareció una buena oportunidad para volver a recomendar esta aventura mística latinoamericana. Gaucho and the Grasslands está disponible en Steam, y al momento de publicar este post, tiene un descuento del 20% sobre el precio final.

El post Nuevas actualizaciones en Gaucho and the Grasslands fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Variable not found

Enlaces interesantes 620

septiembre 22, 2025 06:05

Un agente robótico comprando por internet en nombre de un usuario

Vamos con una nueva colección de enlaces a contenidos de interés que he ido recopilando durante la última semana.

Destacamos, en primer lugar, el anuncio de que las versiones STS (Soporte a Largo Plazo) de .NET pasarán a tener 24 meses de soporte en lugar de los 18 actuales, comenzando ya con .NET 9. Una buena noticia para todos aquellos que preferimos estabilidad y no estar actualizando constantemente.

Interesante el post de Ali Hamza Ansari sobre el uso de "async void" en C# y por qué es peligroso, aunque también explica cuándo es aceptable usarlo.

A tener en cuenta asimismo el anuncio de la especificación final WebAssembly 3.0, que trae varios cambios importantes, como el direccionamiento de 64 bits, mejoras en gestión de la memoria y rendimiento, manejo de excepciones y otras.

Y Google ha anunciado Agent Payments Protocol (AP2), un protocolo abierto para facilitar el pago a través de agentes de IA de forme segura y verificable, buscando estandarizar el comercio automatizado 😱 

En resto, a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Machine learning / IA

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

.NET MAUI

Otros

Publicado en Variable not found.

» Leer más, comentarios, etc...

Blog Bitix

Novedades y nuevas características de Java 25

septiembre 19, 2025 07:00

Habitualmente publicaba un artículo por cada versión de Java, pero teniendo en cuenta que para producción es recomendable usar preferentemente las versiones LTS estoy prefiriendo publicar uno en cada versión LTS con el acumulado de características de la versión anterior. En este caso de Java 25 como nueva LTS que sucede a Java 21.

Continuar leyendo en Blog Bitix

» Leer más, comentarios, etc...

Picando Código

Nada que ver en las audaces aventuras de desarrollar un proyecto de código abierto popular

septiembre 18, 2025 07:50

Vuelve un poco de tranquilidad a la parte de mi que se estresa cuando tiene asuntos pendientes. Patchstack marcó como corregida la vulnerabilidad en List Category Posts en la versión 0.92.0 del plugin 🥳

Wordfence todavía no actualizó su sitio y sigue teniendo mensajes alarmantes para las pobres personas que decidan instalar este plugin. Pero allá ellos (Actualizado unos días después: ya marcan la vulnerabilidad como parcheada en 0.92.0).

Desde hace un buen tiempo nos han venido reportando "vulnerabilidades" en el código. Pero siempre con un detalle: un usuario malicioso tiene que tener acceso al escritorio de WordPress para poder editar las entradas. O sea, si tu sistema está comprometido, se puede hacer daño usando el plugin. Pero obviamente se puede hacer mucho más daño por otro lado.

Supongo que esta gente le paga bounties a quienes encuentran fallos de seguridad en software de uso amplio. Y debe haber algún tipo de negocio por detrás, de repente de asustar a la gente para que "necesite" de los servicios de seguridad que otros venden. No sé, ¡pero qué molesto!

Nada que ver acá, por favor dispérsense

Nada que ver acá, por favor dispérsense

Sigo pensando que el "fallo" en nuestro plugin nunca debió haber hecho el ruido que hizo. Ayer alguien publicó en el foro de WordPress que Wordfence le asustó muerto con la advertencia. ¡Un poco exagerado! Igual no sé qué tan alarmante sea la advertencia de Wordfence, pero de nuevo, la falla sólo podía explotarse en un sistema ya comprometido. Nunca fue un problema crítico. Es como decir que WordPress es "vulnerable" porque si alguien aprende las credenciales de un usuario administrador por ingeniería social, podría inyectar código malicoso en el sistema.

Espero podamos dejar atrás este tema. Debí haberle dedicado tiempo a corregirlo antes, simplemente para evitar tanto usuario alarmado. Siempre les contestamos con paciencia y explicando que el riesgo era prácticamente nulo. Pero tuvimos que enfrentar a unos pocos que necesitaban un sopapo a mano abierta atrás de la oreja. Actualizar el código significó volver a usar Vagrant y escribir un poco de PHP. Por suerte no tuve que tocar Subversion porque tenemos una automatización que publica las versiones nuevas al crear un tag nuevo en git. Sirvió para no perder la práctica con todo esto...

En fin, ojalá ya para próximas versiones de plugins agreguemos cosas nuevas y arreglemos bugs y no tenga que andar escribiendo más sobre CVEs que son un dolor de gónadas.

El post Nada que ver en las audaces aventuras de desarrollar un proyecto de código abierto popular fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Picando Código

Recomendando un par de blogs en español

septiembre 17, 2025 07:00

Quiero compartir un par de entradas de blogs que leí y me parecieron interesantes. Sigo en mi misión de leer más blogs en español y agregarlos a mi lector RSS para mantenerme un poco más conectado al tipo de contenidos en internet que me llaman más. A modo de continuación de lo que empecé con aquella entrevista que me hizo tecnolocuras, a ver si me voy generando más comunidad virtual. Ya de paso recomiendo visitar tecnolocuras.

Blogs Transformers Emacs

En primer lugar recomiendo Quijote Libre:

Mis notas para aprender, organizarme y seguir creciendo. Hechas con cariño y trabajo, en texto plano, con herramientas libres como Arch Linux y Emacs, y compartidas en agradecimiento a la comunidad de creadores que me han enseñado.

Llegué a la entrada Emacs es un ser vivo, no una reliquia por Mastodon. Creo que la cuenta Hispa-Emacs lo compartió, y me gustó la entrada. Si bien el blog tiene poco contenido por el momento, quedó agregado a mi RSS y seguiré leyendo. También se puede seguir al autor por el fediverso en @quijote_libre@mastodon.social.

El segundo blog que quiero recomendar hoy, también llegué gracias a Mastodon, el blog de Maik Civeira, profe, escritor y bloguero, nerd profesional, friki antifascista:

Hola, les doy la bienvenida a mi blog de ensayo y divulgación del conocimiento y el pensamiento crítico. Aquí hablamos de cosas frikis y cultura general, pero también de política, ciencia y filosofía. Pásenle a lo barrido.

Me llamó la atención el título de este post, y la lectura resultó entretenida también. Se trata de un estudio marxista de la película Transformers: One - Transformers: ¿Una película marxista?
El blog tiene un montón de entradas interesantes, está en línea como Picando Código también desde 2007. Se le puede encontrar por el fediverso en @MaikCiveira@mastodon.social.

La mierdificación de las grandes redes sociales ha venido bien para alguna gente, me vengo encontrando cosas muy buenas por el fediverso. Un ambiente más sano y agradable. Si tienen cuentas del fediverso o blogs para recomendar, bienvenidas sean. Que siga creciendo mi lista de feeds RSS ¡Nos encontramos por el ciberespacio!

El post Recomendando un par de blogs en español fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Picando Código

Publicado List Category Posts v0.92.0 con mejoras en "seguridad"

septiembre 16, 2025 08:18

List Category PostsDesde mayo venimos luchando con Wordfence y Patchstak, que reportaron un problema de seguridad en el plugin de WordPress List Category Posts. Con Klemens, el otro "mantenedor" que ha venido aportando mucho al código del plugin, venimos conversando del tema por correo electrónico.

El "fallo de seguridad" o la "vulnerabilidad" que reportan es que un usuario con acceso al sistema de archivos y permisos de al menos editar un post o página, puede incluir PHP malicioso.

Ahora, un usuario que tiene acceso al sistema de archivos y a editar contenido, puede hacer mucho más daño por otros lados que con este plugin. El problema principal es que hay un usuario con malas intenciones que adquirió estos accesos, no que el plugin tiene una falla de seguridad y permite hacer cosas malas.

Incluso en el reporte de Patchstack (que me imagino es de donde levanta Wordfence la información), dice: 

This security issue has a low severity impact and is unlikely to be exploited. (Este problema de seguridad tiene un impacto de baja gravedad y es poco probable que sea explotado).

La "falla" es una característica del plugin. Cuenta con un sistema de plantillas php con código relevante al plugin. Esto permite formatear con mucho más detalle la forma en que se van a mostrar los posts listados por el plugin. El tema es que necesita incluir archivos php, cosa que sean portables, reutilizables y porque es una característica para gente que sepa php. Y si se incluye un archivo con código malicioso, puede hacer daño al sistema. De nuevo: hay que tener acceso al sistema de archivos del servidor para subir/escribir el código malicioso.

La mayoría de los usuarios de WordPress son gente no-técnica. Así que es entendible que se alarmen al ver una advertencia de seguridad del plugin en sus Escritorios de WordPress. Lo que veo como un poco de falta de responsabilidad es de Wordfence y demás sistemas. Informan de forma bastante exagerada y alarmante que este plugin tiene una falla de seguridad. La mayoría de la gente no se va a tomar el tiempo de leer de qué se trata. Va a ver la advertencia y se va a preocupar.

Lo primero sería aclarar que es de riesgo bajo, que no es algo que un usuario externo al blog pueda "explotar" y que se necesita tener un usuario con privilegios comprometido en el sistema.

Desde que pasó todo esto, hemos tenido que lidiar con usuarios de todos lados reclamando y escribiéndonos en el foro de WordPress, GitHub y por correo electrónico. Una persona estuvo muy cerca de agotar mi paciencia y hacer que respondiera con algo casi ofensivo:

"¿Pueden por favor arreglarlo para que no tengamos que seguir intentando responder a las preguntas de nuestros clientes?"

Respondí que el plugin ha sido escrito como un esfuerzo voluntario en el espítiru del software libre. Quería decir "no respondemos a tus clientes", pero fui más diplomático con "entiendo que otros han hecho sus negocios en base a este software libre (podría haber agregado sin aportar nada a cambio pero 🤐) pero no es un negocio para nosotros". Ya bastante se ha escrito de las expectativas de gente que explota el software libre sin entender su ecosistema, pero acá un ejemplo muy claro. No es igual en todos los casos, pero para nosotros este plugin es un esfuerzo voluntario que no tiene un negocio por atrás para sustentarlo.

Agregué que obviamente cualquiera es libre de dejar de usar el plugin si no está feliz con las explicaciones respecto a la "vulnerabilidad".

Otra persona escribió que "había pasado el código por un sistema de inteligencia artificial y parece haber varios problemas de seguridad severos". Tuve que borrar el comentario porque no aportaba nada a la discusión, pero además no pude responderlo sin entrar en descalificaciones personales. Su comentario era el equivalente a "sacrifiqué una cabra al dios de la guerra y sus entrañas me decían que en el código parece haber varios problemas de seguridad". Estas herramientas que vienen vendiendo como "inteligencia artificial" estos tecnofascistas encocainados no están generando mucha inteligencia en la población...

En GitHub alguien anunció que no estaba conforme con las explicaciones y que iba a buscar una alternativa. Estuve a punto de responder simplemente con este gif:

I'm leaving. Okay then, that was always allowed

Pero éstas son las cosas que sólo pensamos, no actuamos sobre ellas. Y después las posteamos en nuestros blogs personales para que no queden frustradas haciendo mal en nuestra cabeza 😄

Espero que con esta nueva versión al menos nos digan que está resuelto el tema. Sino, Klemens tiene otra idea para agregar al código que potencialmente resolvería el problema si es que no están conformes los expertos en seguridad con este último arreglo. Veremos qué pasa. Mientras tanto, seguimos experimentando las audaces aventuras de desarrollar un proyecto de código abierto popular. 

List Category Posts es un plugin para WordPress, es software libre publicado bajo la GPLv2. El código fuente está disponible en Codeberg, en GitHub y en WordPress.org (SVN). Se puede descargar desde el sitio de plugins de WordPress.

El post Publicado List Category Posts v0.92.0 con mejoras en "seguridad" fue publicado originalmente en Picando Código.

» Leer más, comentarios, etc...

Header Files

Mejorando los tiempos de compilación

agosto 28, 2025 09:00

Introducción

Como todos los lectores sabrán, C++ es un lenguaje compilado, lo que implica que su código fuente se transforma en un archivo binario ejecutable. No es necesario que el usuario final realice este proceso, lo hace el desarrollador. Generalmente es un proceso bastante rápido, pero a medida que el proyecto comienza a crecer también lo hace el tiempo requerido para su compilación. No es extraño encontrar proyectos de mediano tamaño que tardan 15-20 minutos en compilarse, y personalmente he trabajado con algunos que requerían unas 13 horas partiendo de cero.

Una de las primeras formas de reducir el tiempo de compilación usadas fue la de evitar compilar cosas que no fuesen necesarias: si un fichero ni ninguna de sus dependencias ni parámetros han sido modificados, una nueva compilación devolverá el mismo fichero objeto, por lo que una comparación de los timestamps entre ficheros fuente y objeto ayudará a centrarnos en aquellos que sí requieren ser procesados nuevamente. Este mecanismo es estándar en cualquier sistema de compilación, bien sean Makefiles, proyectos de MSVC, Xcode, etc.

Aún así, esto no suele ser suficiente, siendo frecuente tener que esperar entre 30 segundos y 5 minutos hasta que la compilación termine. Si sumamos el hecho de que tenemos que compilar multitud de veces cada día, en total supone un montón de tiempo dedicado a ver pasar líneas frente a nuestros ojos. Un día normal para mí conlleva entre 30 y 50 compilaciones, con una duración media de 30s, lo que nos da entre 15 y 25 minutos por jornada.

Créditos: https://xkcd.com/303/

Casi todos aprovechamos estos micro-descansos durante las compilaciones para hacer algo en paralelo. Dos minutos apenas da tiempo para ir al baño, en 4 podemos hacernos un café (contando el tiempo de ir a la cocina, calentar la leche, etc.). Podemos aprovechar para responder un email o mirar el estado del equipo, etc. Pero, salvo contadas ocasiones, la experiencia dicta que es mejor no hacer nada, no sólo porque lo que creíamos que era cosa de 1 minuto se extienda, sino porque además hacer muchas cosas al mismo tiempo significa perder el enfoque: el cambio de contexto penaliza la tarea principal. Lo mejor que podemos hacer es tratar de minimizar ese tiempo muerto (obviamente estoy asumiendo que compilamos cuando lo necesitamos y no como tic nervioso).

En este artículo comentaremos algunas estrategias de optimización (todas centradas en reducir la cantidad de código a compilar, ya veremos por qué), cómo aplicarlas eficientemente, y aspectos a tener en cuenta para que no nos salga el tiro por la culata.

Poniendo a dieta al compilador

El proceso de compilación de proyectos en C++ se lleva a cabo, normalmente, en tres etapas: preprocesado, compilación y enlazado. Simplificando mucho, diremos que en la primera el fichero de código fuente es leído y convertido en un fichero de código fuente intermedio completamente autocontenido (es decir, que toda la información necesaria para compilarse está presente en dicho fichero). En la etapa de compilación, cada uno de estos ficheros autocontenidos es analizado y las sentencias C++ son transformadas en código binario intermedio, aunque aún no es ejecutable. Es en la tercera etapa donde todas las unidades de compilación son enlazadas entre sí junto a las dependencias externas, resultando en el fichero ejecutable.

Primero que nada, es importante mencionar que el preprocesado no hace ningún tipo de análisis de código; más bien podríamos verlo como una serie de operaciones de edición de texto. Por ejemplo, las macros son operaciones de sustitución de bloques de código (buscar/reemplazar), la inclusión de ficheros de cabecera es como un copiar/pegar, y la compilación condicional es como eliminación de código. Es durante la compilación donde se determina si el fichero tiene una sintaxis C++ válida y se identifica qué partes de todo el código compilado son realmente necesarias.

Como podemos intuir, la compilación más rápida será aquella que sólo compile lo que realmente estamos necesitando.

Análisis de la compilación

Es posible que muchos managers no entiendan el beneficio de reducir en 3 segundos el proceso de compilación. La forma más sencilla de justificarlo es, claramente, desde el punto de vista económico: 3 segundos por 30 veces diarias por 20 días al mes es media hora que gano al mes para el proyecto por programador. Lo mismo aplica para las pipelines de CI/CD.

De todas formas, si para conseguir esos 3 segundos hemos necesitado 3 días de trabajo, no hemos mejorado el proceso, ya que el ROI (Return on Investment) será muy bajo. Pero si por el contrario lo hemos hecho en una hora, habrá merecido la pena.

Se ve claramente que, como en toda optimización, lo primero que tenemos que hacer es averiguar dónde necesitamos optimizar, para atacar los problemas que nos den el mayor salto posible. Y es que casi siempre los cuellos de botella están centrados en unos pocos lugares.

Centraré la sección de profiling en clang, aunque el modus operandi en general es análogo en otros compiladores. Lo primero será activar la opción -ftime-trace, con la cual obtendremos un análisis de la compilación con el tiempo dedicado a cada etapa de la compilación y fichero (en MSVC sería /timetrace). El fichero generado se ubicará junto a los ficheros objeto intermedios, con el mismo nombre de la unidad de compilación pero extensión JSON. Podemos abrirlo con la herramienta de tracing incluida en cualquier navegador Chromium (por ejemplo, en Edge es edge://tracing/), o con otras como Perfetto (https://ui.perfetto.dev/).

Identificando objetivos

Hay básicamente dos formas que uso para detectar los cuellos de botella. La primera sería analizar los ficheros JSON manualmente, y lo que suelo hacer en estos casos es ordenarlos por tamaño y centrarme en los más grandes; si bien no siguen un orden estricto, en general un fichero JSON grande significa que el timetrace recolectó muchos datos sobre el mismo. La segunda ya es más elaborada, y pasa por un script que lee los JSON y básicamente ensambla estadísticas globales del proyecto, incluyendo ficheros de mayor tiempo (medio), número de inclusiones y tiempo total.

Personalmente uso una combinación de ambas: con el script obtengo una clasificación (el podio), los ficheros que más impactan la compilación. Luego los analizo manualmente, abriendo cualquiera de los JSON referenciados para su análisis.

La siguiente imagen muestra las trazas para la compilación de un fichero .cpp modesto, de 2K líneas y 68KB de peso (es un ejemplo real, por lo que he ocultado algunos datos). Los resultados se muestran como un flame graph: el eje horizontal es el tiempo desde el inicio del procesado del fichero, donde cada bloque es una fase de la compilación y su ancho es el tiempo invertido en dicha fase; el eje vertical es el call stack.

Vista general de un timetrace

En este artículo nos centraremos en los bloques verdes, que representan la lectura, preprocesado y parseo (análisis sintáctico). Los bloques más grandes son nuestros principales cuellos de botella: ficheros que tardan mucho en ser analizados. Estos ficheros son los que tenemos que optimizar.

Ahora bien, debemos saber si, dado un bloque grande, el fichero asociado es pesado en sí mismo o porque otros (ficheros incluidos por el primero) lo engordan. Podemos obtener esta información en dos formas (complementarias entre sí): la primera es observar el eje vertical y el impacto de las dependencias (véase el bloque central: el primer fichero consume mucho, pero es obvio que es culpa de dos dependencias). La otra es observando los detalles de cada bloque (basta con seleccionar el bloque y ver el pánel inferior):

Detalle de un timetrace

Acá podemos ver que si bien el procesado del fichero toma 325ms (wall duration), el fichero como tal solo toma 2ms (self time).

Esta combinación de acciones permite centrar los esfuerzos en los ficheros más importantes, si no podría pasar que el análisis individual sin ningún tipo de guía nos lleve a optimizar ficheros con un impacto muy bajo en la compilación final del proyecto.

Estrategias a seguir

A continuación comentaremos algunas de las acciones que podemos llevar a cabo para mejorar los tiempos de compilación, aunque podrían resumirse en quitar lo que no hace falta. El orden de exposición corresponde al que uso personalmente, siguiendo como criterio el tiempo que lleva aplicarlas.

  • Incluir únicamente los ficheros de cabecera necesarios. Es normal que, a lo largo de la historia del proyecto, el código haya cambiado mucho, por lo que es posible que haya ficheros de cabecera innecesarios, o que estemos incluyendo un súper fichero que contiene muchas cosas cuando sólo necesitamos una. Cualquier limpieza viene bien, y más aún si es de alguno de nuestros objetivos. Esta estrategia puede no salir siempre bien en el caso de que otro fichero de cabecera del mismo documento esté incluyendo al que hemos borrado, con lo que no estamos ganando nada. Esta estrategia paga mejor cuando se aplica a limpiar los propios ficheros de cabecera de includes innecesarios, ya que el impacto se multiplica. Truco: algunos IDEs y analizadores estáticos proveen esta información de forma directa.
  • Utilizar forward-declarations. Si nuestro fichero de cabecera sólo hace uso de una clase como una referencia o puntero (es decir, no necesita saber detalles), podríamos eliminar el fichero de cabecera que la declara y sustituirla por un forward declaration.
  • Separar ficheros con múltiples declaraciones en ficheros individuales. Existe una regla de oro (aunque algo flexible) que dice que cada declaración debe ir en su propio fichero. Esto permite que incluyamos únicamente lo que necesitamos.
  • Extraer declaraciones anidadas. Es una especie de corolario de la estrategia anterior: si una clase define una clase o enumeración dentro de la misma, cualquier referencia a los segundos obligará a incluir a la primera por completo.
  • Utilizar el patrón Pimpl. No entraré en detalles, pero este patrón permite separar mejor la declaración de la implementación. Así no sólo evitamos incluir ficheros de cabecera que sólo son necesarios en la implementación, sino que además cualquier modificación en la misma no impacta a los ficheros que usan esta clase.
  • Refactorizar las clases grandes en clases más pequeñas y con menos responsabilidades. De nuevo, una forma de incluir sólo lo que se necesita, pero que además mejora enormemente el diseño reduciendo el acoplamiento entre módulos.

Pasándonos de la raya

Consideremos ahora algunas posibles desventajas de las estrategias mencionadas:

  • Un forward-declaration es un segundo lugar al que tenemos que prestar atención si hacemos algún cambio a un tipo de dato (especialmente su nombre) aunque normalmente esto implica únicamente un fallo de compilación.
  • Tener decenas o cientos de mini-ficheros para definir enums puede ser tedioso de mantener; en algunas ocasiones bastará con tener unos pocos ficheros de “tipos” básicos, agrupados por componente o función.
  • El uso del patrón Pimpl implica en la práctica una desreferencia de memoria adicional. En los sistemas modernos esto no suele ser un problema, pero convendría tenerlo presente si lo usamos en secciones de código donde el rendimiento es crítico (y el profiler ya nos ha dicho que el pimpl es el problema; no optimicemos prematuramente, primero el diseño).

Conclusión

Optimizar los tiempos de compilación no es sólo una cuestión de comodidad para el desarrollador, sino una inversión que mejora la productividad del equipo y reduce los costes del proyecto. Las estrategias presentadas en esta primera parte se centran en el principio fundamental de “compilar únicamente lo necesario”, atacando el problema en su origen: la cantidad de código que debe procesar el compilador.

La clave del éxito radica en medir antes de optimizar. Herramientas como -ftime-trace nos permiten identificar los verdaderos cuellos de botella y centrar nuestros esfuerzos donde realmente importa. No todos los ficheros tienen el mismo impacto, y una optimización bien dirigida puede resultar en mejoras significativas con un esfuerzo mínimo.

Recordemos que estas optimizaciones deben aplicarse con criterio: aunque las estrategias mencionadas son generalmente beneficiosas, siempre conviene evaluar el coste de mantenimiento frente a la ganancia obtenida.

El tiempo de compilación perdido nunca se recupera, pero el tiempo invertido en optimizarlo se amortiza cada día.

» Leer más, comentarios, etc...

Header Files

5 cosas que puedes hacer al migrar a C++ moderno

agosto 14, 2025 06:00

Si bien C++11 y el resto de versiones del bien llamado C++ moderno llevan ya tiempo entre nosotros, muchos programadores siguen usando C++ a la antigua usanza, en detrimento de la legibilidad, flexibilidad de diseño e incluso del rendimiento que nos ofrecen las versiones más recientes. He aquí algunas cosas que puedes hacer fácilmente para comenzar a disfrutar de las ventajas del C++ moderno.

No hagas nada

Sí, como suena, la primera de ellas es “no hagas nada”. Haz un benchmarking de tu código antes y después de migrar y seguramente te sorprenderás, especialmente si haces un uso intensivo de contenedores de la biblioteca estándar.

Una de las principales razones de esto es la introducción de la semántica de movimiento, con la consecuencia añadida de que muchos de los métodos y constructores ya existentes han recibido soporte para argumentos r-value, lo que significa que, sin mover un dedo, disfrutamos ya de sus beneficios. Además, existen otras optimizaciones que pueden destacar dependiendo del compilador que usemos, tales como el return-value-optimization (RVO, obligatorio a partir de C++17).

El siguiente ejemplo muestra un caso no poco común de creación de un vector de objetos (adjunto también el código del benchmark):

std::vector<std::string> create_vector(size_t n, std::string s)
{
    std::vector<std::string> v;

    for (size_t i = 0; i < n; ++i)
    {
        v.push_back(s + s);
    }

    return v;
}

int main(int argc, char* argv[])
{
    const size_t n = atoi(argv[1]);
    const size_t m = atoi(argv[2]);

    int z = 0;
    for (size_t i = 0; i < m; ++i)
    {
        char c = (rand() % 26) + 'a';
        size_t sn = (rand() % 1000) + 10;
        std::vector<std::string> v = create_vector(n + i, std::string(sn, c));

        const int x = v.front().front();
        z += x; // to prevent compiler to remove code
    }

    std::cout << z << std::endl;

    return 0;
}

El compilador usado ha sido Apple clang version 14.0.3 (clang-1403.0.22.14.1), todos con nivel de optimización -O2 y bajo plataforma ARM. Todas las pruebas se han hecho con n=10000 y m=1000, midiendo el tiempo de ejecución con time 5 veces y promediando los resultados:

  • --std=c++03: real 5.586s, user 3.325s, system 2.010s
  • --std=c++11: real 1.856s, user 1.166s, system 0.689s
  • --std=c++14: real 1.875s, user 1.155s, system 0.692s
  • --std=c++17: real 1.829s, user 1.139s, system 0.689s
  • --std=c++20: real 1.839s, user 1.159s, system 0.674s

Sin hacer nada hemos podido triplicar la velocidad de nuestro programa simplemente compilando con una versión más moderna del lenguaje. Esto obviamente no quiere decir que todo nuestro programa se acelere 3x; este ejemplo está preparado específicamente para mostrar esta mejora, pero da una idea clara de los beneficios que implican las nuevas características del lenguaje.

Auto-matiza la deducción de tipos

C++11 introdujo un nuevo significado para la palabra reservada auto (es el único caso que conozco de cambios en este sentido). Se usa en una declaración para deducir el tipo de la variable a partir de su inicialización; y si el compilador no es capaz de hacer una deducción única, la declaración se considera incorrecta y se genera un error.

El uso de auto permite reducir la cantidad de código a escribir (y leer), simplificando el mismo, y moviendo el nivel de abstracción al qué en lugar del cómo (o con qué).

std::vector<int> generate_ids(int n) { ... }

// Before
const std::vector<int> ids_old_way = generate_ids();

// Now
const auto ids_old_way = generate_ids();

Un caso especialmente útil es a la hora de usar iteradores:

std::map<std::string, std::vector<std::string> > synonyms;

// Before
std::map<std::string, std::vector<std::string> >::iterator it = synonyms.find(word);

// Now
auto it = synonyms.find(word);

Además, se facilitan los refactorings y optimizaciones de código al reducir el número de errores de compilación: si se cambia el tipo de un contenedor, auto se deducirá el nuevo iterador y listo (obviamente, si el nuevo tipo de iterador no es compatible con el anterior sí puede haber problemas, por ejemplo si se pasa de un std::vector a un std::unordered_map). Lo mismo sucedería si se cambia el tipo de retorno de una función: con auto tendríamos ya hecha una parte del pastel.

La única pega (que por otra parte tiene su lado positivo), es que el nombre de la variable cobra más peso ya que no tenemos a la mano (¿ojo?) su tipo. Pero como dije, esto puede incluso mejorar el código al obligarnos a poner nombres descriptivos (cardVector podría ser cardCollection o simplemente cards, y esa enigmática DatabaseConnectionController* dcc = DatabaseConnectionController::create() pasar a ser auto dbConnectionController = DatabaseConnectionController::create()).

Pythoniza tu código

El que diga que C++ moderno no ha copiado se ha inspirando en aspectos de otros lenguajes más jóvenes (especialmente Python), pues simplemente está negando lo obvio. Las nuevas sintaxis introducidas no sólo ayudan a hacer un código más compacto, sino que además permiten mejorar la expresividad del código y elevar el nivel de abstracción.

  • Range-for: seguramente la más conocida de estas pythonizaciones, permite recorrer una colección de elementos, sin necesidad de preocuparse del tipo exacto de contenedor. C++ ya disponía de un par de formas de hacerlo (un for desde begin hasta end, y el std::for_each), pero el range-for es más natural en muchos casos donde solamente queremos recorrer los elementos (pero no modificar el contenedor, por ejemplo).

      for (size_t i = 0; i < container.size(); ++i) { // random-access iterators
          foo(container[i]);
      }
    
      for (std::list<int>::iterator it = list.begin(); it != list.end(); ++it) { // basic iterator version
          foo(*it);
      }
    
      for (auto it = container.begin(); it != container.end(); ++it) { // more generic using 'auto'
          foo(*it);
      }
    
      std::for_each(container.begin(), container.end(), foo); // using an algorithm
    
      for (auto&& c : container) { // range-for
          foo(c);
      }
    

    Como detalle curioso, he visto cómo el uso del range-for puede optimizar código en determinados momentos. Un range-for siempre copiará el iterador end(), por lo que si nuestro contenedor hacía uso de un end() costoso, eso que nos ahorramos.

    Por último, una rápida comparación entre std::for_each y los range-for:

    • Los range-for permiten utilizar las instrucciones break y continue para modificar el flujo.
    • std::for_each puede ser paralelizado (C++17, ver más abajo).
  • Utiliza las listas de inicialización, de esta forma puedes inicializar colecciones de datos en la propia declaración, e incluso hacerlas constantes.

      const std::map<int, std::string> numbers = {
          {1, "one"},
          {2, "two"},
          {3, "three"},
      };
    
  • Mejora la expresividad atando variables. Devolver pares o tuplas es una forma común de evitar crear structs específicamente para devolver varios valores en una función. Ahora bien, el problema surge rápidamente cuando no sabemos qué significan el .first o el .second, y peor aún si comparten el mismo tipo de datos.

      std::pair<int, std::string> get_id_and_name();
    
      // Without binding
      const auto id_and_name = get_id_and_name();
      std::cout << "ID: " << id_and_name.first << ", name: " << id_and_name.second << std::endl;
    
      // With binding
      const auto [id, name] = get_id_and_name();
      std::cout << "ID: " << id << ", name: " << name << std::endl;
    

    También puede usarse al iterar sobre mapas:

      std::map<int, std::string> roman_numbers;
    
      // Without binding
      for (const auto& it : roman_numbers) {
          std::cout << "Number " << it.first << " is " << it.second << std::endl;
      }
    
      // With binding
      for (const auto& [decimal, roman] : roman_numbers) {
          std::cout << "Number " << decimal << " is " << roman << std::endl;
      }
    

Haz uso de los nuevos contenedores y métodos

C++11 introduce nuevas estructuras de datos que mejoran drásticamente el rendimiento bajo determinadas condiciones:

  • Todo std::map cuyas claves sean tipos básicos (char, int, float, enumeraciones, punteros, etc.), y con más de unas pocas decenas de elementos, puede ser reemplazado por std::unordered_map. Es el equivalente de una tabla hash y sus operaciones son mucho más eficientes: O(1) de media para la inserción y la búsqueda, dependiendo de las colisiones que puedan generarse. También puede usarse con otros tipos, tales como std::string pero acá el rendimiento va a depender también del tamaño medio de la clave. Cuidado que con mapas de poco tamaño puede no notarse el rendimiento o incluso disminuir (el coste relativo de calcular la función hash respecto a la comparación del tipo bruto aumenta conforme el número de elementos es más pequeño).
  • De forma análoga tenemos a std::unordered_set como alternativa a std::set. En ambos casos es importante hacer notar que, tal y como indica su nombre, las claves no están ordenadas, por lo que hay que tener cuidado si la implementación actual depende de ello. Esto no debe de ser un impedimento por sí mismo para migrar; por ejemplo, si sólo se requieren las claves ordenadas para un proceso de serialización, y el rendimiento del mismo no es crítico, se podrían extraer las claves, ordenarlas y serializarlas en orden, manteniendo así la compatibilidad con el código anterior.
  • Usar std::string_view (C++17) en los argumentos de funciones que no requieren modificar la cadena de texto. std::string_view es básicamente un wrapper al estilo de las cadenas de texto en C (un puntero al primer caracter y un tamaño) pero de forma segura y compatible con std::string donde haga falta. De esta forma, cuando se requiere un subconjunto de la cadena, se evita pasar copias innecesarias.

Gracias a los r-value y a los variadic templates, C++11 introdujo nuevos métodos para añadir elementos a un contenedor de forma más eficiente. Tradicionalmente usamos push_back para añadir elementos a un std::vector o insert para los std::map. Ahora bien, en ambos casos el método primero reserva e inicializa el espacio para el elemento en el contenedor, y luego es que copia (o mueve) el elemento en sí. En la práctica esto significa que tenemos que llamar a un constructor por defecto y a un constructor de copia (o movimiento). En C++11 tenemos std::vector::emplace_back y std::map::emplace que nos permitirán construir in-place el elemento en su zona de memoria reservada, generando un código mucho más eficiente. Desgraciadamente, y por compatibilidad hacia atrás, los métodos anteriores push_back e insert no pudieron ganar esta mejora y la migración tenemos que hacerla a mano (además de cambiar costumbre de los métodos a usar).

struct my_bag {
    my_bag(int32_t a, int32_t b, int32_t c);
};

std::vector<my_bag> bags;

// Before
bags.push_back(my_bag(1, 3, 3));

// Now
bags.emplace_back(1, 2, 3);

Así, vemos que emplace_back se ha de llamar con los mismos argumentos del constructor. Si hiciese my_bag bag{2, 3, 4}; bags.emplace_back(bag); estaría llamando al constructor de copia, pero in-place, que sería una mejora más no la óptima.

Reduce la dependencia de bibliotecas de terceros

Como extensión del punto actual, y como ya se ha visto, C++11 y posteriores han ido extendiendo la biblioteca estándar con nuevos integrantes, muchas veces inspirándose en populares bibliotecas de terceros, especialmente Boost.

  • std::thread, std::mutex, para gestión de hilos y sincronización. boost::thread no es exactamente igual que std::thread, la de Boost tiene un conjunto mayor de funcionalidades, tales como interrupción de un hilo y manejo de colecciones de hilos.
  • std::chrono, para operaciones con unidades de tiempo.
  • std::optional, std::variant, para tipos opcionales y variantes tipo-seguras.
  • std::filesystem (C++17), para gestión del sistema de ficheros (aunque no es 100% equivalente).
  • std::ranges (C++20), inspirándose en ranges-v3.

Mejora la gestión de recursos

Uno de los puntos que muchos desarrolladores critican a C++ es la gestión de memoria (punteros nulos, colgantes, etc). Es cierto que, tal y como comentaba Bjarne Stroustrup, C hace que sea fácil pegarte un tiro en el pie; C++ lo hace más difícil, pero cuando lo haces te vuela toda la pierna. Pero también es cierto que desde C++11 es aún más difícil ya que la biblioteca estándar provee de muchos mecanismos para evitarlo. Los dos principales son std::unique_ptr y std::shared_ptr. el primero permite expresar que un objeto tiene un único dueño, mientras el segundo distribuye, mediante un contador de referencias, la propiedad entre varios.

Un ejemplo común para unique_ptr son las clases manager, que centralizan el acceso a un determinado recurso. Así, esta clase puede tener un unique_ptr y pasar una referencia a todas las demás. Además, los unique_ptr no pueden ser copiados, sólo movidos, por lo que la transferencia de propiedad es explícita. Los shared_ptr, por otro lado, son más comunes en elementos con un ciclo de vida impredecible o donde los actores creadores del objeto pueden desaparecer antes que el objeto en sí.

Un código de C++ moderno no debería usar punteros raw para almacenar objetos. A la hora de pasar un objeto unique_ptr podemos o bien usar una referencia (que además obliga a no pasar un nullptr); si el objeto puede no estar inicializado podríamos pasar un std::optional<Objeto&> (C++17), pero en este caso no hay una ventaja muy clara respecto a pasar un puntero raw ya que se puede usar mal en ambos casos. El acceso a punteros nulos sigue siendo responsabilidad del programador. Así que cuidado en este caso.

Ambos tipos de punteros inteligentes se basan en un principio muy conocido de C++ y del que ya he hablado en otras ocasiones: el RAII (ver RAII 1 y RAII 2). No me extenderé acá en este tema y refiero a dichas páginas para más información

Bonos

Algunos pequeños cambios adicionales que marcan una gran diferencia:

  • Nuevos literales: ""s para std::string, ""ms para std::chrono::milliseconds, 0x1234_u32 para enteros con tipo específico, etc. Hacen el código más expresivo y evitan conversiones implícitas.
  • Paralelización de algoritmos (C++17): simplemente añade std::execution::par a tus std::sort, std::transform y otros algoritmos para aprovechar múltiples núcleos automáticamente.
  • if constexpr (C++17): cambia el complicado SFINAE por código estructurado más legible en templates. Permite escribir código condicional que se evalúa en tiempo de compilación.
  • Designated initializers (C++20): inicializa estructuras de forma más clara con Point{.x = 10, .y = 20} en lugar de Point{10, 20}.
  • std::optional (C++17): expresa explícitamente cuando una función puede no devolver un valor válido, eliminando la ambigüedad de los valores “especiales” como -1 o nullptr.
  • Fold expressions (C++17): simplifica operaciones en parameter packs con expresiones como (args + ...) en lugar de recursión manual.
  • Lambda expressions mejoradas: desde C++11, pero con mejoras constantes. Usa [&] para capturar por referencia, [=] por copia, o mezcla ambas. En C++14 puedes usar generic lambdas con auto en los parámetros.
  • constexpr everywhere: marca funciones como constexpr siempre que sea posible. El compilador las evaluará en tiempo de compilación cuando pueda, mejorando el rendimiento.
  • std::array vs arrays C: reemplaza int arr[10] por std::array<int, 10>. Obtienes los beneficios de los contenedores STL sin coste adicional.
  • Inicialización uniforme: usa {} en lugar de () para la inicialización. Es más segura (previene narrowing conversions) y más consistente.

Conclusión

Migrar a C++ moderno no es solo cambiar el estándar del compilador; es adoptar una mentalidad que prioriza la expresividad, el rendimiento y la seguridad. Como hemos visto, algunas de estas mejoras llegan prácticamente “gratis” con solo recompilar el código, mientras que otras requieren cambios mínimos que pueden transformar drásticamente la calidad del software.

Los cinco puntos que hemos cubierto —aprovechar las optimizaciones automáticas, usar auto para simplificar el código, adoptar las sintaxis “pythonizadas”, migrar a contenedores más eficientes y mejorar la gestión de recursos— representan solo la punta del iceberg de lo que C++ moderno tiene para ofrecer.

La belleza del C++ moderno radica en que permite escribir código más limpio y expresivo sin sacrificar el rendimiento que siempre ha caracterizado al lenguaje. Al contrario, en muchos casos lo mejora. Así que la próxima vez que inicies un proyecto o tengas la oportunidad de refactorizar código existente, no dudes en darle una oportunidad a estas características. Tu código (y tus compañeros de equipo) te lo agradecerán.

» Leer más, comentarios, etc...

Navegapolis

IA en empresas ágiles y en las que dicen ser ágiles

junio 27, 2025 10:36

En la comunidad ágil, documentos como el “Scrum Guide Expansion Pack“ intentan guiar el uso de la IA en las organizaciones ágiles, pero la verdad es que pueden ser cuchillos de doble filo: puede ayudar a usar la IA sobre las bases de la agilidad o pueden ser la coartada perfecta para perpetuar viejos hábitos disfrazados de agilidad. En concreto el «Scrum Guide Expansion Pack» dedica gran esfuerzo a recordar que “lo humano es lo primero” y que la IA debe ser sólo un “aumento cognitivo”, pero al mismo tiempo se explaya en describir con detalle la eficiencia que aporta la inteligencia artificial.

Es inevitable preguntarse si en la práctica, la presión por los resultados no inclinará la balanza hacia la optimización y convertirá las buenas intenciones humanistas en discursos de bien quedar.

La encrucijada oculta en el mismo marco

Para la cultura de la empresa, el «Scrum Guide Expansion Pack» es un test de Rorschach, que refleja la que ya tiene. La empresa puede proyectar sus valores sobre el texto y encontrar, la confirmación que necesita para validar su enfoque.

Una organización centrada en las personas, busca en el texto la inspiración para validar o desarrollar su cultura de confianza, creatividad y desarrollo de personas y , por supuesto, la encuentra:

  • Encuentra un liderazgo que sirve, no que manda: Confirma su creencia en un liderazgo de servicio al leer que su función es «…cultivar el entorno para los equipos Scrum autogestionados…«. Identifica el significado de nutrir y remover obstáculos, no dirigir ni controlar.
  • Encuentra en la autogestión un motor de innovación al encontrar que los «Equipos Scrum autogestionados organizados en torno al valor son cruciales para la resolución creativa de problemas y la captura de la emergencia«. Valida que la autogestión es el camino para desatar el potencial colectivo ante la complejidad.
  • Pone a la IA al servicio del talento humano, afianzando su visión de la tecnología como una palanca para las personas, al leer que la IA debe permitir que «…los miembros humanos del equipo Scrum se centren en consideraciones estratégicas, creativas y éticas«. La IA es un asistente que libera, no un sustituto que optimiza.

Pero al mismo tiempo, una organización con una cultura arraigada en la eficiencia, la predictibilidad y el control de procesos, busca en el mismo documento la justificación para perfeccionar y acelerar su maquinaria productiva y, también la encuentra:

  • Encuentra en el Lean Thinking la coartada para la eficiencia extrema: El pensamiento lean «…reduce el desperdicio en el trabajo y en cómo se lleva a cabo…«. Desperdicio es todo lo que no sea producción directa, osea se pueden eliminar debates «innecesarios» y estandarizar procesos para maximizar el flow.
  • Encuentra en la IA el camino hacia la automatización del control: Se entusiasma al leer que la IA puede «…actualizar y repriorizar los elementos del Product Backlog…» o que sus «…análisis basados en IA mejoran la transparencia, la inspección y la adaptación», viendo aquí un consejo para reemplazar el juicio humano por algoritmos eficientes que optimizan el rendimiento a escala.
  • Encuentra en la responsabilidad un mecanismo de conformidad: Define la profesionalidad de forma rígida al encontrar que los desarrolladores son «…colectivamente responsables de: Instaurar la calidad adhiriéndose y mejorando la definición de resultado terminado (Definition of Output Done)«. La responsabilidad se convierte en conformidad con la norma, y la calidad en una checklist que el sistema debe superar.

Para esta organización, la “agilidad” es un marco adecuado para modernizar el taylorismo, con un barniz vanguardista, sin alterar su núcleo de control y eficiencia.

Las dos organizaciones aplican el “Pack”, y en ambos casos el resultado es la intensificación la cultura que ya poseían.

Cuando el marco deja de ser un mapa y se convierte en un espejo

Un marco ambiguo puede funcionar como un espejo que refleja la cultura y las intenciones preexistentes. A una organización centrada en las personas, el marco valida y refuerza su enfoque, pero a una organización centrada en el producto, también.

Puede ser un eco de lo que ya es la organización y el peligro se hace más profundo cuando no se usa como espejo sino como máscara. Porque la ambigüedad permite adoptar la retórica de la agilidad centrada en las personas —»empoderamiento» , «autogestión» «seguridad psicológica», etc. — para construir una fachada sin una realidad consecuente.

Las consecuencias: cinismo, estancamiento y devaluación

Para las personas, el cinismo de las organizaciones que predican el humanismo pero practican el taylorismo es agotador. Mata la confianza, la motivación y con ello: la creatividad.

Para la organización el resultado es una transformación estancada. Se adoptan los rituales, pero sin cambiar el ADN cultural, confundiendo la actividad con el progreso.

Y para la comunidad, el resultado es la devaluación del término «ágil», que pierde su significado para convertirse en un conjunto de herramientas que cualquier cultura, puede adoptar.

La entrada IA en empresas ágiles y en las que dicen ser ágiles se publicó primero en Navegápolis.

» Leer más, comentarios, etc...

Navegapolis

Agilidad en la encrucijada de la IA: ¿valor o personas?

junio 24, 2025 06:04

El Manifiesto Ágil redefinió el trabajo del conocimiento, al valorar a los «individuos y sus interacciones por encima de los procesos y las herramientas«. Durante más de dos décadas, este principio ha sido la estrella polar de los equipos que desarrollan en entornos complejos y volátiles (VUCA), en los que el valor lo aportan las personas. Pero ahora, la inteligencia artificial desafía esta situación y nos pone en una encrucijada.

La nueva realidad plantea una pregunta incómoda: en un mundo en el que la única inteligencia disponible es la humana, las personas, ¿son un fin en sí mismo o el medio para lograr el verdadero fin: el valor?

El Pacto Tácito: valor a través del bienestar

El paradigma de la producción está acotado entre dos extremos

  • los entornos industriales, donde son los procesos y la tecnología los responsables de la calidad y del valor del resultado. Donde las personas actúan como operarios que “ayudan” o supervisan, para que se ejecuten correctamente.
  • En el otro lado, en los entornos VUCA, caracterizados por la ambigüedad y el cambio constante. Donde la calidad y el valor del resultado depende del conocimiento tácito de las personas, ese que no puede ser explicitado en un proceso, y donde son los procesos y la tecnología los que “ayudan” y potencian el valor de las personas.

La agilidad entendió una verdad biológica: el talento y la creatividad humana dependen de factores emocionales como el estado de ánimo y la motivación. Para que “fluyan” es necesaria una cultura ágil en un entorno de trabajo centrado en el bienestar de las personas.

Esto nos lleva al dilema central: ¿Cuál es el objetivo de una empresa al apostar por la agilidad? ¿Las personas, y el valor que aportan es la consecuencia?, ¿o directamente el valor y para lograrlo tiene que desarrollar una cultura de bienestar? Hasta ahora la pregunta ha sido puramente filosófica, porque en los dos casos la respuesta es la misma: céntrate en las personas.

La Disrupción: cuando perdemos el monopolio de la inteligencia

Es probable que en poco tiempo la inteligencia artificial alcance el nivel general (AGI) y de capacidad “agéntica”, que rete lo que hasta ahora ha sido monopolio de la inteligencia humana. Pero no hace falta esperar a que esto ocurra (si finalmente ocurre). La actual generación de IA «estrecha» avanzada ya está ejecutando tareas complejas de conocimiento que eran dominio exclusivo del ser humano. Desde el análisis de datos en tiempo real hasta la generación de código o la validación acelerada de hipótesis. La IA ya puede conseguir eficiencia y optimización a una escala sin precedentes.

Esto convierte la pregunta filosófica de antes en una decisión de negocio crítica. Si el objetivo es maximizar el valor y la IA puede ofrecerlo sin el “coste” que implica la gestión el bienestar humano, la tentación de elegir esta ruta es muy atractiva.

La contradicción ya es palpable. Documentos como el «Scrum Guide Expansion Pack» dedican un gran esfuerzo a recordarnos que «lo humano es lo primero» y que la IA debe ser solo un «aumento cognitivo». Pero al mismo tiempo se explayan con en describir con detalle la eficiencia que aporta la inteligencia artificial. Es inevitable preguntarse si en la práctica, la presión por los resultados no inclinará la balanza hacia la optimización y convertirá las buenas intenciones humanistas en discursos de bien quedar.

La pregunta de si el objetivo es el valor o las personas pone al descubierto la base de las dos formas de entender la agilidad. Lo que se viene denominando “hacer agilidad” o “ser ágil”

Hacer agilidad o agilidad técnica.

Consiste en un desarrollo iterativo e incremental para entregar valor temprano, frecuente y creciente, empleando el conocimiento del sistema, que antes de la IA estaba explicitado en el binomio de procesos y tecnología y ahora se le suma un tercer pilar: la inteligencia artificial. La premisa es la de los entornos industriales: «la calidad del resultado depende de la calidad de los procesos (y la tecnología)».

Aquí las personas tienen un rol de asistencia. «Ayudan» o «asisten» a que el proceso tecnológico, ahora inteligente, se realice correctamente. Son los «humanos en el bucle» que validan las sugerencias de la IA, supervisan los algoritmos y corrigen las desviaciones del sistema optimizado. La agilidad aquí es sinónimo de velocidad, eficiencia y predictibilidad. Es el camino de la optimización, una ruta atractiva, fácil de justificar en un informe de resultados, pero que simplifica la agilidad a su esqueleto mecánico, despojándola del alma humanista.

Ser ágil o Agilidad completa.

Representa una reafirmación consciente de los principios originales. Es una «Agilidad completa» o humanista que, si bien también entrega valor temprano, de forma iterativa e incremental, lo hace desde un punto de partida opuesto.

Esta vía se aferra al principio de que la agilidad prefiere «a las personas y su interacción sobre los procesos y las herramientas», incluso cuando las herramientas son redes neuronales capaces de proezas cognitivas. Aquí, el conocimiento que realmente importa sigue siendo el «tácito humano», aquel que emerge de la experiencia, la colaboración, la intuición y la creatividad de un equipo de personas motivadas.

En este modelo, los procesos, la tecnología y la inteligencia artificial no son los protagonistas del sistema, sino los potenciadores del talento humano. La IA se convierte en un poderoso asistente que automatiza las tareas repetitivas, ofrece datos para enriquecer el debate, libera a las personas de la carga cognitiva trivial y permite al equipo centrarse en lo que los humanos hacemos de forma única: comprender el contexto del mundo físico real, empatizar con el cliente, negociar las complejidades políticas de un proyecto y, sobre todo, innovar de forma disruptiva.

Esta agilidad requiere, ahora más que nunca, «culturas ágiles» que proporcionen el bienestar y desarrollo personal. Porque si la IA puede replicar la inteligencia analítica, la ventaja competitiva humana residirá precisamente en su «naturaleza biológica / emocional»: la motivación, la curiosidad y un estado de ánimo positivo son las claves para que el talento pueda «fluir» y superar las soluciones algorítmicas.

¿Qué agilidad queremos?

La agilidad agilidad no se concreta por los avances de la IA, sino por las decisiones de los líderes. La elección entre un modelo «técnico» y uno «completo» no es una decisión tecnológica, sino una decisión estratégica y filosófica.

Podemos usar la IA para construir un taylorismo digital, una versión eficiente y optimizada de desarrollo de productos o podemos usarla para potenciar la inteligencia colectiva de los equipos.

¿Para qué queremos ser ágiles?

La entrada Agilidad en la encrucijada de la IA: ¿valor o personas? se publicó primero en Navegápolis.

» Leer más, comentarios, etc...

Meta-Info

¿Que es?

Planeta Código es un agregador de weblogs sobre programación y desarrollo en castellano. Si eres lector te permite seguirlos de modo cómodo en esta misma página o mediante el fichero de subscripción.

rss subscripción

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

Si tienes un weblog de programación y quieres ser añadido aquí, envíame un email solicitándolo.

Idea: Juanjo Navarro

Diseño: Albin