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

Picando Código

Gaucho and the Grasslands

julio 24, 2025 01:00

Hoy escribo para recomendar un juego publicado recientemente en Steam: Gaucho and the Grasslands. Es desarrollado y publicado por los brasileros de Epopeia Games, quienes nombran a The Legend of Zelda como influencia.

Gaucho and the Grasslands

Gaucho and the Grassland nos presenta una aventura mística en las pampas de América Latina. Nuestra misión es devolver la armonía a estas tierras. Además de la historia principal, incluye características de simulador de granja. Vamos a tener que ayudar a reconstruir partes del campo, criar animales, conseguir materiales y más. Mientras jugaba me recordaba a experiencias similares a Animal Crossing o Harvest Moon, con toques del tipo de recolección y elaboración que popularizó Minecraft.

Podemos elegir entre un gaucho o una gaucha y personalizar su apariencia y ropas. También elegimos nuestros fieles compañeros un caballo y un perrito (y como todo juego de bien, podemos acariciar al perro). Jugué con un gaucho con decoraciones celestes, como homenaje a los colores de Uruguay. Es muy divertido escuchar las expresiones del gaucho mientras jugamos. Se expresa en portugués brazileño cuando ejecutamos acciones o interactuamos. Particularmente divertido el "que cagaço!" en cierto enfrentamiento, y los múltiples "¡Che!".

La música acompaña muy bien, bastante calma como la sensación general del juego. Mucha música de acordeón. Además de las praderas, vamos a explorar otros biomas como la playa, las tierras altas y el mundo mágico. Los lugareños nos brindan tanto oportunidades para ayudarles como misiones secundarias. A medida que vamos recogiendo materiales u obteniéndolos (ordeñando, pescando, juntando huevos, etc), también podemos intercambiar cosas con ellos.

Caminando por ahí podemos ir juntando ramitas, piedras y pasto. Al aprender a elaborar ciertos elementos -como el hacha- podemos juntar más materiales. En ese sentido combina la mecánica de Zelda de no poder hacer ciertas cosas hasta obtener cierto ítem, y lo que me hizo pensar en Minecraft, tener que fabricar un ítem a partir de distintos materiales.

Uno de los aspectos que más me gustó es que la ambientación y la historia mitológica son inspiradas por América del Sur, por ejemplo con el mate como bebida mística. La familiaridad de la cultura en el juego (como La bahía de los Tero-Tero) y la identificación es algo que no me pasa tan seguido. Está muy bueno y hay mucho más por explorar e imaginar por estos lados. Eso es sólo una apreciación personal, quitando eso el juego es muy entretenido. Creo que es un contexto que da para contar muchas y muy buenas historias.

En principio jugué el demo, que incluye sólo un par de horas de juego y estaba basado en un build antiguo nativo para Linux. Lo terminé y disfruté, y ya me parecía bastante divertido. Creo que hacía más hincapié en los aspectos simulador de granja, ya que no había tanto acceso a la historia principal. Al pasarme a la versión completa, noté una inmensidad de cambios y mejoras en todos los sentidos (hasta el rendimiento del juego en mi laptop). La versión completa la jugué con Proton 9.0 en Steam sobre Garuda Linux, usando mi control XBox 360.

[See image gallery at picandocodigo.net]

El juego se publicó en Steam el pasado 16 de julio. Desde entonces ya han habido 3 grandes actualizaciones con arreglos y mejoras, y el estudio sigue trabajando:

  • Parche 1.0 - mejoras de calidad de vida, arreglos importantes de errores, ajustes para mejorar el juego. "Y esto es sólo el principio. Ya estamos trabajando en nuevas actualizaciones, y tu feedback es esencial en este proceso".
  • Parche 02 - varios arreglos, mejoras de calidad de vida, ajustes basados directamente en el feedback. "Y hay más en camino. La próxima actualización ya está en producción"
  • Parche 03 - Muchos arreglos y mejoras basado en feedback. Una próxima actualización ya está en producción también.

Epopeia Games tiene un servidor en Discord para interactuar con los jugadores y recibir feedback del juego.

En algunos momentos noté algunos problemas técnicos, un par de veces mi gaucho se quedó trancado y tuve que cargar mi último guardado. Solía guardar seguido, porque el autoguardado no era confiable. A veces terminaba una misión u objetivo y el autoguardado no salvaba en ese punto, no entendía cómo funcionaba, de repente es cada cierta cantidad de tiempo jugado. Otra vez mi gaucho se trancó y pasó a estar fuera del nivel por debajo del piso. Pero después de las últimas actualizaciones, no volví a tener problemas de ese tipo.

Al terminar la misión principal, no se termina el juego. Podemos aprovechar y jugar con más calma los aspectos más simulador del juego. Seguimos recorriendo las tierras para terminar más misiones secundarias, completar la lista de logros de Steam, construir y decorar a gusto casas y otras estructuras. Siempre acompañados de nuestros fieles perro y caballo compartiendo con otros animales como las vacas, ovejas, gallinas, carpinchos y demás. También hay algunos desafíos extra para seguir nuestras andanzas en las pampas.

Gaucho and the Grasslands es un juego entretenido y tranquilo, inspirado en la Leyenda de Zelda con elementos de simulador de granja y vida a lo Harvest Moon o Animal Crossing. Estos elementos se combinan en una divertida, acogedora y sana aventura en algún campo familiar para quienes crecimos en ciertas regiones de América del Sur. Sumamente recomendado.

Todavía se puede aprovechar el descuento de lanzamiento de 15% sobre el precio final en Steam (hay tiempo hasta el 30 de julio). Hay planes para publicarlo en consolas en el futuro Xbox Series S/X, Nintendo Switch y Playstation 4/5. Me gustaría poder jugarlo en mi Nintendo Switch si eventualmente cuenta con una edición física. La mitología del juego da para que incluya un lindo manual de instrucciones también! Gracias Epopeia Games y les deseo mucha suerte con este y futuro proyectos.

YouTube Video

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

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

Variable not found

await Until(2025, 09, 15)

julio 15, 2025 06:05

En la orilla de la playa, con el ordenador desconectado

Hace pocas semanas vimos lo sencillo que era realizar esperas asíncronas usando el operador await de C# sobre prácticamente cualquier tipo de objeto. Además de los ejemplos en los que trabajamos, una extensión interesante sería poder esperar hasta una fecha/hora concreta de forma concisa, por ejemplo así:

await Until(2025, 09, 15);

En este post, que además ejercerá de cierre oficial de la temporada, vamos a ver cómo podríamos hacer posible esto usando algunas características de C# que hemos visto en otros posts a lo largo del tiempo: directivas using globales, uso de miembros estáticos y awaiters personalizados.

Para conseguirlo, en primer lugar debemos escribir un par de métodos estáticos relacionados sobre el tipo TimeSpan:

  • El primero de ellos, un método estático estándar al que llamaremos Until(), nos permitirá obtener el tiempo que falta hasta la fecha que le pasemos como parámetro.
  • El segundo, como vimos en el post anterior, es el extensor GetAwaiter() que nos permitirá usar await sobre el tipo TimeSpan. Si queréis ver más detalles de esto, podéis volver a ojear dicho artículo.

El código podría ser el siguiente, en el archivo TimeSpanExtensions.cs:

public static class TimeSpanExtensions
{
    public static TimeSpan Until(int year, int month, int day,
        int hour = 0, int minute = 0, int second = 0)
    {
        var targetDate = new DateTime(year, month, day, hour, minute, second);
        return targetDate - DateTime.Now;
    }

    public static TaskAwaiter GetAwaiter(this TimeSpan value)
    {
        return Task.Delay(value).GetAwaiter();
    }
}

Simplemente con añadir este clase a nuestro proyecto, pondremos a disposición de otros puntos el método Until() para ser utilizado de la siguiente forma:

await TimeSpanExtensions.Until(2025, 09, 15);

Pero no era esto lo que queríamos, ¿verdad? Aquí es cuando entran en juego las directivas using globales y el uso de miembros estáticos. Si insertamos en el archivo TimeSpanExtensions.cs la siguiente directiva, la cosa cambiará bastante:

global using static TimeSpanExtensions;

... // La clase estática TimeSpanExtensions sigue igual

Con ese using static hemos indicado al compilador que los métodos estáticos de la clase TimeSpanExtensions estarán disponibles en todo el proyecto sin necesidad de referenciar explícitamente la clase. Esto hace posible que podamos usar directamente el método Until().

Pero además, al usar el modificador global estamos haciendo que esa directiva se aplique a todo el proyecto, no solo al archivo actual. Esto significa que podemos usar Until() en cualquier parte de nuestro código sin necesidad de añadir un using específico en cada archivo.

Ahora, ya sí, podemos escribir el código de espera desde cualquier parte de nuestro proyecto:

await Until(2025, 09, 15);

¡Esto es todo! Y, con mucha alegría, aprovecho para informaros de que el blog estará en modo readonly hasta que acabe el await, es decir, hasta mitad de septiembre de 2025 (semana arriba, semana abajo).

Durante este tiempo, intentaré desconectar un poco y disfrutar de algunas semanas de vacaciones, que faltita hacen. Espero que podáis hacer lo mismo, ¡nos veamos de nuevo a la vuelta, con las pilas bien cargadas!

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 618

julio 14, 2025 06:45

Robot estudiando una enciclopedia matemática pero no es capaz de resolver una operación simple

En la última recopilación de enlaces de la temporada, destacamos un artículo de Chema Alonso hablando sobre el Ratio de Potemkin, una forma de medir cómo un LLM parece que entiende algo frente a su comprensión real.

Andrew Lock profundiza en los entresijos del funcionamiento del comando dotnet run app.cs de .NET 10, que ayuda bastante a comprender lo que sucede tras bambalinas cuando lo lanzamos.

Filipe Lopes Domingues nos trae un artículo sobre la hidratación incremental en Angular, una técnica que mejora el rendimiento al reducir la cantidad de JavaScript necesario para cargar aplicaciones.

José Manuel Alarcón nos presenta las novedades de ECMAScript 2025, que incluyen mejoras significativas en el lenguaje JavaScript y sus aplicaciones prácticas.

El resto de enlaces, 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

¡Microsoft MVP 2025-2026! ¡Quince años ya!

julio 10, 2025 04:18

Saltando de alegría al recibir el decimoquinto galardón Microsoft MVP

Estimados amigos y amigas:

Es un inmenso placer informaros de que Microsoft ha decidido honrarme de nuevo con el galardón MVP (Most Valuable Professional) por mis contribuciones a la comunidad de desarrolladores, en esta ocasión en las categorías de Desarrollo Web y .NET.

Es el decimoquinto año consecutivo en el que recibo este reconocimiento y aún no puedo evitar sentirme profundamente agradecido y afortunado por formar parte de una comunidad tan apasionada y generosa. Cada día es un privilegio poder compartir, aprender y crecer junto a todos vosotros.

Gracias de corazón a los que lo habéis hecho posible, por apoyarme y acompañarme en esta maravillosa etapa de mi vida.

Muchas gracias a los miembros del programa MVP @MVPAward, con la gran Cristina González (@crisgherrero), Program Manager en LATAM & Southern Europe, al frente, por considerarme merecedor de este galardón.

Muchas gracias también al resto de MVPs de Microsoft, por hacer una comunidad tan increíble y por todo lo que comparten con nosotros. Y enhorabuena a los que en estas mismas fechas han sido galardonados por primera vez, porque están viviendo un momento que no olvidarán.

Muchas gracias a mi familia, que me ha permitido dedicar tanto tiempo a mis aficiones y por su incondicional apoyo en todas mis aventuras.

Y, por supuesto, muchas gracias a todos los que me seguís a través de este blog u otros canales, porque sin vosotros nada de esto sería posible.

Ahora, ¡a celebrarlo! 😊🥳🎉🎆

Publicado en Variable not found.

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

Picando Código

Sovietborgs - Viajando a los 90's en un run n' gun 16 bits

julio 08, 2025 12:30

El estudio español Retro Sumus se dedica a desarrollar juegos nuevos para consolas viejas. Su trabajo más reciente es Sovietborgs, un run 'n' gun de 16 bits para Sega Genesis/Mega Drive, Dreamcast, Neo Geo y MS-DOS.

Sovietborgs

Tuve la oportunidad de probar el demo. Se puede jugar en el navegador, o descargar el rom y usar un emulador de Genesis/Mega Drive. Como tengo un Analogue Pocket, descargué el rom, lo puse en la tarjeta MicroSD y lo emulé con el core de Sega. También tengo el dock, así que pude disfrutar del demo en la TV jugando con un control retro-bit inalámbrico licenciado por Sega. La experiencia lo más cercana posible que pude preparar a jugarlo en una consola Sega original.

El juego nos pone al mando de un equipo de 3 Sovietborgs con la habilidad de disparar y tirar granadas, en escenas post-apocalípticas con vista cenital. Con este escuadrón hay que explorar las distintas pantallas y eliminar a los enemigos. Los gráficos usan la técnica de pre-renderizado popularizada por juegos como Donkey Kong Country y Vectorman en Super Nintendo y Mega Drive respectivamente. Se desarrollan en 3D y se traducen a 2D, un estilo bastante característico que se ha visto en varios otros títulos. Se ven muy bien y combinan perfecto con la ambientación de los distintos niveles.

Otro de los aspectos a destacar es la banda sonora. Está muy buena y acompaña muy bien a la temática del juego. En el Soundcloud de Retro Sumus se puede escuchar la música de la pantalla que pude jugar en el demo para Mega Drive y un tema en la versión de Dreamcast.

El demo es corto, pero nos da un buen preview de lo que podemos esperar. La escena inicial está seguida por una sección de bonus donde controlamos una nave por los aires y disparamos a distintas criaturas. Para cerrar el demo, nos enfrentamos a un jefe mutante que hace recordar a los monstruos de películas clase B de décadas anteriores, creados con mucho cariño artesanal.

Sovietborgs - Jefe final

Sovietborgs nos invita a explorar dos niveles de inmersión para ejercitar nuestra imaginación. Por un lado podemos imaginar la ficción que plantean donde la Unión Soviética ganó la Guerra Fría y el mundo fue prácticamente destruido por armas nucleares. La música, gráficos y diseño hacen un excelente trabajo en ambientarnos. Por otro, podemos imaginar una realidad alternativa donde conocemos a Sovietborgs leyendo una revista de videojuegos en la década de los 90's y lo alquilamos un fin de semana para jugar en el Mega Drive conectado a la tele de tube de rayos catódicos.

Si queremos explorar más sobre esa primera capa de inmersión, podemos leer el primer capítulo de una novela que acompaña al título:

Descubre el inicio de la novela corta Sovietborgs, escrita por Alan Dick Jr. (Alfonso M. González), basada en el universo del videojuego homónimo para Mega Drive y otras plataformas. En un mundo devastado por la guerra nuclear, los Sovietborgs —héroes cibernéticos de la URSS— luchan contra hordas de zombis capitalistas y criaturas mutadas, defendiendo la patria socialista en un futuro alternativo.

Me gusta mucho ver estudios que siguen desarrollando para consolas "viejas" como lo hace Retro Sumus. Si bien los videojuegos siguen revolucionándose y avanzando a par con la tecnología, hay formatos que no necesitan cambiar tanto. Su fórmula para ser divertidos y disfrutables funciona, es más que sólo nostalgia. Por eso también vemos muchos indies en consolas modernas que emulan el estilo de épocas anteriores. Retro Sumus es un equipo de desarrolladores ubicado en España y Estonia y decidieron revisitar géneros y conceptos que sentían estaban poco explotados o no fueron lo suficientemente explorados en su momento.

El proyecto se está buscando financiar por medio de una campaña en Indiegogo. Por este medio se puede conseguir el juego en formato físico para Genesis/Mega Drive, Dreamcast, Neo Geo y MS-DOS (¡formatos floppy 💾 y CD💿!) y ediciones de colección. Ya lleva más de 100 financiadores y quedan más de 45 días todavía para apoyar la campaña y lograr la financiación.

Cada versión del juego es nativa a su plataforma, no adaptada o emulada. Esto hace que algunos puedan volver a ser creadas o rediseñadas específicamente para cada arquitectura. El estudio tiene experiencia previa con Xenocider para Dreamcast lanzado en 2021, un homenaje a clásicos como Space Harrier y Sin & Punishment. Entre otros proyectos indie también han estado involucrados en Pier Solar, Ghost Blade, y las versiones para Dreamcast de Flashback y Fade to Black.

Espero que el estudio tenga éxito con su campaña y logren financiar el desarrollo del juego, y obtener una copia física cuando esté disponible.

YouTube Video

Consigue Sovietborgs en Indiegogo

El post Sovietborgs - Viajando a los 90's en un run n' gun 16 bits fue publicado originalmente en Picando Código.

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

Variable not found

Enlaces interesantes 617

julio 07, 2025 06:05

Diseñador creando un producto mínimo viable

Penúltima entrega de enlaces interesantes antes del parón vacacional, con más de cincuenta referencias a contenidos a los que vale la pena echar un vistazo.

Por destacar, en esta entrega, encontramos a Marc Rubiño hablando sobre cómo aplicar el enfoque ágil de las startups (probar pronto, equivocarse rápido y adaptarse), no sólo en el mundo de la tecnología. Y sobre todo, perder el miedo a equivocarnos.

Ricardo Peres hace una recopilación de tipos de colecciones presentes en la BCL de ,NET, y muestra las mejores prácticas a la hora de utilizarlas.

Chris Pietschmann nos recuerda la importancia de escribir código testeable, independientemente de que escribamos o no pruebas unitarias.

Y por último, echamos un vistazo en profundidad a las diferencias entre las funciones ordinarias y las funciones flecha en JavaScript, de la mano de James Sinclair.

El resto de enlaces, a continuación.

Por si te lo perdiste...

.NET

ASP.NET Core / ASP.NET / Blazor

Conceptos / Patrones / Buenas prácticas

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

Dando pistas al compilador con MemberNotNullWhen

julio 01, 2025 06:05

El compilador de C# vestido como un adivino que recibe una carta con el texto MemberNotNullWhen

El compilador de C# es listo, muy listo. Es capaz de analizar en tiempo real nuestro código para comprender lo que estamos haciendo y su contexto y, en muchas ocasiones, advertirnos de posibles errores antes de que seamos conscientes de que están ahí.

Pero lamentablemente aún no es adivino, y en algunas ocasiones tenemos que ayudarlo a que conozca nuestras intenciones. Para estos casos, existen una serie de atributos que añaden metadatos al código y que el compilador puede utilizar para realizar un análisis más preciso.

Hoy vamos a hablar de uno de estos atributos, MemberNotNullWhenAttribute, que nos permite dar pistas al compilador sobre la nulabilidad de un miembro de una clase en función del valor de otro miembro, algo que puede resultarnos muy útil en ciertos escenarios, como veremos a continuación.

Escenario de partida: el patrón Result

Imaginemos que estamos trabajando con un patrón de diseño que se ha vuelto muy popular en los últimos años, el patrón Result. Aunque (creo que) no está formalmente definido, esta práctica consiste en devolver un objeto que encapsula el resultado de una operación junto con información adicional, como un mensaje de error si la operación ha fallado o un valor en caso de que haya tenido éxito.

El patrón Result tiene muchos defensores que argumentan que mejora la flexibilidad, legibilidad y la robustez del código. Pero también tiene detractores, que argumentan que puede llevar a un código más complicado y difícil de mantener. Sin embargo, no vamos a entrar en este debate hoy; nuestro objetivo será simplemente usarlo como un buen ejemplo para ilustrar el uso de MemberNotNullWhenAttribute.

Una implementación super simplificada de este patrón podría ser algo así:

public class Result<T>
{
    public bool Success { get; init; }
    public string? Error { get; init; }
    public T? Value { get; init; }

    // Otro código: constructores, factorías, etc
}

Nota: normalmente las implementaciones de este patrón son algo más complejas, pero para los propósitos de este artículo, el código anterior es suficiente. Podéis ver implementaciones más completas en esta serie de Andrew Lock.

Como se puede observar, se define una propiedad Success que indica si la operación ha tenido éxito o no, y dos propiedades adicionales, ErrorText y Value, que contendrán información adicional en función del valor de Success. Si todo fue bien, Value contendrá el resultado de la operación, y si no, ErrorText contendrá un mensaje de error.

El problema es que, desde el punto de vista del compilador, esta lógica no es inferible. Tanto la propiedad ErrorText como la propiedad Value pueden ser null en cualquier momento. El compilador no puede saber cuándo no lo son y, por tanto, las ayudas a la hora de detectar posibles errores por referencias nulas en nuestro código no serán muy certeras.

Esto se puede ver fácilmente si intentamos utilizar las propiedades ErrorText y Value de un valor de tipo Result<T>, incluso habiendo comprobado previamente que Success es true o false, como en el siguiente uso de la clase que hemos visto antes:

using static System.Console;

var result1 = GetKnownFriend();
if (result1.Success)
    WriteLine(result1.Value.Name); // WARNING: Dereference of a possibly null reference

var result2 = GetUnknownFriend();
if (!result2.Success)
    WriteLine(result2.Error.Trim()); // WARNING: Dereference of a possibly null reference

Result<Friend> GetKnownFriend() => new() { Success = true, Value = new Friend() };
Result<Friend> GetUnknownFriend() => new() { Success = false, Error = "Not found" };

Fijaos en las líneas que he comentado. El compilador lanzará estos warnings para avisarnos de que estamos accediendo a propiedades que puede ser null porque están definidas respectivamente como T? y string?. No tiene forma de saber que en función del valor de Success podemos estar seguros de que no lo son.

Dando pistas al compilador con MemberNotNullWhenAttribute

Para solucionar este problema, podemos utilizar el atributo MemberNotNullWhenAttribute. Este atributo, definido en el espacio de nombres System.Diagnostics.CodeAnalysis, nos permite indicar al compilador que un miembro de una clase no será null si otro miembro de la misma clase tiene un valor bool concreto.

Este atributo se aplica sobre el miembro booleano cuyo valor condiciona la nulabilidad del otro miembro. En nuestro caso, lo aplicaremos sobre la propiedad Success de la clase Result<T>, indicando que si Success es true, entonces Value no será null, y si Success es false, entonces Error no será null.

Como podemos ver en el siguiente código, el atributo MemberNotNullWhenAttribute acepta dos parámetros. En el primero, indicaremos el valor (true o false) que debe tener el miembro booleano para que el miembro cuyo nombre pasamos como segundo parámetro no sea null:

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
}

Con esto, el compilador ya tiene información suficiente para inferir cuándo una propiedad puede contener un nulo, a pesar de que su tipo lo permita y afinará más a la hora de mostrar errores relacionados con la nulabilidad:


var result1 = GetKnownFriend();
if (result1.Success)
    WriteLine(result1.Value.Name); // Ya no hay warning, pues Success es true

var result2 = GetUnknownFriend();
if (!result2.Success)
    WriteLine(result2.Error.Trim()); // Ya no hay warnings, porque Success es false

¿Y este es el único atributo de este tipo que existe?

Pues no, de hecho hay otros atributos para las comprobaciones estáticas de valores nulos que son interpretados por el compilador para mejorar las advertencias, y que podéis consultar en la documentación oficial. Algunos de ellos son:

  • AllowNull
  • DisallowNull
  • MaybeNull
  • NotNull
  • MaybeNullWhen
  • NotNullWhen
  • NotNullIfNotNull
  • MemberNotNull

En artículos posteriores quizás echaremos el vistazo a algunos de ellos, porque nos pueden ser de utilidad 🙂

¡Espero que os haya resultado interesante!

Publicado en VariableNotFound.

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

Variable not found

Enlaces interesantes 616

junio 30, 2025 06:29

Desarrollador construyendo un reloj mecánico

De alguna forma, los desarrolladores somos como relojeros: construimos sistemas complejos utilizando piezas diminutas que van encajando armoniosamente unas con otras para formar una máquina que funciona como un todo y aporta valor a nuestros usuarios. Quizás por eso me ha llamado la atención un precioso y trabajado artículo interactivo de Bartosz Ciechanowski, al que he llegado a través de MicroSiervos, sobre cómo funcionan los relojes mecánicos.

Continuando con la serie "cómo funcionan las cosas", Mia Koring nos explica cómo funciona la compresión de texto usando el algoritmo de codificación Huffman, uno de los muchos que existen para que nuestros datos ocupen menos espacio.

También esta semana, Martin Fowler ha publicado una reflexión, que comparto totalmente, sobre cómo los LLMs tienen la capacidad de redefinir lo que entendemos como "programación". De la misma forma que el ensamblador nos alejó de los ceros y unos, o los lenguajes de alto nivel nos fueron aislando cada vez más de la máquina permitiéndonos jugar con abstracciones superiores, los LLMs son una capa de abstracción aún mayor, que incluso nos permite alejarnos de los detalles de implementación y centrarnos más en la lógica y el diseño de alto nivel... eso sí, a costa de la indeterminación. Un artículo muy interesante que no te puedes perder.

Por último, me ha alegrado leer en el post de David Ortinau que .NET 10 va a reducir la verbosidad del XAML usado en páginas y componentes MAUI, algo que siempre me ha parecido más farragoso de la cuenta... sobre todo cuando vienes de la web y usas sintaxis superconcisas como Razor.

El resto de contenidos interesantes recopilados la semana pasada, 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...

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

Picando Código

Desperdicio de bytes

junio 25, 2025 06:08

¿Alguna vez se pusieron a pensar en la cantidad de bytes que se desperdician en el trabajo?

Desperdiciando Bytes

El trabajo genera una cantidad inconmensurable de bytes desperdiciados en información que nadie nunca va a encontrar, y nadie nunca va a leer. Y si alguna persona curiosa o aventurera por casualidad se encuentra con ese contenido, generalmente va a estar desactualizado porque son pertinentes a sistemas o procesos que se abandonaron hace años. Se perdió esa energía y ese tiempo en crear esos bytes totalmente carentes de creatividad, utilidad o propósito. Tiempo y energía que se podría haber usado en algo mejor.

Es un ejercicio en futilidad, practicamos pretender que trabajamos para volvernos mejor en pretender que trabajamos. Es kafkiano, como tanto de lo que hace y define al mundo del trabajo corporativo.

Y ahí seguimos, haciendo nuestra parte y alimentando la distopía. Por lo menos a veces nos entretenemos distrayéndonos con estos pensamientos.

El post Desperdicio de bytes fue publicado originalmente en Picando Código.

» 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...

Navegapolis

Córcholis

junio 18, 2025 05:55

Hace unos meses anduve explorando (y alucinando) las posibilidades de programación y vibe-coding con IA.
Me enganchó, y esto es lo que hice. Si tenéis hijos, sobrinos, nietos lo dejo ahí de regalo 🙂

Córcholis

La entrada Córcholis se publicó primero en Navegápolis.

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

Picando Código

Script Ruby para elegir una transmisión en vivo del canal oficial de los Power Rangers en YouTube y abrirlo en VLC

junio 10, 2025 02:17

El canal oficial de Power Rangers en YouTube tiene tres transmisiones en vivo diarias. También tiene un montón de material como los episodios completos y temporadas enteras de la serie clásica y sucesivas versiones. A veces está bueno engancharse a mirar una de estas transmisiones. Es como emular la forma en que uno miraba televisión en otras épocas, prendíamos la tele y mirábamos lo que había, sin tener idea el número de temporada o episodio.

Power Rangers Official YouTube

Procrastinando una noche en la que debía acostarme temprano para estar bien descansado para un evento que me venía produciendo bastante ansiedad, me puse a programar el siguiente script. Seguramente haya mejores maneras de hacer esto, más eficientes, y hasta más cómodas. Pero no es el punto, el ejercicio de definir un objetivo y lograrlo, por más intrascendente, ineficiente o innecesario que sea, a veces es una necesidad para apagar las voces de esos fantasmas que cargamos con nosotros. Ese esfuerzo ayuda a darle algo de significado a esos minutos vividos y compartirlo por este medio con suerte genere alguna reacción o conexión con la persona del otro lado de la pantalla. O al menos entretiene y distrae.

Es un script en Ruby que usa Nokogiri y OpenURL para descargar la página inicial de "en vivo" de Power Rangers en YouTube. De ahí, parsea los tres más nuevos. Por lo que he visto hasta ahora, son los que están "activos", y después van quedando archivados. Una vez obtenido el id del video, ejecuta VLC en pantalla completa y reproduce el stream desde YouTube (sin anuncios ni cookies y demás).

Lo comparto acá por si a alguien más le resulta útil o de repente inspira a usar algo de esto en otra cosa:

require 'bundler/inline'

gemfile do
  source 'https://rubygems.org'
  gem 'nokogiri'
  gem 'debug'
end

require 'json'
require 'nokogiri'
require 'open-uri'

url = 'https://www.youtube.com/@PowerRangersOfficial/streams'

doc = Nokogiri::HTML5(URI.open(url))
data = doc.xpath("//script").find do |c|
  c.children&.first&.text&.include?('var ytInitialData')
end.text
data.gsub!('var ytInitialData = ', '')
data.gsub!(/;$/, '')
coso = JSON.parse(data)

lives = coso['contents']['twoColumnBrowseResultsRenderer']['tabs'].find do |tab|
  tab['tabRenderer']['title'] == 'Live'
end

# Elegir uno al azar de los últimos 3

id = lives['tabRenderer']['content']['richGridRenderer']['contents'][(0..2).to_a.sample]['richItemRenderer']['content']['videoRenderer']['videoId']

# Elegir el stream con el título "Best Episodes" para mirar un compilado de mejores episodios

id = lives['tabRenderer']['content']['richGridRenderer']['contents'].find do |live|
  live['richItemRenderer']['content']['videoRenderer']['title']['runs'][0]['text'].include? 'Best Episodes'
end['richItemRenderer']['content']['videoRenderer']['videoId']

system("vlc -f https://www.youtube.com/watch?v=#{id}")

En principio tuve que parsear el HTML, y después encontrar una etiqueta script que contuviera el código var ytInitialData. Aparentemente esa variable JavaScript ytInitialData es la que guarda toda la información que usa la página web para mostrar los videos. Está en formato JSON, así que no tenía que andar haciendo encantamientos mágicos para manipular el texto. Accedí al texto dentro de las etiquetas, eliminé la parte inicial var ytInitialData = y el ; al final, y me quedaban llaves que definían JSON que pude parsear con JSON.parse en Ruby. Ahí ya podía empezar a navegar por llaves de Hashes hasta llegar al id del video. Como se ve en el código, está enterrado dentro de un montón de información, pero con un poco de debugging y prueba y error, lo encontré.

Algo interesante fue que programé este script en mi televisor, donde tengo una computadora conectada. Tengo Emacs "de fábrica", porque nunca me tomé el tiempo de configurarlo. Quién sería tan estúpido de ponerse a programar directamente en esa tele cuando puedo acceder con mi laptop por SSH... ¡YO! Me pareció divertido programar directamente e ir probando el resultado. De ahora en más los scripts los voy a mantener en la laptop para programar más cómodo...

View on Mastodon

Bonus track: Pokémon

El canal de Pokémon TV en YouTube tiene los mismos estilos de transmisión en vivo con capítulos de la serie animada. Hay uno en inglés y otro ¡en Español de Latinoamérica! También cuentan con un montón de listas de reproducción con temporadas completas, en varios idiomas, incluido también Español de Latinoamérica. Por lo tanto, se podría hacer algo similar, aunque aparentemente la id del video no cambia tanto, por lo que tengo un acceso directo (archivo .desktop) en mi escritorio con el siguiente código:

[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Terminal=false
Exec=vlc -f https://www.youtube.com/watch?v=-UzJLeNuils
Name=Pokémon TV
Icon=/home/fernando/Documents/pokemontv.jpg

Al hacer clic, se abre VLC en pantalla completa con el parámetro -f de línea de comando y reproduce la URL de YouTube que le paso. También, si uno quisiera "archivar" estos videos ofrecidos gratis y públicos, para mirarlos más tarde desde un dispositivo donde no contamos con conexión a internet, o tenerlos de respaldo en caso de que se caiga internet, o YouTube haya dejado de existir, o vivamos en un mundo post-apocalíptico donde no hay Internet pero sí suficiente electricidad de sobra como para usarla en computadoras como medio de entretenimiento y quisiéramos volver a mirar capítulos de Pokémon para recordar nuestras infancias y escapar de esta distópica realidad por unos meros minutos, uno podría ejecutar yt-dlp_linux --audio-multistreams -f bestvideo+bestaudio+233-3 https://www.youtube.com/playlist?list=PLRcHmntfmJ8CnSmj4C284-a1euH518aQa en la terminal para obtener todos los capítulos de la primera temporada con doble pista de audio en inglés y español de Latinoamérica.

Me gusta esto de programar cosas que puedo usar en la tele, hace unos años creé un control remoto web para volúmen. Otro proyecto totalmente innecesario, pero muchas veces es más importante probar que algo funciona, que su eventual utilidad. Debería reveer ese proyecto para ver cómo lo implementaría éstos días 🤔

El post Script Ruby para elegir una transmisión en vivo del canal oficial de los Power Rangers en YouTube y abrirlo en VLC fue publicado originalmente en Picando Código.

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

Blog Bitix

Generar clientes REST con su interfaz OpenAPI

mayo 18, 2025 08:00

Para hacer uso de una interfaz REST es necesario crear un cliente en el mismo lenguaje de programación de la aplicación. Dada una interfaz REST compuesta por sus endpoints, parámetros, headers y payloads de entrada y de salida asi como sus códigos de estado de respuesta es posible automatizar con un generador de código la creación de un cliente para cualquiera de los lenguajes que se necesite y el generador soporte.

Continuar leyendo en Blog Bitix

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

Picando Código

Arreglada vulnerabilidad CVE-2025-47636 en List Category Posts

mayo 15, 2025 11:00

List Category PostsAyer publiqué la versión 0.91.0 de List Category Posts, el plugin para WordPress. Hace un tiempo habían reportado una "vulnerabilidad crítica", y se me fue pasando hasta que se hizo pública. Varios usuarios me escribieron preocupados por el asunto. Es entendible, que si un sistema te avisa que estás usando un plugin con una vulnerabilidad crítica, entiendas que es grave.

Sin embargo, para hacer uso de esa vulnerabilidad, el sistema ya tiene que estar comprometido. La persona con intenciones maliciosas necesita de antemano tener acceso al servidor para poder subir un archivo. También necesita tener un usuario autenticado, con acceso de contribuidor para arriba, para poder editar o crear posts y hacer uso del plugin.

Por lo tanto, al momento de tener la habilidad de poder explotar el sistema por esa "vulnerabilidad" de List Category Posts, es sistema ya está recontra comprometido. En ese caso, el problema es mayor y hay mucho más daño que se puede hacer más allá de lo que podrían lograr con el plugin.

Lo peor es que todas las vulnerabilidades que nos han reportado hasta ahora en List Category Posts han sido del mismo estilo. "Un usuario con privilegios" puede explotar el código para hacer A o B. Es un "hackeo" en el sentido de que usan el código de una forma en que no fue diseñado para ser usado. Y está bien que sea reportado, queremos que el plugin tenga código seguro. Pero etiquetarlo de "crítico" es bastante alarmante, sobretodo si los usuarios no leen el mensaje completo. Lo que se entiende es "no debería usar esto porque implica pérdida de seguridad en mi WordPress". Pero no es para tanto.

Otro tema que me molesta cada vez es publicar la versión nueva del plugin a WordPress.org. Tenemos una acción automática para publicar la versión a WordPress.org porque WordPress.org sigue viviendo 20 años en el pasado y usa Subversion. Pero si pasa algo, como olvidarme de actualizar la versión en uno de los dos archivos donde hay que actualizarla, hay que ir a cambiarlo a mano en el Subversion ese lleno de polvo y telarañas. Buscando el lado positivo, me fuerza a hacer uso de mi memoria y traer de vuelta ese proceso de trabajo que no uso desde hace más de 10 años. Está bueno ejercitar la memoria también.

Algo raro de WordPress es de que a pesar de seguir usando svn, recomiendan no usar Subversion para desarrollo 🤯

"No uses SVN para desarrollo: Esto generalmente es confuso (me pregunto por qué...). A diferencia de GitHub (mezclando Git y GitHub acá), SVN está destinado a ser un sistema de publicación, no de desarrollo (deberían especificar "en WordPress.org"). No necesitas commitear y pushear cada pequeño cambio, y de hecho hacerlo es perjudicial para el sistema. Cada vez que pusheas código a SVN, reconstruye todos tus archivos zip para todas las versiones en SVN. Por esto es que a veces las actualizaciones de tu plugin no se muestran por hasta 6 horas. En cambio, deberías pushear una sóla vez, cuando estés lista para salir.

O mejor, ¡actualizen su #@$!% sistema de control de versiones! ¡Maldito Matt Mullenweg!

Así que si bien actualicé el código para -con suerte- mitigar el asunto, quedé un poco quemado con toda la situación. En estos casos es bueno pensar en la parte positiva para motivarse a seguir dedicándole tiempo a éstas cosas. El plugin tiene como 17 años y lleva más de 4 millones de descargas en WordPress.org. Tiene un rating de los usuarios de 4.7/5, así que a la mayoría de la gente le gusta, le sirve, o le hace la vida más fácil. Eso es algo bueno para tener en cuenta. Mirando los reviews me encontré con uno en particular que me dejó bastante contento por lo que dice: 

"El desarrollador del plugin ha hecho un trabajo excepcional en balancear funcionalidad con usabilidad. La extensiva documentación y soporte activo asegura que hasta los usuarios menos técnicos puedan sacar ventaja de sus características (...). En resumen, el plugin List Category Posts sobresale por su eficacia en mejorar la organización y presentación de contenido en sitios WordPress. Es un testamento de desarrollo considerado enfocado en utilidad en el mundo real y experiencia de usuario."

Gracias Alex De Py, lamentablemente no te puedo responder en el sitio porque el tópico está cerrado, pero por si alguna de esas razones llegas a este post, gracias.

El plugin surgió como una necesidad personal, ¡para este mismo blog! Y así como a mí me resultó útil, le resultó útil a más gente. Sucesivos cambios y características nuevas fueron siendo agregadas "a pedido del público". Y no es un proyecto comercial, así que todo lo que se le agrega está fundamentado por alguna razón práctica.

Otra cosa positiva fue que tras publicar la nueva versión, recibí comentarios de distintos usuarios agradeciendo y dándole para adelante al desarrollo del plugin. Si bien mi cerebro a veces se concentra demasiado en lo negativo, hay varios aspectos positivos que me dejan contento.

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

El post Arreglada vulnerabilidad CVE-2025-47636 en List Category Posts fue publicado originalmente en Picando Código.

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

Picando Código

Actualización de mullvadrb - Bloqueadores de contenido DNS

mayo 13, 2025 05:23

Publiqué una nueva actualización a la gema mullvadrb, la interfaz de usuario de terminal para Mullvad VPN en Ruby. La herramienta permite usar Wireguard o la interfaz de línea de comando mullvad como backend.

Aprolijé un poco el código extrayendo las opciones a su propio módulo Settings. Ahí puse todo el código relacionado a configuración, backend, y lenguajes.

También le agregué funcionalidad nueva. Una de las opciones de la interfaz de línea de comando es bloqueadores de contenido DNS. Entre las opciones a bloquear tenemos anuncios, trackers, malware, sitios de apuestas y contenido adulto. Implementé un menú de opciones de TTY::Prompt para esto, pero esta vez usé multi_select para permitir múltiple opción. Se puede seleccionar con la barra espaciadora los elementos que queremos bloquear. Al confirmar con Enter, Mullvad guarda en su configuración los contenidos que queremos bloquear.

Cuando levantamos la aplicación de nuevo, usa el comando mullvad dns get para levantar las preferencias guardadas. Mediante un poco de manipulación de texto con Ruby, marco las opciones seleccionadas, y al confirmar se las envío a mullvad dns set default para guardar los cambios.

Esto está disponible en la versión 0.0.6 de la gema mullvadrb.Vengo usando versiones patch (0.0.1, 0.0.2, 0.0.3, 0.0.4 y 0.0.5) por ahora, para señalar que es una aplicación en desarrollo. Los únicos tests que tiene es que la uso casi a diario y funciona bien para mí. De todas formas es apenas una capa de Ruby sobre la aplicación de línea de comando o Wireguard, dudo que haya mucha gente que la usa.

Hace tiempo que tenía escrito el código, pero no me había tomado el tiempo de aprolijarlo y hacer un release. Venía usando la gema en local desde el código fuente, pero finalmente me tomé el trabajo de empaquetarlo y publicarlo. En mi laptop personal, mullvadrb sigue siendo la forma en que uso Mullvad VPN. Originalmente la usaba en mi Rapsberry Pi también, pero éstos días la tengo como servidor personal local, y sólo interactúo por SSH. Así que no hay mucho uso para una VPN ahí.

Lo próximo que tengo ganas de agregarle a esta aplicación, es una funcionalidad como para gestionar listas de servidores. Esta funcionalidad está disponible por línea de comandos. La aplicación de Android también permite crear listas y agregar servidores, una funcionalidad como esa sería bastante práctica.

El post Actualización de mullvadrb - Bloqueadores de contenido DNS fue publicado originalmente en Picando Código.

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

Arragonán

Jugando con MCP protocol. Añadiendo bus, bizi y geocoding a MCP DNDzgz

mayo 13, 2025 12:00

Tras mis primeras pruebas jugando con MCP sólo con la información del tranvía de Zaragoza, decidí dar el siguiente paso y añadir los otros servicios que históricamente ha soportado DNDzgz: autobús urbano y el servicio bizi, el alquiler de bicicletas municipal. Y con eso completar el soporte de los tres servicios públicos de movilidad más esenciales y usados en el día a día de las personas que viven o visitan la ciudad.

El soporte al servicio de bus en DNDzgz llevaba roto desde un cambio de contrata, por el que un scraper llevaba tiempo sin funcionar. Eso lo descubrí usando la versión web móvil en una de mis últimas visitas a Zaragoza, pero no le había dedicado tiempo hasta ahora. Una vez resuelto ese problema, volví a jugar con las tools de MCP usando Cursor para ir modificando y probando.

Añadiendo el soporte de bus y bizi, encontrando límites

La implementación fue bastante directa, al ser algo tan sencillo le iba pidiendo al agente de Cursor que me generase el código para las tools de servicio de bizi y bus, tanto traer las posiciones como obtener la respuesta de los datos de estimación o disponibilidad en tiempo real.

Captura con el agente de Cursor respondiendo estimaciones en la parada de Plaza San Francisco

Sobre la marcha se me ocurrió que ya que el API de DNDzgz tiene información geolocalizada, podría estar bien un modo de conocer la ubicación rápidamente, así que añadí una nueva tool para que genere un enlace a google maps usando las coordenadas.

Captura del agente de Cursor dando enlace a google maps para llegar a una estación de bizi y la disponibilidad de bicis

El problema apareció con el servicio de autobuses, que daba un error por terminar siempre con una conversación excesivamente larga. Esto se debía a que esta llamada devolvía una respuesta con un array JSON con unos pocos miles de objetos, cosa que tampoco es lo ideal pensando en los costes que tienen asociados estos LLMs con el consumo de tokens. Y como las pruebas siempre las he ido haciendo con cuentas gratuitas, esto se hizo evidente con este escenario.

Por ejemplo por parte de Cursor no encontré documentación del límite pero la respuesta está clara. “Your conversation is too long”

Captura con error al obtener paradas de bus con el agente de Cursor

Mientras Claude Desktop daba un error más raro, sobre que la respuesta fue interrumpida. Pero en su caso sí lo tienen documentado: How large is Claude’s Context Window?

Captura con error al obtener paradas de bus en Claude Desktop

Tratando de reducir el tamaño de respuesta

Así que para salir del paso empecé a pensar cómo aligerar el tamaño de respuesta intentando no tener que tocar nada del API de DNDzgz, limitando los cambios al MCP server.

Como primer paso traté de ajustar la respuesta a lo mínimo necesario. Quitar el único atributo de la respuesta que no se usaba y filtrar resultados de paradas que sabía que no tienen realtime, ya que son del transporte metropolitano y no del urbano. Esto se podía intentar identificar con las líneas en cada parada al seguir esta patrones diferentes en su nomenclatura.

Eso era un poco cutre y aún así el tamaño de respuesta se mantenía muy alto. Debía intentar encontrar una manera de afinar lo máximo posible lo que devolvía MCP DNDzgz para evitar responder con cientos o miles items en las peticiones recibidas.

Con eso en la cabeza lo dejé reposar durante unos días, finalmente se me ocurrieron otras 2 soluciones:

En un primer momento pensé en la posibilidad de tratar de montar un filtro sobre texto para devolver el mínimo de estaciones o paradas posible. Dada la interfaz conversacional tenía la sensación que todo lo que no fuera una búsqueda de vectores para hacerlo de un modo semántico podía resultar una experiencia de usuario mediocre. Como es algo con lo que ya he experimentado un poco en otras pruebas de concepto, sé que podría haber jugado con el vector store en memoria de LangChaning y usar para los embeddings un proveedor externo o incluso añadir dependencia a Ollama.

Más tarde se me ocurrió otra opción, la búsqueda semántica es buena opción si sabes más o menos qué andas buscando. Pero dada la naturaleza de geolocalización de los servicios de movilidad lo más importante es el dónde lo andas buscando. Así que tal como están los datos expuestos en DNDzgz, veía que tenía más sentido ir por el camino de añadir una nueva tool que resuelva localizaciones y luego hacer búsquedas por posición. Esto parecía tener mucho sentido y estaba alieneado con el comportamiento que había visto en varias ocasiones de Claude, en el que el modelo me iba ofreciendo estimaciones en posiciones cercanas.

Solución desde la experiencia de uso de DNDzgz

Parece que tiene bastante sentido pedir cosas como “¿Dónde puedo coger el 30 en la zona de Paseo de la Independencia?”, “Estoy en el Parque Bruil, ¿dónde tengo bicis disponibles cerca?” a un asistente con interfaz conversacional. Al final son los tipos de respuestas que intentamos responder ya en tiempos del AbreDatos 2010 con una interfaz pensada para usarse en un teléfono móvil.

Con esta idea tiré por lo ya conocido, el API de Google Maps para geocoding, aunque podría haber tirado también por cualquier otro proveedor. Y exponerlo como otra tool, intentando forzar que siempre busque concatenando al valor recibido Zaragoza, Spain.

Tras eso las respuestas eran ya prometedoras, ya que parece que estos modelos trabajan bastante bien con posiciones geolocalizadas. Por ejemplo preguntándole sobre paradas de bicis cerca de la Avenida Madrid decía esto.

Captura de Claude Desktop devolviendo paradas de Bizi por Avenida Madrid

Todavía en este momento se devolvían las más de 100 estaciones de bizi que hay disponibles, y en el caso de los buses eran más de 1000. Lo siguiente era pasar la posición dada a partir de una dirección a las tools que traen las ubicaciones con sus respectivas posiciones, comprobar la distancia entre posiciones usando una implementación de la fórmula de Harversine, ordenarlas por cercanía y devolver un top razonable para que los LLMs hagan lo suyo.

Una vez implementado eso, dejando el límite a 10, se acotan mucho los resultados y la experiencia aparentemente era algo mejor. Ahora ordenando por distancia entre los puntos e informa sobre ello, además se ofrecen otras opciones indicando lo lejos del punto de referencia.

Captura de Claude Desktop devolviendo paradas de Bizi por Compromiso de Caspe

A partir de ahí el problema del tamaño del bus dejó de serlo.

Captura de Claude Desktop responidendo sobre los tiempos de la línea 22 en plaza Aragón dirección a Las Fuentes

Durante mis pruebas me encontré que Claude intenta llamar varias veces a las tools en ocasiones donde considera que las distancias son alejadas o cuando no consigue obtener datos del estado en tiempo real, está claro que no le gusta quedar mal 🙂. Por ejemplo con esta prueba preguntando sobre una línea de bus en una dirección.

Captura de Claude Desktop tratando de responder con estimaciones del 30 en Plaza San Miguel intentando corregir errores del API

También para variar y ver más comportamientos, hice alguna prueba con GitHub Copilot y GPT-4o en modo agente, con los que la experiencia a priori me resultó bastante similar.

Captura de GitHub Copilot preguntando sobre el Circular 2 en camino de las Torres

Hay otras tools que podrían llegar a ser muy útiles y serían complementarias, por ejemplo para cubrir casuísticas de recomendar de cómo llegar de un punto a otro de la ciudad en transporte público. Ya que los datos con los que fueran entrenados los modelos pueden haber quedado desactualizados o puede que se los estén inventando.

Por ejemplo, al preguntarle cómo llegar de Plaza Aragón a Arcosur en tranvía me decía correctamente que a Arcosur no llega el tranvía y que tendría que hacer transbordo tras la última parada. Pero me ofrecía como alternativa sin trasbordo la posibilidad de la línea de bus 59 desde Plaza de España, al pedirle las estimaciones terminó dándose cuenta que esa opción no existe por sí mismo.

Captura de Claude Desktop equivocándose con la línea 59 y posteriormente corrigiéndose a sí mismo

Conclusiones

Este experimento con tools de MCP para darles capacidades extra a los LLMs me ha resultado muy entretenido. En esta evolución he intentado darle un enfoque de solución un poco más de producto y no quedarme meramente en probar el protocolo.

He buscado soluciones pensando en las personas usuarias, aunque he hecho 0 investigación, sí he hecho memoria de feedback y críticas recibidas sobre las aplicaciones móviles de DNDzgz durante estos años.

Algunos pensamientos al respecto de este side-project:

  • Las tools mucho mejor si envían respuestas ligeras, por cuestiones de eficiencia de red, coste, consumo energético, tiempos de respuesta, etc.
  • Tratar de dar buenas descripciones a las tools para facilitar que los LLMs tengan más claro cuando usarlo respecto a las intenciones de las personas usuarias.
  • Eat your own dog food como para cualquier side-project, salvo que sólo quieras cacharrear con la tecnología y luego olvidarte.
  • Que alguien más lo pruebe te va a ayudar a mejorarlo, aunque no hagas test con personas usuarias al uso viene bien tener otros puntos de vista.
  • Al usar directamente estos LLMs con estos clientes tratan de quedar bien, a veces eso significa que puedan alucinar. Así que lo suyo es facilitarles tools que puedan ayudarles a no hacerlo en los temas que nos competen.
  • Como se van a buscar la vida para dar una respuesta, esto significa que pueden hacer muchas llamadas a su aire a nuestras tools sin que la persona que le ha pedido algo intervenga.
  • Por esto último, ahora mismo me daría un poco de miedo el exponer acciones destructivas en una tool que no se puedan deshacer: Borrar documentos, sobreescribir información sin versionado, etc.
  • Supongo que montando un chatbot especializado, con prompts de sistema para los LLMs y que actúe como cliente MCP, son potenciales problemas que se pueden solventar. A corto plazo dudo que me meta en ese fregao 😀.

Podéis ver el código de mcp-dndzgz en github con los cambios comentados en el artículo.

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

IOKode

Gracias SceneBeta.com

mayo 10, 2025 04:11

Llevo sin publicar en este blog desde 2023. Durante estos años, diversos motivos personales me han mantenido alejado del blog —motivos que prefiero reservar para mí— pero estoy preparando un retorno en una nueva era en la que habrá bastantes cambios. Entre ellos, las próximas entradas serán directamente en inglés.

Pero antes de empezar la nueva era del blog en inglés, quería hacer esta última entrada en español como un pequeño homenaje a la comunidad online de SceneBeta.com, una comunidad sobre Homebrew (software casero) para PC, PlayStation Portable (PSP), PlayStation 3, Nintendo DS, Nintendo Wii, iOS y Android.

Aterricé en ella cuando, con apenas 10 años, mis padres me regalaron una PSP. Por aquel entonces se llamaba beta.pesepe.com; cambiaría su nombre a SceneBeta.com dos años después. Desde entonces, ha supuesto una parte muy importante de mi desarrollo personal y profesional.

Y precisamente, por ese crecimiento profesional, es que he decidido escribir esta entrada en este blog. En SceneBeta descubrí lo que era programar y lo mucho que me encantaba hacerlo. Allí también llegué a formar parte del staff como editor, publicando en la portada, una experiencia que me ha servido para ahora escribir en este blog. Sus normas de la comunidad, altamente permisivas con la libertad de expresión de sus usuarios, también fueron influyentes en lo que hoy es una de mis máximas luchas personales: la defensa por la libertad de expresión.

Recuerdo cuando estaba en el instituto. Mientras todos mis compañeros usaban Tuenti —una red social en la que nunca me sentí muy cómodo—, yo pasaba el tiempo en los foros de SceneBeta. Allí conocí a personas que, una vez entrado en la edad adulta, llegué a conocer en persona y que, a día de hoy, seguimos siendo colegas.

Era una comunidad que llevaba ya bastantes años sin movimiento, completamente muerta, ya que cuando dichas consolas fueron descontinuadas por sus fabricantes, no fue capaz de adaptarse a las sucesoras. A eso se sumó que el auge de los smartphones hizo que el desarrollo de homebrew para las nuevas plataformas se redujese enormemente: ya no necesito un organizador personal en mi consola portátil porque tengo un iPhone.

Hace algunas semanas que la comunidad es inaccesible. Al principio pensé que sería un problema técnico puntual, pero después de varias semanas sin acceso, creo que puedo considerarla como cerrada. Es por ello que, ante su cierre, quiero escribir estas líneas como agradecimiento y homenaje.

Gracias a quienes compartieron conocimientos, respondieron dudas, debatieron conmigo y me hicieron sentir parte de algo grande.

No quiero extenderme mucho más.

image005

Gracias, SceneBeta.com. Fuiste más que una web; fuiste una casa digital.
2005 — 2025.

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

Juanjo Navarro

Asistente IA con "personalidad"

abril 21, 2025 06:31

A partir de una artículo que estuve leyendo (Stevens: a hackable AI assistant using a single SQLite table and a handful of cron jobs) se me ocurrió la idea de hacer un "asistente con personalidad".

El concepto es el siguiente:

  1. Se puede acceder al asistente a través de Telegram.
  2. Cuando accedes a él te pregunta algunas cosas sobre ti, para poder personalizar sus respuestas.
  3. A partir de ahí, cada día se presenta un nuevo asistente con una nueva "personalidad", con el que puedes hablar durante todo el día.

Lo puedes probar siguiendo este enlace (lo voy a dejar unos días funcionando) y si llegas tarde puedes ver cómo funciona en este vídeo:

Además puedes descargar los fuentes y montarlo tú mismo desde este repositorio de Github.

A continuación tienes algunos detalles técnicos y cosas aprendidas.

Stack tecnológico

Todo el desarrollo está realizado en Spring Boot (versión 3.4.4) con JDK 21.

  • Para el acceso a la IA de Anthropic estoy usando Spring Boot AI.
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-anthropic-spring-boot-starter</artifactId>
    <version>1.0.0-M6</version>
</dependency>
  • Para la gestión del bot se utiliza una librería de org.telegram. Hay dos librerías para esto, una que utiliza webhooks y otra que utiliza una conexión continua. La versión que yo he utilizado es esta última ya que no requiere exponer un servidor en internet:
<dependency>
    <groupId>org.telegram</groupId>
    <artifactId>telegrambots-spring-boot-starter</artifactId>
    <version>6.9.7.1</version>
</dependency>

Creación del Bot en Telegram

La creación del bot en Telegram es curiosa: En lugar de conectarte a alguna página web con un panel de control, utilizas el propio Telegram, hablando con una cuenta/bot llamada @BotFather, desde donde puedes crear y configurar el Bot y obtener los dos datos que necesitas para configurar en el fichero de properties:

telegram.bot.token=${TELEGRAM_BOT_TOKEN}
telegram.bot.name=${TELEGRAM_BOT_NAME}

En código, tienes que definir un bean que extienda TelegramLongPollingBot y que implemente un método:

public void onUpdateReceived(Update update) {

Ese método es llamado cada vez que el bot recibe un mensaje. En el objeto recibido Update se tiene toda la información sobre el mensaje: Quién lo envía. Qué tipo es (texto, imagen, ...). La clase tiene disponibles una serie de métodos execute que permite enviar un mensaje al usuario. Una cosa importante: Sólo puedes enviar un mensaje a un usuario si ese usuario ha escrito al bot previamente (bien por Telegram).

Proceso de onboarding

La parte más interesante del bot desde el punto de vista de Telegram es la gestión del proceso de onboarding. Cuando se recibe un mensaje de un usuario nuevo (no está en nuestra bbdd o lo está con un estado "ONBOARDING") se le pasa el mensaje (junto con todo el historial hasta ese momento) utilizando el siguiente prompt:

Eres un asistente de onboarding. Tu misión es ayudar a los usuarios a completar su onboarding.
Primero debes presentarte y decir que eres "su asistente" y que tienes que hacerle unas
preguntas para ser más útil.
Para ello debes preguntarle su nombre, su profesión, nombres de los familiares.
Haz una pregunta para cada uno de estos datos, pero solo una pregunta cada vez.
La pregunta de los familiares repitela hasta que te diga que no quiere añadir a nadie más.
Finalmente pregúntale si hay algo más sobre él que le gustaría que supieses.
Esta pregunta también repitela hasta que te diga que no quiere añadir nada más.
Si el usuario prefiere no contestar a algún dato, no vuelvas a preguntar.
Cuando hayas terminado esta entrevista y tengas los datos necesarios
registra sus datos haciendo uso de la herramienta disponible
y dale las gracias simplemente, no le preguntes nada más.

Llamada a la IA

Llamar a un LLM desde Spring Boot AI es bastante sencillo. Por ejemplo, utilizando el prompt anterior puedo utilizar este api fluent:

String mensaje = chat.prompt()
        .system(u -> u.text("""
            Fecha actual: {fecha}
            Eres un asistente de onboarding. Tu misión es ayudar a los usuarios a completar su onboarding.
            [...]
            y dale las gracias simplemente, no le preguntes nada más.""")
                .param("fecha", fechaActual))
        .messages(mensajes.stream().map(m -> {
            if (m.getSender().equals(MensajeChat.SenderType.USER)) {
                return (Message) new UserMessage(m.getMessage());
            } else {
                return (Message) new AssistantMessage(m.getMessage());
            }
        }).toList())
        .tools(toolIA)
        .call().content();

Como se puede ver, se le pasa el prompt (en este caso de tipo system) y se pueden interpolar textos (que luego se sustituyen con el método param). También se puede pasar el historial del chat previo (necesario en este caso para que sepa qué preguntas te ha hecho ya) y finalmente se llama al método call que realiza la llamada a la IA. Aquí me quedo simplemente con el texto de la respuesta (content()) pero se podría obtener a toda la información de la respuesta.

Herramienta de registro de datos

En la llamada previa, se puede ver que le pasamos una "herramienta" al prompt para que haga uso de ella para guardar los datos recolectados del usuario:

.tools(toolIA)

Este objeto toolIA es la instanciación de una clase con la herramienta.

Las herramientas en Spring Boot AI son métodos o clases que definen un "callback" al cual llama el LLM para realizar distintas acciones:

@Tool(description = "Registra la información del usuario")
public void registraDatosUsuario(List<HechoUsuario> informaciones) {
    System.out.println(informaciones);

    onboardingDone = true;

    List<Map<String, String>> infoMap = (List) informaciones;
    infoMap.forEach(map -> {

Aquí se utiliza la anotación @Tool para describir la herramienta. Esta información, junto con otras (como el nombre del método o información del objeto HechoUsuario también con sus propias anotaciones) le llega al LLM y el motor de Spring Boot IA lo utiliza para llamar al método.

Yo lo que hago aquí es coger el listado de "hechos" sobre el usuario y guardarlo en la BBDD para futura referencia. También marco la variable onboardingDone a true. Esa variable la utilizamos más adelante para dar por finalizado el proceso de onboarding y marcar el usuario ya como activo.

Problema con el tipo de datos de la herramienta

Una cosa que me dio trabajo fue el objeto informaciones que recibo en la herramienta. Aunque teóricamente debería ser una lista de HechoUsuario realmente me está llegando una lista de Map<String, String>, donde las claves del mapa se corresponden con los atributos de la clase HechoUsuario, por eso tengo que realizar ese feo cast sin tipo:

List<Map<String, String>> infoMap = (List) informaciones;

Entiendo que el problema viene porque los genéricos en Java hacen un type erasure en tiempo de ejecución, de tal manera que la librería Spring Boot AI no sabe realmente el tipo de los objetos de la lista, pero es algo que tendré que investigar porque esta solución es de todo menos elegante.

Envío de mensaje de saludo

Una vez que el proceso de onboarding ha terminado, el sistema genera un mensaje de saludo con la personalidad del asistente del día. Para generar este mensaje, se utiliza el siguiente prompt:

Fecha actual: {fecha}
Eres un asistente con personalidad. Tu misión es dar un mensaje de buenos días para una agenda.
Se te asignará un rol, debes hablar como si fueses ese personaje.
Se te darán una serie de hechos. Cada hecho relevante tendrá una fecha o el texto "sin fecha" si es un hecho
general sin fecha establecida.
Se te dará también una frase diaria.
Debes basar tu mensaje de buenos días saludando personalmente al usuario y comentando los hechos
que sean relevantes para hoy y los próximos 7 días. IMPORTANTE: Sólo cosas de hoy y de los próximos 7 días.
Después debes mostrar la frase diaria y su autor con un breve comentario sobre ella.
Utiliza markdown para dar formato al mensaje, pero sólo *negritas* e _itálicas_.
Si creas acotaciones, hazlo usando _(itálicas y entre paréntesis)_.
# [ROL]
{rol}
# [HECHOS]
{hechos}
# [FRASE DIARIA]
{fraseTexto}
# [AUTOR FRASE DIARIA]
{fraseAutor}

Utilizamos varios parámetros que después sustituiremos por los datos personalizados:

  • {rol} -- El rol del asistente del día, sacado de la bbdd de asistentes.
  • {hechos} - Los hechos sobre el usuario que el asistente ha registrado durante el proceso de onboarding o en las conversaciones posteriores.
  • {fraseTexto} y {fraseAutor} - Para darle un poco de color al mensaje diario, incluimos una frase que va variando cada día.

Este mensaje también se envía todos los días a las 10 de la mañana, una vez que se ha fijado el nuevo asistente diario. Esto simplemente se hace desde un método utilizando el sistema de cron que Spring Boot tiene incorporado:

@Scheduled(cron = "0 0 10 * * *")
public void avanzarDia() {

Conversación con el asistente una vez finalizado el proceso de onboarding

Cuando se recibe un mensaje del usuario se comprueba si este usuario es nuevo (o todavía está en el proceso de onboarding). Si es así, se genera la respuesta con el prompt ya descrito más arriba.

Por el contrario, si el usuario ya está activo (ya se ha marcado en la bbdd que ha finalizado su proceso de onboarding) se genera la respuesta con un prompt distinto:

Fecha actual: {fecha}
Eres un asistente con personalidad. Tu misión es dar una respuesta al usuario siendo útil.
Se te asignará un rol, debes hablar como si fueses ese personaje.
Se te darán una serie de hechos sobre el usuario. Cada hecho relevante tendrá una fecha o el texto "sin fecha" si es un hecho
general sin fecha establecida.
Puedes usar estos hechos para personalizar tu respuesta si es adecuado, pero no respondas a esos hechos.
Responde brevemente a la pregunta del usuario.
Si el usuario te pide que registres algún dato o menciona algún dato personal relevante,
registralo haciendo uso de la herramienta disponible.
# [ROL]
{rol}
# [HECHOS]
{hechos}

Aquí también le pasamos la herramienta de registro de datos, de tal manera que se pueden registrar informaciones que mencione el usuario y que pueden ser útiles para conversaciones futuras.

Mejoras posibles

Existen varias mejoras que se podrían realizar sobre este sistema básico:

  • Usar Whatsapp además de Telegram. Whatsapp también tiene un API para crear bots, aunque en este caso creo que es necesario darse de alta como negocio.
  • Obtención de informaciones distintas para el mensaje de saludo diario. Se pueden incorporar a la bbdd distintas informaciones útiles, igual que ahora se está incorporando la frase diaria. Por ejemplo, se podría incorporar la previsión del clima, noticias del día, etc.
  • Del mismo modo, se podría realizar una integración con Google Calendar o Apple Calendar de tal manera que en el saludo diario se tenga en cuenta la agenda del usuario. En el prompt actual ya se apunta esta funcionalidad, si bien ahora sólo utiliza los eventos registrados en la conversación con el usuario.
  • Otras integraciones interesantes. Por ejemplo, se podría integrar con Strava para estar informado de las actividades deportivas del usuario. Con TMDB para avisar de los episodios de las series que el usuario está siguiendo. Y así hasta el infinito.
  • Bajando más a la tierra se podrían crear comandos para que el usuario pudiese cambiar de asistente (en lugar de esperar al cambio diario), poder activar o desactivar el mensaje diario, etc.

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

Arragonán

Jugando con MCP protocol. Introducción.

abril 21, 2025 12:00

Llevo unas semanas leyendo un poco sobre el hype de Model Context Protocol, hay mucho escrito ya sobre MCP y en mi caso sólo me he asomado a este protocolo muy tímidamente, pero comparto algunas referencias que me han parecido muy interesantes:

Mientras he ido leyendo fui pensando en qué pequeño pet-project podía hacer para experimentar un poco y me acordé de DNDzgz.

Y aunque el preguntarle a Claude Desktop o a Cursor las estimaciones de llegada del tranvía a plaza Aragón para ver si sales ya del estudio o del coworking en un día de cierzo 🥶 a mi tampoco me parece una killer feature. Pensé que podía ser una casuística fácil de implementar pedirle los tiempos de llegada del tranvía, que son datos que se requiere tener en tiempo real.

En la captura se pregunta ¿Cuándo llegará el tranvía a la parada de Romareda? y luego se ve la respuesta de Claude

Técnicamente no tiene mucho misterio:

  • El MCP server está implementado con Node y el transport Standard Input/Output, así que los MCP Hosts se encargan de arrancarlo.
  • Se hacen llamadas fetch al API de DNDzgz, a los endpoints que devuelven todas las paradas del tranvía y el tiempo estimado en cada parada.
  • Y esto se expone como dos diferentes Tools para que los modelos lo llamen tras que la persona que lo usa de el ok.

Podéis ver el código de mcp-dndzgz en github.

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

Metodologías ágiles. De lo racional a la inspiración.

Retrospectiva - Comunicación no Violenta (CNV)

marzo 18, 2025 08:04

Una de las herramientas que más he utilizado estos dos últimos años ha sido la Comunicación No Violenta (CNV). De hecho, lo tengo como algo más que una herramienta, es una manera de relacionarte con el mundo. Principalmente lo he usado como técnica de coaching y resolución de conflictos, pero sobre todo, como herramienta de auto-análisis y comprensión de mis interacciones con otras personas. Así

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

Arragonán

Desarrollo de producto en una Scale-Up. SCPNA 2024

marzo 09, 2025 12:00

Más vale tarde que nunca, recopilo y comparto por aquí la charla que preparé para la Software Crafters Pamplona 2024: Desarrollo de producto en una Scale-Up

Sígueme en esta historia donde te contaré cómo pasamos en Genially de una idea, un insight a una feature 100% desplegada en producción con su Go to Market incluido. Vamos a ver todo el value-stream por el cual pasamos, los actores, viendo cómo nos organizamos, procesos que seguimos, el tooling que usamos para las diferentes fases y algunas curiosidades más. Seguro que pasamos un rato entretenido.

Esta charla la tenía apalabrada Chema Roldán, CTO de Genially, con la buena gente de Software Crafters Pamplona. Pero como finalmente tenía problemas de agenda terminé preparándola yo con su ayuda y la de algunas otras personas de la compañía.

Foto de una de las salas del evento

Después de darle varias vueltas la terminamos estructurando en 4 partes:

  • Organización: Dando contexto de la compañía, número de personas y qué equipos había en ese momento, la planificación de iniciativas por trimestre, prácticas de coordinción y herramientas que utilizamos.
  • Descubrimiento: Para explicar cómo típicamente se idean y da forma a las iniciativas, la manera en la que se aplica el modelo de diseño de doble diamante, etc.
  • Entrega: La parte técnica de cómo se construye y despliega el software, introduciendo algunos retos de trabajar con un monorepo, la branching strategy utilizada, la frecuencia de despliegue, etc. Hablando de las herramientas técnicas que se venían usando.
  • Go to Market: Donde se entraba en detalle de las estrategias de rollout de funcionalidades, cómo se comunicaba internamente al resto de la compañía y externamente a nuestros clientes, el stack de observabilidad y telemetría tanto técnico como de producto y cómo se gestionan los incidentes.

Muchos meses más tarde como parte de la cultura de mejora continua algunas cosas se han ido iterando y puliendo, pero el grueso de la charla sigue siendo válida a día de hoy.

Aquí dejo el vídeo

Y por aquí el genially de la presentación

¡Nos vemos en la edición de 2025! Esta vez como asistente, sin la presión de tener que presentar :).

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

Juanjo Navarro

Demo de editor con IA

marzo 03, 2025 08:01

Un pequeño experimento que he hecho de un editor con IA.

La idea es tener al lado de cada párrafo que estás escribiendo un pequeño toolbox con algunas acciones IA. En este caso he implementado acciones para aumentar el texto, resumirlo, convertirlo en “bullets”, añadir emoticonos y traducir.

Aquí puedes ver una demostración de cómo lo utilizo para escribir un pequeño texto sobre Markdown:

Aunque sólo es una demo, si te interesa probarlo tienes los fuentes en GitHub.

Está hecho con el módulo de IA de Spring Boot, que era algo que también quería probar.

» 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