Quantcast
Channel: Planeta Código
Viewing all 2715 articles
Browse latest View live

Blog Bitix: Comando para convertir una imagen SVG a PNG y JPG con diferentes tamaños y color de fondo con Inkskape

$
0
0

El formato de imagen SVG tiene la ventaja de permitir el escalado de la imagen sin pérdida de calidad y suele tener menor tamaño que la imagen equivalente en formato PNG y JPG. Si es necesario la imagen SVG es exportable a formato de imagen PNG y JPG con el tamaño deseado o color de fondo. El editor de imágenes Inkscape permite con su utilidad de línea de comandos automatizar y exportar archivos SVG a PNG y JPG y ocultar y mostrar las capas deseadas del archivo original para obtener el resultado deseado en la exportación.

Inkscape

GNU

El formato de imagen SVG es un formato de imagen vectorial donde las líneas, formas, posición y colores se describen en formato texto, tiene la ventaja de ser un formato escalable que no pierde resolución independiente del tamaño de imagen en la que se represente, es decir, la imagen tiene la misma calidad al tamaño full hd 1920x1080 que a 4K 3840x2560 que en 800x600 píxeles.

Con las imágenes de fotos en formato JPG y sin pérdida de calidad PNG la resolución adecuada para mostrar estas imágenes es la original del archivo a otra resolución hay que hacer un escalado con un algoritmo para añadir o quitar píxeles, el escalado es una operación imprecisa que resta algo de calidad a la imagen. Escalar el tamaño de una imagen JPG o PNG es necesario para obtener la imagen en otros tamaños, dependiendo del número de píxeles a añadir si se hace más grande que la original o píxeles a quitar si se reduce el tamaño la pérdida de calidad se nota más o menos.

Imagen en formato JPG originalImagen en formato JPG escalada a 300x200 píxeles

Imagen en formato JPG original y escalada a 300x200 píxeles

Los navegadores y dispositivos móviles ya soportan como formato de imagen el SVG, en la web y los dispositivos móviles es especialmente adecuado este formato ya que además de adaptarse a la variedad de tamaños de los dispositivos de escritorio o móviles suelen tener un menor tamaño de archivo lo que hace que se descarguen más rápido al requerir menos ancho de banda.

Aún con los beneficios que posee el formato SVG algunas aplicaciones no soportan el formato SVG y en este caso es necesario hacer una conversión de SVG a los formatos binarios rasterizados PNG o JPG. El formato SVG permite obtener estas imágenes PNG y JPG en diferentes tamaños sin pérdida de calidad.

Inkscape es un editor de imágenes vectoriales con una utilidad de línea de comandos que permite convertir y exportar imágenes en formato SVG a PNG y JPG en el tamaño y con el color de fondo deseado. El siguiente comando convierte todos los archivos SVG a PNG de una carpeta. En el comando se indican varios parámetros como la anchura deseada de la imagen, el color de fondo, los identificativos de las capas a exportar, el formato de salida y el nombre del archivo creado. Posteriormente con un segundo comando hay que convertir las imágenes de formato PNG a JPG, dependiendo del tipo de imagen, los colores y degradados de la imagen el tamaño en formato PNG será mayor o menor que en formato JPG.

1
2
$ for f in *.svg;do inkscape -w 750"$f" --export-background white --export-background-opacity 1 --export-type png --export-filename "$(basename ${f%.*})-750.png";done;
$ for f in *.png;do convert "$f""${f%.*}.jpg";done;
inkscape-convert-svg-png.sh

Imagen original en formato SVG

Imagen original en formato SVG

El editor Inkscape permite definir capas con diferentes elementos de la imagen, la linea de comandos permite exportar únicamente capas deseadas de la imagen para obtener el resultado deseado en la exportación. El SVG anterior contiene en el mismo archivo diferentes capas con diferentes versiones de la imagen adecuadas para un fondo claro y oscuro.

1
2
3
4
5
6
7
8
9
BASENAME="apache-tapestry"ARTWORK_FILE="$BASENAME-artwork.svg"

$ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-icontext-800-light.png"
$ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-icontext-800-dark.png"
$ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightIconObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-icon-800-light.png"
$ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkIconObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-icon-800-dark.png"
$ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightTextObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-text-800-light.png"
$ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkTextObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-text-800-dark.png"
inkscape-png-versions.sh

Imagen en formato PNGImagen en formato PNG

Imagen en formato PNGImagen en formato PNG

Imagen en formato PNGImagen en formato PNG

Diferentes versiones de la imagen SVG en formato PNG

También es posible modificar el SVG original para mostrar y ocultar las capas visibles del archivo. Los siguientes comandos permiten exportar a PNG la imagen en diferentes versiones (icono y texto, solo icono o solo texto), con diferente color de fondo (transparente, blanco y negro) y en diferente tamaño. Esto permite automatizar y hacerlo mucho más rápido que el repetitivo proceso que sería realizar manualmente la exportación usando la interfaz gráfica de Inkscape.

1
2
3
4
5
6
7
8
9
BASENAME="apache-tapestry"ARTWORK_FILE="$BASENAME-artwork.svg"

$ (cp "$ARTWORK_FILE""$BASENAME-icon-light.svg"&& inkscape "$BASENAME-icon-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
$ (cp "$ARTWORK_FILE""$BASENAME-icon-dark.svg"&& inkscape "$BASENAME-icon-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
$ (cp "$ARTWORK_FILE""$BASENAME-text-light.svg"&& inkscape "$BASENAME-text-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb EditDeselect --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
$ (cp "$ARTWORK_FILE""$BASENAME-text-dark.svg"&& inkscape "$BASENAME-text-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
$ (cp "$ARTWORK_FILE""$BASENAME-icontext-light.svg"&& inkscape "$BASENAME-icontext-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
$ (cp "$ARTWORK_FILE""$BASENAME-icontext-dark.svg"&& inkscape "$BASENAME-icontext-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
inkscape-svg-versions.sh

Imagen en formato SVGImagen en formato SVG

Imagen en formato SVGImagen en formato SVG

Imagen en formato SVGImagen en formato SVG

Diferentes versiones de la misma imagen SVG

Variable not found: Enlaces interesantes 408

$
0
0
Enlaces interesantes

Seguimos comentando los códigos de estado HTTP que coinciden con el número de entrega de la serie enlaces interesantes, y en esta ocasión le ha llegado el turno al HTTP 408.

Este código de estado es enviado por un servidor cuando cierra una conexión debido a que se ha superado el tiempo de espera máximo establecido para recibir una petición, es decir, se ha producido un timeout antes de que la petición haya terminado de llegar. En estos casos, el error suele ir acompañado de un encabezado Connection con el valor close, que indica que el servidor desea cerrar la conexión:

HTTP/1.1 408 Request timeout
Connection: close

Y ahora, ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

Eventos

.NET Core / .NET

ASP.NET Core / ASP.NET

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

    Web / HTML / CSS / Javascript

    Visual Studio / Complementos / Herramientas

    Xamarin

    Otros

    Publicado en Variable not found.

    Variable not found: Cómo invocar funciones Javascript desde Blazor (interop 1/3)

    $
    0
    0
    BlazorAunque uno de los objetivos de partida de Blazor es permitir que toda la lógica de presentación de una aplicación pueda ser implementada sin tocar una línea de Javascript, sería absurdo ignorar que este lenguaje dispone de uno de los ecosistemas más amplios y ricos del mundo del software. Sin duda, sería una pena (y un error) intentar ignorar la ingente cantidad de bibliotecas y componentes que existen para él.

    Tampoco podemos olvidar que puede que nosotros mismos tengamos bibliotecas Javascript que nos podría interesar reutilizar en nuevas interfaces de usuario. De nuevo, sería un error que el hecho de saltar a Blazor nos obligara a reescribir todo desde cero.

    Por estas razones Blazor dispone de mecanismos para interoperar con Javascript bidireccionalmente, pues:
    • desde nuestras aplicaciones Blazor podemos invocar funciones Javascript,
    • y desde Javascript podemos invocar métodos escritos en C# con Blazor.
    En este post vamos a centrarnos en la primera posibilidad, dejando la segunda para otras publicaciones de la serie que llegarán más adelante.

    ¿Cómo invocar métodos Javascript desde Blazor (Server o WebAssembly)?

    Durante el arranque de una aplicación Blazor se registra en el inyector de dependencias un objeto IJSRuntime, una abstracción que nos dará acceso a los objetos y funciones Javascript que estén disponibles en la página actual.

    Como podemos ver, esta interfaz es bastante sencilla pues sólo nos facilita un método InvokeAsync() con un par de variantes:
    public interface IJSRuntime
    {
    ValueTask<TValue> InvokeAsync<TValue>(string identifier, object[] args);
    ValueTask<TValue> InvokeAsync<TValue>(
    string identifier, CancellationToken cancellationToken, object[] args);
    }
    Un detalle interesante es que, por detrás, cada "sabor" de Blazor deberá implementar esta interfaz expresamente, pues la forma de acceder al Javascript del navegador cambia radicalmente según si estamos ejecutando nuestra aplicación con Blazor Server o Blazor WebAssembly.
    Por tanto, ya de forma intuitiva, podemos ver que para invocar código script desde Blazor, lo único que tenemos que hacer es solicitar al inyector una instancia de IJSRuntime y utilizar el método InvokeAsync(), como en el siguiente ejemplo, que en este caso retornará el valor obtenido tras hacer un prompt al usuario:
    @inject IJSRuntime JsRuntime

    <h1>Blazor To JS Demo</h1>
    <button class="btn btn-primary" @onclick="PromptNameAsync">Click!</button>
    @if (!string.IsNullOrEmpty(name))
    {
    <p>Hello, @name</p>
    }

    @code {
    string name;

    async Task PromptNameAsync()
    {
    name = await JsRuntime.InvokeAsync<string>("prompt", "What is your name?");
    }
    }
    El parámetro genérico de InvokeAsync() indica el tipo de dato retornado por la llamada a la función Javascript; el primer parámetro es el identificador de la función y a continuación van los argumentos que queramos enviarle.

    Además de los métodos impuestos por la interfaz IJSRuntime, podemos utilizar métodos extensores que nos simplificarán algunos escenarios. Por ejemplo, si estamos llamando a una función que no retorna ningún valor, podremos utilizar InvokeVoidAsync() de una forma muy similar a como hemos visto arriba:
    await JsRuntime.InvokeVoidAsync("console.log", "Hello world!");
    await JsRuntime.InvokeVoidAsync("alert", "Warning!");
    Por supuesto, la función a invocar puede ser de las existentes "de serie" en Javascript, o bien una función personalizada que hayamos implementado sobre la página o en un .js vinculado a ella, por ejemplo:
    await JsRuntime.InvokeVoidAsync("location.reload");
    Observad que en todos los casos la llamada es asíncrona; esto, además de por razones estructurales (en Blazor Server el método a invocar está físicamente separado del punto desde el cual realizamos la llamada), es para permitir la llamada a funciones asíncronas que retornan un objeto Promise de Javascript:
    function executeSomething() {
    return new Promise((resolve, reject) => {
    console.log("Waiting 3 seconds...")
    setTimeout(() => resolve(), 3000);
    });
    }
    La función anterior podría ser ejecutada de la siguente manera:
    async Task PromptNameAsync()
    {
    await JsRuntime.InvokeVoidAsync("executeSomething");
    }
    Otras variantes del método nos permiten indicar un token que podemos utilizar para cancelar la llamada cuando nos interese. Por ejemplo, el siguiente código muestra cómo utilizar un timeout, lo cual puede venir bien si no queremos que una llamada se quede "enganchada" más tiempo de la cuenta:
    try
    {
    var source = new CancellationTokenSource(TimeSpan.FromSeconds(1));
    await JsRuntime.InvokeVoidAsync("executeSomething", source.Token);
    await JsRuntime.InvokeVoidAsync("alert", "Done!");
    }
    catch (TaskCanceledException ex)
    {
    await JsRuntime.InvokeVoidAsync("alert", "The task was cancelled!");
    }
    Como parámetros en las llamadas podemos utilizar cualquier tipo de objeto, que serán serializados automáticamente y enviados a la función. Por ejemplo, imaginad que tenemos una clase de datos como la siguiente:
    // Friend.cs
    public class Friend
    {
    public string Name { get; set; }
    public int Age { get; set; }
    }
    Esta clase podríamos utilizarla directamente en una invocación a través del interop con Javascript:
    @page "/"
    @inject IJSRuntime JsRuntime

    <h1>Complex object demo</h1>
    <button class="btn btn-primary" @onclick="ShowFriendData">Click!</button>

    @code {
    async Task ShowFriendData()
    {
    var friend = new Friend {Name = "John", Age = 32};
    await JsRuntime.InvokeVoidAsync("showFriendData", friend);
    }
    }
    Y la función Javascript podría ser la siguiente; observad que al serializar se han convertido las propiedades a camel casing, que es lo que se esperaría desde el lado Javascript:
    function showFriendData(friend) {
    alert(`Hello, ${friend.name}, you are ${friend.age} years old`);
    }
    De la misma forma, podríamos utilizar tipos complejos como retorno de estas llamadas, es decir, hacer un InvokeAsync<Friend>() sobre una función Javascript que retornara un objeto de dicho tipo.

    ¿Y si no quiero ejecutar una función, sino leer una variable o propiedad?

    Es importante tener en cuenta que, como su nombre sugiere, InvokeAsync() sólo puede ser utilizado para invocar funciones. Si quisiéramos, por ejemplo, obtener el valor de una variable o propiedad, deberíamos utilizar una función JS personalizada.

    Por ejemplo, supongamos que queremos obtener el título de la página actual, disponible en la propiedad de la página document.title desde Blazor. Lo primero sería crear la función que actuará como puente:
    function getTitle() {
    return document.title;
    }
    Y luego, ya desde Blazor, podríamos invocarla con total normalidad:
    @page "/title"
    @inject IJSRuntime JsRuntime

    <h1>Show title Demo</h1>
    <button class="btn btn-primary" @onclick="ShowTitle">Click!</button>

    @code {
    async Task ShowTitle()
    {
    var title = await JsRuntime.InvokeAsync<string>("getTitle");
    await JsRuntime.InvokeVoidAsync("alert", "Title: " + title);
    }
    }
    ¡Y esto es todo! Creo que a lo largo de este post hemos visto lo más destacado de este mecanismo que nos permite invocar funciones Javascript desde nuestro código C#/Blazor.

    En entradas posteriores veremos cómo hacerlo en sentido inverso, es decir, permitir que desde Javascript invoquemos métodos escritos en C#.

    Publicado en Variable not found.

    Bitácora de Javier Gutiérrez Chamorro (Guti): Casio fx-9860GIII

    $
    0
    0


    Hace un par de meses Casio anunciaba la disponibilidad de sus nuevas calculadoras gráfica GIII, con el objetivo de sustituir a las anteriores GII lanzadas en 2009. Mientras que el modelo de entrada fx-7400GIII heredero de la fx-7400GII, no va a llegar a España, sí que ha llegado, y de hecho ya se vende la …

    Casio fx-9860GIII Leer más »



    Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

    Fixed Buffer: Azure DevOps Ephimeral Agents: Agentes de usar y tirar

    $
    0
    0
    Tiempo de lectura:7minutos
    Imagen ornamental para el artículo "Azure DevOps Ephimeral Agents: Agentes de usar y tirar"

    En la última entrada tocamos un tema un poco diferente ya que nos salimos de temas como entity framework core, código de alto rendimiento y la línea general del blog para hablar de un tema un poco más general como es el cómo controlar un ordenador utilizando simplemente la mirada.

    Hoy toca volver a la carga con la temática habitual y les toca el turno a los agentes efímeros de Azure DevOps.

    ¿Qué es un agente efímero de Azure DevOps?

    La primera pregunta que hay que resolver es obviamente que es un agente efímero de Azure DevOps, y la respuesta ya se puede intuir del título, un agente de usar y tirar. La idea es crear un agente que esté disponible para poder realizar un trabajo y al acabarlo se elimine.

    Los agentes de Azure DevOps es el nombre con el que se conoce a las diferentes máquinas que están conectadas a la organización de DevOps y que son las encargadas de realizar los trabajos de integración y despliegue. A su vez, los agentes se organizan en grupos llamados pools

    ¿Por qué se puede querer un agente de usar y tirar?

    La segunda pregunta que hay que responder es sin duda para que puede hacer falta algo así. Para ello hace falta conocer un poco el funcionamiento de los agentes de Azure DevOps.

    Cuando tu creas una organización, automáticamente y sin que tengas que preocuparte de nada, se crean diferentes pools de agentes hospedados por Microsoft.

    Esto se puede comprobar de manera sencilla yendo a las propiedades de la organización o a las de cualquier proyecto. Dentro de ellas hay un apartado específico para los pools de agentes donde se muestran los diferentes grupos de agentes disponibles.

    La imagen señala el botón Agent pools dentro de las propiedades de la organización y muestra los dos pools que hospeda Microsoft que son Azure Pipelines y Default

    Lo bueno de estos agentes hospedados por Microsoft es que no requieren de ningún preparativo ni mantenimiento, están listos para usar desde el primer momento y corren directamente sobre Azure. Si el software que traen instalado de serie es suficiente (que es lo habitual), puedes usarlos sin preocuparte de nada.

    ¿Entonces, dónde está el problema? Principalmente tienen 2 problemas:

    • Eres dependiente del software preinstalado
    • Todos los recursos que necesites deben ser accesibles desde internet

    Para el primero de los problemas se puede buscar alguna solución como por ejemplo instalar tú mismo lo necesario en el agente dentro del propio pipeline. En cambio, el segundo caso es el que generalmente se vuelve un problema serio…

    Imagina que durante el proceso de integración continua o para desplegar necesitas acceder a un recurso de una red privada. Al no tener ningún tipo de control sobre los agentes, no hay manera posible de conectarlo a la red privada y por tanto el agente no va a poder hacer su trabajo.

    Hasta la llegada a Azure DevOps de los agentes efímeros, la única solución que nos quedaba era la de tener un agente dentro de la red privada. Esto va desde una máquina virtual hasta un pod de Kubernetes, sea el modelo que sea y se hospede donde se hospede, necesitabas crear el agente previamente y asociarlo a un pool para poder usarlo.

    En honor a la verdad, existía alguna manera como cuenta Gisela Torres en su blog, que consistía en que tú mismo creases y destruyeses un contenedor en Azure Container Instances durante el pipeline.

    La idea de estos agentes efímeros de Azure DevOps es que puedas crear un agente de un solo uso como un contenedor de Azure Container Instances dentro de la red privada, y a diferencia de la aproximación que nos ofrecía Gisela, se destruya solo al terminar independientemente todo. Esto es especialmente útil ya que garantiza la optimización de costes al evitar posibles problemas que impidan la destrucción del contenedor una vez acabado el trabajo.

    Poniendo en marcha un agente efímero

    Vale, ya tenemos claro el concepto sobre cuándo y por qué utilizar un agente efímero para Azure DevOps. ¿Cómo lo ponemos en marcha?

    En primer lugar, hay que tener en cuenta que vamos a crear un agente en un contenedor Docker, por lo tanto, vamos a necesitar crear una imagen Docker y subirla a un repositorio. Para crear la imagen vamos a partir del Dockerfile base para agentes efímeros de Azure DevOps que podemos encontrar en su repositorio de Github. Estas imágenes base a diferencia de las habituales, añaden toda la infraestructura necesaria para parar el agente, desregistrarlo del pool, y borrar el contenedor automáticamente.

    Una vez que tenemos la imagen subida a un contenedor, vamos a crear un pool especifico que será donde se registren las instancias de los agentes a medida que se vayan creando. Para eso basta con ir de nuevo a la sección Agent pool de las propiedades de la organización o del proyecto, y pulsar sobre el botón de ‘añadir pool’ en la parte superior derecha. Esto nos mostrará una pequeña ventana donde elegiremos el nombre del pool. Una vez creado, hay que recordar el nombre ya que lo necesitaremos más adelante.

    Como último paso de configuración previa, vamos a tener que crear las dos conexiones de servicio que vamos a utilizar en caso de que no las tengamos ya, una será hacia el registro de imágenes de docker y la otra hacia la propia suscripción de Azure. Esto se puede conseguir desde el botón ‘nueva conexión de servicio’ en la sección de conexiones de servicio.

    La imagen señala la sección de conexiones de servicio y el botón de crear nueva conexión de servicio

    Aquí vamos a crear en caso de que no exista una conexión de tipo Azure Resource Manager hacia la suscripción y otra de Docker Registry hacia el registro donde está la imagen.

    También es necesario que tengamos un token que tenga permisos para operar el grupo de agentes. Este es el mismo requisito que si estuviésemos creando una agente de Azure DevOps normal y lo podemos conseguir siguiendo la documentación oficial.

    Por último pero no menos importante, vamos a necesitar instalar la tarea Ephemeral Pipelines Agents desde el marketplace. Una vez que lo tengamos, ya estamos listos para empezar a crear pipelines que utilicen agentes efímeros de Azure DevOps.

    Crear un pipeline que utilice el agente efímero

    Hemos llegado a la parte más fácil de todas, vamos a crear un pipeline multi etapa donde la primera etapa va a crear el agente efímero y la segunda va a realizar el trabajo dentro de nuestra red privada. Para eso basta con que creemos un pipeline con un yaml como este:

    trigger: none
    
    variables:
      # Nombre del grupo de recursos donde se creará el agente
      ResourceGroup: 'ephemeral-agents'
      # Nombre del grupo de recursos donde está la red privada
      vNetResourceGroup: 'ephemeral-agents'
      # Nombre de la red privada
      vnetName: private-network
      # Nombre de la subnet donde se conectará el agente
      subnetName: 'agents'
      # Localización
      location: 'West Europe'
      # Nombre del ACR
      acrName: fixedbuffer
      # Nombre de la imagen del agente
      imageName: $(acrName).azurecr.io/agent
      # Nombre de la conexión de servicio de ARM
      azureSubscription: 'Visual Studio Enterprise'
      # Nombre de la conexión de servicio de Docker Registry
      containerRegistryConnection: 'fixedbuffer'
      # Nombre del pool de agente
      agentpool: ephemeral
      # Token para el agente, puede ser $(System.AccessToken) si tiene suficientes permisos
      accessToken: 'accessToken'
    
    
    stages:
    - stage: PrepareAgent
      jobs:
      - job: PrepareAgent
        displayName: Prepare Agent
        pool: Hosted Ubuntu 1604
        steps:
        - task: AzureContainerAgentCreate@0
          displayName: 'Azure Container Create for pool ephemeral'
          inputs:
            azureSubscription: $(azureSubscription)
            resourceGroupName: $(ResourceGroup)
            location: $(location)
            azureDevOpsToken: $(accessToken)
            containerRegistry: $(containerRegistryConnection)
            imageName: $(imageName)
            agentPool: $(agentpool)
            agentPrefix: 'ephemeral-'
            vnetResourceGroupName: $(vNetResourceGroup)
            vnetName: $(vnetName)
            subnetName: $(subnetName)
    
    - stage: Deploy
      jobs:
      - deployment: Deploy
        environment: 'Production'
        displayName: Deploy Files
        pool: ${{variables.agentpool}}
        strategy:
          runOnce:
            deploy:
              steps:
              - script: echo 'Hello World'
    

    Sí todo va bien, cuando ejecutemos este pipeline debería crearse un agente en la suscripción el cual va a ejecutar el stage de Deploy (en este caso dirá Hello World) y una vez termine se borrará el agente.

    Conclusión

    La posibilidad de utilizar agentes efímeros en Azure DevOps simplifica enormemente la infraestructura necesaria para ciertos despliegues donde las redes privadas requerirían de levantar una máquina exclusivamente para esa labor. Pero ojo, no es oro todo lo que reluce, hay que evaluar si realmente compensa utilizar este modelo ya que, si por ejemplo hacemos muchos despliegues a día, o son despliegues que duran varias horas, quizás no sea una opción interesante a nivel de costes. En los proyectos en los que trabajo por ejemplo suele haber un cluster de kubernetes conectado a esa red privada y resulta mucho más interesante tener un pod desplegado dedicado a esa labor.

    Pese a todo, si se hacen despliegues una o dos veces al día y no son especialmente lentos, la opción de utilizar agentes efímeros puede ser una muy buena solución para evitar tener que preocuparse de mantener máquinas y librarse de algunos costes. Cuanto menos, son una herramienta muy interesante a tener en cuenta 🙂

    **La entrada Azure DevOps Ephimeral Agents: Agentes de usar y tirar se publicó primero en Fixed Buffer.**

    Una sinfonía en C#: Azure Devops: ¿Cómo saber el nombre de todas las variables y sus valores?

    $
    0
    0

    Si son como yo seguramente se confundirán todo el tiempo qué variable predefinda tiene qué valor, además de tener claro el valor en sí.

    Por ejemplo al crear un pipeline de build no recordar si Build.ArtificatsStagingDirectory es lo mismo que Agent.BuildDirectory, etc. más aún, si además las variables que nosotros definimos tienen el valor esperado.

    En este post vamos a ver un pequeño truco para averiguarlo en 2 minutos.

    PowerShell al rescate

    Además con este método vamos a saber cómo acceder a estas variables desde PowerShell, es decir, si hacemos un script (o inline) para que se ejecute como un step de un build o release podemos acceder a todas las variables.

    Las variables de Azure Devops son variables de entorno en la máquina que se ejecuta el agente.

    Al fin de cuentas cada variable de sistema de Azure Devops como las que definimos nosotros terminan llegando al agente como variables de entorno, y podemos acceder a ellas desde PowerShell o desde cualquier aplicación, solo tenemos que tener en cuenta algunas reglas.

    Conversiones de nombre

    • Primero, los nombres de todas las variables se convierten a mayúsculas.
    • Segundo, los . que separan (por ejemplo) Agent.BuildDirectory, se reemplazan por _

    Y listo, entonces System.ArtifiactsDirectory podemos encontrarlo como variable de entorno con el nombre SYSTEM_ARTIFACTSDIRECTORY

    Y cómo hacemos para averiguar todos los nombres y valores desde Azure Devops?

    Bien, el truco es simple, solo tenemos que crear un pipeline de build (o release, depende de qué variables queremos conocer) y agregamos una tarea de Powershell, elegimos que el Type del script sea “inline” y ponemos lo siguiente

    ls env:

    image

    Solo eso, y la ejecutamos (si es un build guardar y encolar, si es un release, guardar, generar release, encolar) y cuando finaliza miramos el log, y veremos todas las variables de entorno de la máquina del agente en cuestión y entre ellas las propias de Azure Devops.

    image

    Ah, y para leer variables de entorno (como por ejemplo las de Azure Devops sean predefinidas o las creadas por nosotros) es tan simple como:

    $variableValue = [System.Environment]::GetEnvironmentVariable("SYSTEM_DEFAULTWORKINGDIRECTORY")

    Y obtenemos el valor de System.DefaultWorkingDirectory en este caso

    Enjoy.

    Blog Bitix: Generar en el dominio los identificativos de las entidades aplicando DDD antes de persistirlas en la base de datos

    $
    0
    0

    Las bases de datos tiene la capacidad de generar identificativos para los datos que se insertan. En el caso de las bases de datos relacionales con secuencias que generan en el momento de inserción la clave primaria de la fila en una tabla, normalmente es un número y utilizando Java con JPA con las anotaciones Id, GeneratedValue y SequenceGenerator en la clase Java que representa a la entidad. Para Domain Driven Design delegar en el momento de inserción la generación del identificativo de la entidad es un problema ya que hace que la entidad sea inválida al no tener identidad hasta persistirla y la base de datos es un elemento externo que debe ser independizado del dominio de la aplicación. En este artículo comento una implementación siguiendo los principios de DDD para dar solución a estos dos problemas.

    Java

    Tradicionalmente la tarea de generar los identificativos de las entidades de dominio se delega en la base de datos en el momento de persistir la entidad. La base de datos en la columna de la tabla de la entidad para el identificativo generalmente es de tipo numérico y la base de datos le asigna un valor incremental para cada fila o entidad guardada.

    Este modelo de delegar en la base de datos el generar la identificativos de las entidades tiene dos problemas en la teoría de Domain Driven Design o DDD:

    • La aplicación requiere y es dependiente de un sistema externo para asignar la identidad de una entidad del dominio creada en la aplicación.
    • La entidad no tiene identidad inicialmente, lo que significa que la entidad es creada con un estado inválido por ser incompleto.

    Que la entidad no tenga identidad asignado y esté incompleta en el momento de creación tiene inconvenientes ya que al implementar los métodos equals y hashCode en Java para una entidad estos se basa en el identificativo de la entidad para determinar si dos instancias de un objeto es la misma, si la entidad no tiene identidad el método equals es ineficaz. Al mismo tiempo el método hashCode, y también el método equals, es utilizado por la API de colecciones de Java en su mayoría con lo que la entidad no es posible guardarla en colecciones que dependan de estos métodos para su correcto funcionamiento. Para usar los métodos equals y hashCode de las entidades es necesario esperar a guardar la entidad en la base de datos para que se le asigne el identificativo.

    También en DDD se suelen utilizar eventos como mecanismo de comunicar que en el sistema se ha sucedido algo, si la entidad no tiene identificativo no es posible comunicar que ha ocurrido algo, al menos no incluyendo el identificativo.

    Identificativos universales como identificadores

    Una posibilidad es generar identificativos universales para los identificativos de las entidades, sin embargo, la clase UUID depende de elementos externos al dominio como el tiempo del sistema. Al mismo tiempo la entidad no es consciente de la existencia de otras entidades y no le es posible determinar la unicidad del identificativo.

    En DDD todo elemento que dependa de algo externo ha de se independizado del dominio. De modo que el UUID aplicando DDD no se genera en la entidad sino en la capa de servicio mediante un elemento externo que en la terminología de DDD es un adaptador, el identificativo se le proporciona a la entidad en el momento de creación en el constructor como parámetro.

    1
    2
    3
    
    System.out.println(UUID.randomUUID().toString());...95ba87c1-f0ac-4c55-9efa-257dbe291a7d
    UuidGenerator.java

    Delegar la generación de identificativos en el repositorio

    Dado que en DDD se utiliza un repositorio para persistir las entidades en una base de datos externa a la lógica de dominio, la tarea de generar los identificadores que depende de un elemento externo es posible ubicarla en la misma clase repositorio, de esta manera la lógica queda con cohesión ya que todo lo relativo a la entidad está ubicada en su repositorio.

    Al mismo tiempo delegar la tarea de crear el identificativo en el repositorio permite variar la implementación, una opción es delegar en la base de datos la obtención del identificativo o utilizar el método de identificativo universal anterior. En el caso de delegar en la base de datos la generación del identificativo, es la base de datos la que lo genera igual que en el caso de la autogeneración pero ahora no de manera implícita sino de forma explícita.

    Ejemplo utilizando JPA y Spring Data

    Utilizando Spring Data con JPA para añadir métodos personalizados en la clase del repositorio hay que crear una interfaz que los incluya y construir una implementación de esa interfaz. La misma interfaz es implementada por la interfaz de Spring Data, y Spring haciendo su magia y por composición crea un repositorio que tiene tanto los métodos implementados por Spring como la implementación de los métodos personalizados, en este caso el de generar el identificativo.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    packageio.github.picodotdev.blogbitix.entitiesid.domain.product;importorg.springframework.data.jpa.repository.JpaSpecificationExecutor;importorg.springframework.data.jpa.repository.Modifying;importorg.springframework.data.jpa.repository.Query;importorg.springframework.data.repository.PagingAndSortingRepository;publicinterfaceProductRepositoryextendsPagingAndSortingRepository<Product,ProductId>,JpaSpecificationExecutor<Product>,CustomProductRepository{    @Override    @Modifying    @Query("delete from Product")    voiddeleteAll();}
    ProductRepository.java
    1
    2
    3
    4
    5
    6
    
    packageio.github.picodotdev.blogbitix.entitiesid.domain.product;publicinterfaceCustomProductRepository{    ProductIdgenerateId();}
    CustomProductRepository.java
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    packageio.github.picodotdev.blogbitix.entitiesid.domain.product;importorg.springframework.beans.factory.annotation.Autowired;importjava.math.BigInteger;importjavax.persistence.EntityManager;publicclassCustomProductRepositoryImplimplementsCustomProductRepository{    @Autowired    privateEntityManagerentityManager;    @Override    publicProductIdgenerateId(){        BigIntegerid=(BigInteger)entityManager.createNativeQuery("select nextval('product_id_seq')").getSingleResult();        returnProductId.valueOf(id);    }}
    CustomProductRepositoryImpl.java

    En la clase de la entidad no se usa la anotación GeneratedValue. En vez de esa anotación en este ejemplo se utiliza la anotación EmbeddedId y la anotación Embeddable, aplicando otro de los principios de DDD que es utilizar un tipo especico que representa el identificativo de la entidad en vez de un tipo proporcionado por el lenguaje como un Long o BigInteger. Un tipo específico para la identidad tiene varias ventajas como aprovechar los beneficios del compilador para detectar errores y de los IDE con asistencia de código.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    
    packageio.github.picodotdev.blogbitix.entitiesid.domain.product;importjava.math.BigDecimal;importjava.time.LocalDate;importjava.util.Objects;importjavax.persistence.EmbeddedId;importjavax.persistence.Entity;importjavax.persistence.Id;importjavax.persistence.Table;@Entity@Table(name="Product")publicclassProduct{    @Id    @EmbeddedId    privateProductIdid;    privateStringname;    privateLocalDatedate;    privateBigDecimalprice;    privateIntegerunits;    publicProduct(){    }    publicProduct(ProductIdid,Stringname,LocalDatedate,BigDecimalprice,Integerunits){        this.id=id;        this.name=name;        this.date=date;        this.price=price;        this.units=units;    }    ...    @Override    publicinthashCode(){        returnObjects.hash(id);    }    @Override    publicbooleanequals(Objecto){        if(this==o)            returntrue;        if(o==null)            returnfalse;        if(!(oinstanceofProduct))            returnfalse;        Productthat=(Product)o;        returnObjects.equals(this.id,that.id);    }}
    Product.java
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    
    packageio.github.picodotdev.blogbitix.entitiesid.domain.product;importjava.io.Serializable;importjava.math.BigInteger;importjava.util.Objects;importjavax.persistence.Embeddable;@EmbeddablepublicclassProductIdimplementsSerializable{    privateBigIntegerid;    protectedProductId(){    }    protectedProductId(BigIntegerid){        this.id=id;    }    ...    publicstaticProductIdvalueOf(BigIntegerid){        returnnewProductId(id);    }    @Override    publicinthashCode(){        returnObjects.hash(id);    }    @Override    publicbooleanequals(Objecto){        if(this==o)            returntrue;        if(o==null)            returnfalse;        if(!(oinstanceofProductId))            returnfalse;        ProductIdthat=(ProductId)o;        returnObjects.equals(this.id,that.id);    }}
    ProductId.java

    De esta forma ahora las entidades creadas son completamente válidas desde el momento de generación en el dominio ya que tienen su identificador. Dado que la entidad tiene su propio identificativo desde el inicio de su existencia es posible guardar la entidad en colecciones y lanzar eventos de dominio que incluyan su identificador sin tener que esperar que la base de datos le autogenere uno.

    En este caso de prueba se observa que la entidad Product creada se crea en el constructor con su identificativo asignado sin esperar a que la base de datos lo genere, la base de datos y JPA simplemente persisten el valor que tiene asignado.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    packageio.github.picodotdev.blogbitix.entitiesid.domain.product;...@SpringBootTest@ContextConfiguration(initializers={DefaultPostgresContainer.Initializer.class})publicclassProductRepositoryTest{    @Autowired    privateProductRepositoryproductRepository;    @Test    voidtestRepositoryGenerateId(){        // given
            ProductIdid=productRepository.generateId();        Productproduct=newProduct(id,"Raspberry Pi",LocalDate.now(),newBigDecimal("80.0"),10);        // and
            productRepository.save(product);        // then
            assertEquals(product,productRepository.findById(id).get());    }}
    ProductRepositoryTest.java

    En las trazas se observa la SQL para obtener el valor de la secuencia y la SQL de insert para guardar la entidad.

    1
    2
    3
    
    Hibernate: select nextval('product_id_seq')
    Hibernate: select product0_.id as id1_0_0_, product0_.date as date2_0_0_, product0_.name as name3_0_0_, product0_.price as price4_0_0_, product0_.units as units5_0_0_ from product product0_ where product0_.id=?
    Hibernate: insert into product (date, name, price, units, id) values (?, ?, ?, ?, ?)
    System.out
    Terminal

    El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
    ./gradlew test

    Blog Bitix: Comando para convertir una imagen SVG a PNG y JPG con diferentes tamaños y color de fondo con Inkskape

    $
    0
    0

    El formato de imagen SVG tiene la ventaja de permitir el escalado de la imagen sin pérdida de calidad y suele tener menor tamaño que la imagen equivalente en formato PNG y JPG. Si es necesario la imagen SVG es exportable a formato de imagen PNG y JPG con el tamaño deseado o color de fondo. El editor de imágenes Inkscape permite con su utilidad de línea de comandos automatizar y exportar archivos SVG a PNG y JPG y ocultar y mostrar las capas deseadas del archivo original para obtener el resultado deseado en la exportación.

    Inkscape

    GNU

    El formato de imagen SVG es un formato de imagen vectorial donde las líneas, formas, posición y colores se describen en formato texto, tiene la ventaja de ser un formato escalable que no pierde resolución independiente del tamaño de imagen en la que se represente, es decir, la imagen tiene la misma calidad al tamaño full hd 1920x1080 que a 4K 3840x2560 que en 800x600 píxeles.

    Con las imágenes de fotos en formato JPG y sin pérdida de calidad PNG la resolución adecuada para mostrar estas imágenes es la original del archivo a otra resolución hay que hacer un escalado con un algoritmo para añadir o quitar píxeles, el escalado es una operación imprecisa que resta algo de calidad a la imagen. Escalar el tamaño de una imagen JPG o PNG es necesario para obtener la imagen en otros tamaños, dependiendo del número de píxeles a añadir si se hace más grande que la original o píxeles a quitar si se reduce el tamaño la pérdida de calidad se nota más o menos.

    Imagen en formato JPG originalImagen en formato JPG escalada a 300x200 píxeles

    Imagen en formato JPG original y escalada a 300x200 píxeles

    Los navegadores y dispositivos móviles ya soportan como formato de imagen el SVG, en la web y los dispositivos móviles es especialmente adecuado este formato ya que además de adaptarse a la variedad de tamaños de los dispositivos de escritorio o móviles suelen tener un menor tamaño de archivo lo que hace que se descarguen más rápido al requerir menos ancho de banda.

    Aún con los beneficios que posee el formato SVG algunas aplicaciones no soportan el formato SVG y en este caso es necesario hacer una conversión de SVG a los formatos binarios rasterizados PNG o JPG. El formato SVG permite obtener estas imágenes PNG y JPG en diferentes tamaños sin pérdida de calidad.

    Inkscape es un editor de imágenes vectoriales con una utilidad de línea de comandos que permite convertir y exportar imágenes en formato SVG a PNG y JPG en el tamaño y con el color de fondo deseado. El siguiente comando convierte todos los archivos SVG a PNG de una carpeta. En el comando se indican varios parámetros como la anchura deseada de la imagen, el color de fondo, los identificativos de las capas a exportar, el formato de salida y el nombre del archivo creado. Posteriormente con un segundo comando hay que convertir las imágenes de formato PNG a JPG, dependiendo del tipo de imagen, los colores y degradados de la imagen el tamaño en formato PNG será mayor o menor que en formato JPG.

    1
    2
    
    $ for f in *.svg;do inkscape -w 750"$f" --export-background white --export-background-opacity 1 --export-type png --export-filename "$(basename ${f%.*})-750.png";done;
    $ for f in *.png;do convert "$f""${f%.*}.jpg";done;
    inkscape-convert-svg-png.sh

    Imagen original en formato SVG

    Imagen original en formato SVG

    El editor Inkscape permite definir capas con diferentes elementos de la imagen, la linea de comandos permite exportar únicamente capas deseadas de la imagen para obtener el resultado deseado en la exportación. El SVG anterior contiene en el mismo archivo diferentes capas con diferentes versiones de la imagen adecuadas para un fondo claro y oscuro.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    BASENAME="apache-tapestry"ARTWORK_FILE="$BASENAME-artwork.svg"
    
    $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-icontext-800-light.png"
    $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-icontext-800-dark.png"
    $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightIconObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-icon-800-light.png"
    $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkIconObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-icon-800-dark.png"
    $ inkscape "$ARTWORK_FILE" --export-id-only --export-id lightTextObject --export-background white -w 800 --export-type png --export-filename "$BASENAME-text-800-light.png"
    $ inkscape "$ARTWORK_FILE" --export-id-only --export-id darkTextObject --export-background black -w 800 --export-type png --export-filename "$BASENAME-text-800-dark.png"
    inkscape-png-versions.sh

    Imagen en formato PNGImagen en formato PNG

    Imagen en formato PNGImagen en formato PNG

    Imagen en formato PNGImagen en formato PNG

    Diferentes versiones de la imagen SVG en formato PNG

    También es posible modificar el SVG original para mostrar y ocultar las capas visibles del archivo. Los siguientes comandos permiten exportar a PNG la imagen en diferentes versiones (icono y texto, solo icono o solo texto), con diferente color de fondo (transparente, blanco y negro) y en diferente tamaño. Esto permite automatizar y hacerlo mucho más rápido que el repetitivo proceso que sería realizar manualmente la exportación usando la interfaz gráfica de Inkscape.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    BASENAME="apache-tapestry"ARTWORK_FILE="$BASENAME-artwork.svg"
    
    $ (cp "$ARTWORK_FILE""$BASENAME-icon-light.svg"&& inkscape "$BASENAME-icon-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
    $ (cp "$ARTWORK_FILE""$BASENAME-icon-dark.svg"&& inkscape "$BASENAME-icon-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
    $ (cp "$ARTWORK_FILE""$BASENAME-text-light.svg"&& inkscape "$BASENAME-text-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb EditDeselect --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
    $ (cp "$ARTWORK_FILE""$BASENAME-text-dark.svg"&& inkscape "$BASENAME-text-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
    $ (cp "$ARTWORK_FILE""$BASENAME-icontext-light.svg"&& inkscape "$BASENAME-icontext-light.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
    $ (cp "$ARTWORK_FILE""$BASENAME-icontext-dark.svg"&& inkscape "$BASENAME-icontext-dark.svg" --verb LayerHideAll --verb DialogLayers --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerPrev --verb LayerToggleHide --verb FitCanvasToDrawing --verb FileSave --verb FileQuit)
    inkscape-svg-versions.sh

    Imagen en formato SVGImagen en formato SVG

    Imagen en formato SVGImagen en formato SVG

    Imagen en formato SVGImagen en formato SVG

    Diferentes versiones de la misma imagen SVG

    Blog Bitix: Las sentencias de control de flujo en Java (if, switch, for, while, do-while, try-catch, break, continue e invocación)

    $
    0
    0

    El lenguaje de programación Java utiliza un paradigma orientado a objetos pero también emplea otros paradigmas como el funcional con la incorporación de las lambas en Java 8 y el imperativo en los bloques de sentencias de los métodos. En este artículo están los tipos de sentencias de control de flujo disponibles en el lenguaje de programación Java: condicionales, de repetición, de asignación, de gestión de excepciones e invocación de métodos en Java.

    Java

    Java es un lenguaje orientado a objetos donde aplicando los principios de encapsulación, herencia y polimorfismo, el código está dentro de los métodos de las clases de los objetos. El código dentro de los métodos sigue los principios de los lenguajes imperativos con una secuencia de sentencias de asignación, control de flujo, llamada a otros métodos y de repetición, más recientemente con la incorporación de las lambdas y referencias a métodos en Java 8 es posible emplear también un enfoque de programación funcional.

    La orientación a objetos es una forma de organizar el código y los datos que maneja ese código de modo que se respeten los principios de encapsulación, una forma de reutilizar el código con herencia y una forma de abstraer el comportamiento dependiendo de la clase concreta que implementa el método. Pero el código de Java no es únicamente código orientado a objetos también es un lenguaje imperativo para el código de los métodos.

    El código imperativo se basa en la ejecución de forma secuencial de un conjunto de sentencias. Las sentencias de un método o programa son de diferente tipo: de asignación, condicionales, de repetición, de gestión de excepciones para controlar errores y de llamadas a funciones, en el caso de los lenguajes orientados a objetos llamadas a métodos. Estas sentencias individuales se pueden anidar unas dentro de otras por ejemplo tener una sentencia de repetición dentro del bloque de sentencias de una condicional.

    Cada uno de estos tipos de sentencias forman las piezas básicas de construcción de los programas, combinadas en múltiples lineas de código forman programas complejos que sirven para el propósito para que el programa fue escrito.

    Sentencias de asignación

    Las sentencias de asignación sirven para asignar nuevos valores y referencias a objetos a variables y propiedades de objetos. La sintaxis de la asignación consta del nombre de variable que toma el valor a la izquierda, el operador de asignación en el medio y de la expresión a la derecha. El valor de una variable o propiedad cambia con una sentencia de asignación, el valor anterior se reemplaza por el nuevo valor. El nuevo valor de la variable es el resultado de evaluar la expresión que proporciona el valor, con una asignación de inicialización es posible asignar un valor al mismo tiempo que se declara una variable o propiedad de un objeto.

    Una variable tiene un valor si se trata de un tipo primitivo de datos, en caso de tener como tipo una clase contiene una referencia a una instancia de un objeto de ese tipo o la referencia nula. Una expresión devuelve como resultado un valor y este es el asignado a la variable por la sentencia de asignación.

    Una expresión puede contener múltiples operadores: para datos booleanos (de lógica &&, ||, ! y de comparación ==, !=, <, >, <=, >=), aritméticos para datos numéricos (+, -, *, /, %, ++, –) o operadores para datos binarios (&, |, ^, ~, «, », »>). Otros operadores de asignación (+=, -=, *=, /=, %=, «=, »=, &=, ^=, |=) toman como primer operando el valor de la variable, esto evita repetir el nombre de la variable en la expresión y facilita la legibilidad del código.

    1
    2
    3
    4
    5
    6
    7
    
    variable=expresión;intx=1;Listlist=List.of("1","2");x=2;list=List.of("3","4");
    Asignacion.java

    El operador ternario ?: es una expresión condicional que devuelve el valor de la expresión según el resultado de evaluar una expresión booleana.

    1
    
    variable=(condicionExpresionBoleana)?expresionTrue:expresionFalse;
    CondicionalTernario.java

    Setencias condicionales (if, switch)

    Las sentencias condicionales son un tipo de sentencia que evalua una expresión booleana y dependiendo de su valor verdadero o falso ejecuta o no su su bloque de sentencias asociado para cada caso. Las sentencias a continuación de la condición se ejecutan si la sentencia if se evalúa como verdadero. La sentencia if además puede tener otro bloque de sentencias a ejecutar si la expresión booleana se evalúa como falso, el bloque de sentencias else. Las sentencias if y else se pueden encadenar.

    Diagrama sentencia ifDiagrama sentencia if-else

    Diagramas sentencias if e if-else
    Fuente: beginnersbook.com
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    
    if(condicionExpresionBoleana)sentenciaTrueif(condicionExpresionBoleana){bloqueSentenciasTrue}if(condicionExpresionBoleana){bloqueSentenciasTrue}else{bloqueSentenciasFalse}if(condicionExpresionBoleana){bloqueSentenciasTrue}elseif(condicionExpresionBoleana){bloqueSentenciasTrue}else{    bloqueSentenciasFalse}inta=3;intb=2;if(a>b){    System.out.println("a es mayor que b");}else{    System.out.println("a no es mayor que b");}
    CondicionalIf.java

    Cuando una sentencia if tiene muchas ramas y la expresión condicional comprueba en todos los casos diferentes valores de una misma variable se utiliza la sentencia switch. Si el valor de la variable coincide con el valor del bloque del caso se ejecutan las sentencias de ese bloque. Cada bloque ha de estar finalizado con sentencia break para no evaluar las sentencias del siguiente bloque. El caso default permite ejecutar un bloque de sentencias si el valor de la expresión no coincide con ninguno de los valores de los casos, siendo como la parte else de las sentencias if.

    Diagrama sentencia switch

    Diagrama sentencia switch
    Fuente: beginnersbook.com
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    switch(variable){case1:{       bloqueSentencias}break;case2:{       bloqueSentenciascase3:{       bloqueSentencias}break;default:{       bloqueSentencias}}
    CondicionalSwitch.java

    La sentencia if equivalente del switch anterior sería el siguiente. si es posible se prefiere usar la sentencia switch sobre la if equivalente, más si hay un varias ramas, ya que es mas sencilla, legible y no hace falta indicar en cada expresión de condición la expresión que devuelve el valor.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    if(variable==1){bloqueSentencias}elseif(variable==2){bloqueSentencias}elseif(variable==3){bloqueSentencias}else{bloqueSentencias}
    CondicionalSwitchIf.java

    Las sentencias if se pueden anidar unas dentro de otras esto dificulta la legibilidad del código, para evitar múltiples anidaciones y crear varias ramas se utilizan guard clauses que simplifican el flujo del programa.

    Sentencias de repetición (for, foreach, while, do-while, break, continue)

    Las sentencias de repetición permiten ejecutar un bloque de sentencias durante un número determinado de veces o mientras se cumpla una condición. En cada iteración después de ejecutar el bloque de sentencias la condición se vuelve a evaluar si se sigue cumpliendo, si se cumple se realiza una nueva iteración si no se cumple se sale del bucle y se continua con la siguiente sentencia del programa. Esta evaluación de la condición y ejecución del bloque de sentencias se realiza hasta que la condición del blucle while no se cumpla. Hay varios tipos de bucles.

    La sentencia while ejecuta un bloque de sentencias mientras se cumpla una condición, puede ocurrir el caso de que la condición de la sentencia while no se cumpla y por tanto el bloque de sentencias de repetición no se ejecute ninguna vez. La comprobación de la condición se realiza antes de entrar al bucle.

    Diagrama sentencia while

    Diagrama sentencia while
    Fuente: beginnersbook.com
    1
    2
    3
    
    while(condicionExpresionBoleana){sentences}
    RepeticionWhile.java

    En el bucle do-while la comprobación de la condición está después del bloque de sentencias de repetición, a diferencia del bucle while en el do-while el bloque de sentencias se ejecutan al menos una vez.

    Diagrama sentencia do-while

    Diagrama sentencia do-while
    Fuente: beginnersbook.com
    1
    2
    3
    
    do{bloqueSentencias}while(condicionExpresionBoleana);
    RepeticionDoWhile.java

    La sentencia for utilizan otra sintaxis para realizar bucles, una de las 4 formas de hacer un bucle for contiene una inicialización, condición de repetición e incremento además del bloque de sentencias a ejecutar. Otras formas de bucle for son el forearch para colecciones de objetos.

    Diagrama sentencia for

    Diagrama sentencia for
    Fuente: beginnersbook.com
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    for(inicializacion;condicionExpresionBoleana;incremento){bloqueSentencias}for(inti=0;i<10;++i){System.out.println(i);}// Foreach
    Collection<Object>collection=...;for(Objectobject:collection){System.out.println(object);}
    RepeticionFor.java

    Dentro de las sentencias de bucle se pueden emplear las palabras reservadas break y continue. La sentencia break permite salir del bucle inmediatamente sin necesidad de evaluar la condición. La palabra continue dejar de ejecutar sentencias del bucle y evaluar de nuevo la condición de bucle, si se sigue cumpliendo la condición se ejecuta de nuevo el bloque de sentencias. Las sentencias break y continue normalmente se utilizan dentro de una sentencia condicional if.

    Diagrama sentencia continue

    Diagrama sentencia continue
    Fuente: beginnersbook.com
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    // Break
    for(inti=0;i<10;++i){if(i==5){       break;}System.out.println(i);}// Resultado
    01234// Continue
    for(inti=0;i<10;++i){if(i==5){       continue;}System.out.println(i);}// Resultado
    012346789
    BreakContinue.java

    Un bucle infinito es en un bucle que se itera contnuamente porque la condición de iteración se cumple siempre. Ejecutar continuamente un bloque de sentencias hace que el procesador consuma todos los recursos que se disponen de cómputo de procesador o una alta actividad de entrada y salida que degrada el rendimiento del sistema sin producir ningún resultado útil cuanto menos si no genera errores en el resto de programas del sistema. Suele ser por un error de programación y para resolverlo habitualmente hay que matar el proceso del programa y reiniciarlo, si no se corrige el error en el bucle en las mismas condiciones se producirá de nuevo el bucle infinito.

    Setencias de control de expceciones (try-catch, throw)

    Las expresiones try-catch son el mecanismo de control de errores en Java. Estas expresiones permiten tratar las excepciones lanzadas por la palabra reservada throw en los métodos invocados de su bloque de sentencias.

    1
    2
    3
    4
    5
    
    try{bloqueSentencias}catch(Exceptione){bloqueSentenciasTratamientoExcepcion}
    TryCatch.java

    Las excepciones se lanzan con la palabra reservada throw, toda excepción ha de heredar de Exception y si no hereda de RuntimeExecption ha de declararse en la firma del método para indicar que el método puede lanzar esa excepción en caso de no ser tratada dentro del mismo método con un try-catch.

    1
    2
    3
    4
    5
    6
    7
    
    voidexception()throwsException{    thrownewException();}voidruntime(){    thrownewRuntimeException();}
    Throw.java

    Invocación de métodos

    Las funciones en los lenguajes orientados a objetos dentro de las clases, las clases encapsulan las variables y las funciones o métodos. Los métodos tiene acceso además de a los parámetros que recibe a las variables del objeto en las están contenido y otros métodos de la misma clase u otros objetos respetando los ámbitos de visibilidad de las palabras reservadas public, protected, private y default.

    Las expresiones de invocación a métodos se componen del objeto que recibe la llamada a uno de sus métodos separado por un punto y nombre del método. Si el método llamado devuelve un objeto se puede encadenar otra nueva llamada a un método del objeto devuelto. El valor o referencia a objeto devuelto se puede asignar a una variable también.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    Stringstring="Hola Mundo!";System.out.println(string);// Resultado
    HolaMundo!string=string.toUpperCase();System.out.println(string);// Resultado
    HOLAMUNDO!
    Invocacion.java

    Bitácora de Javier Gutiérrez Chamorro (Guti): Casio necesita un buen software y el desastre de G-Shock Connected

    $
    0
    0


    En el artículo ¿El futuro de los relojes digitales? ya referenciaba las deficiencias del software G-Shock Connected escrito por Casio Computer Co. Ltd. Un aspecto que ya había mencionado en las pruebas de los Casio G-Shock GMW-B5000D, GW-B5600 y sobre todo del GPR-B1000 «Rangeman». No importa si hablamos de plataforma iOS como Android, en ambas …

    Casio necesita un buen software y el desastre de G-Shock Connected Leer más »



    Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

    Variable not found: Enlaces interesantes 409

    $
    0
    0
    Enlaces interesantes

    Usado frecuentemente en las respuestas a peticiones de tipo PUT, el código de estado HTTP 409 es retornado por el servidor para indicar que existe un conflicto con el estado actual del recurso destino de la petición, esperando que el cliente solucione el problema y reintente la operación. Como ayuda, en el cuerpo de la respuesta suele incluirse información que facilitaría al cliente la resolución del conflicto.

    Un ejemplo podría ser cuando intentamos actualizar con PUT una entidad con un número de versión posterior al que estamos enviando, lo que indicaría que otra petición distinta modificó el recurso antes que nosotros.

    Y ya sin conflictos, van los enlaces recopilados durante la semana pasada que, como no podía ser de otra forma, espero que os resulten interesantes :-)

    Por si te lo perdiste...

    .NET Core / .NET

    ASP.NET Core / ASP.NET

    Azure / Cloud

    Conceptos / Patrones / Buenas prácticas

      Web / HTML / CSS / Javascript

      Visual Studio / Complementos / Herramientas

      Xamarin

      Otros

      Publicado en Variable not found.

      Variable not found: Cómo invocar métodos estáticos C# desde Javascript con Blazor (interop 2/3)

      $
      0
      0
      Blazor
      Hace poco veíamos mecanismos de interop que permitían invocar código Javascript desde Blazor, con objeto de poder seguir utilizando código existente en bibliotecas o componentes, ya sean nuestros o de terceros.

      Pero Blazor también aporta mecanismos para la interoperación en sentido contrario, es decir, permitir que desde código Javascript podamos invocar métodos escritos en C#, ya sea con Blazor Server o Blazor WebAssembly.

      De hecho, Blazor permite invocar métodos tanto estáticos como de instancia, aunque en este post nos centraremos sólo en los primeros porque son más sencillos de entender. En un artículo posterior profundizaremos en el segundo escenario.

      Invocando métodos estáticos escritos en C# desde Javascript

      Para que un método estático escrito en C#, ya sea utilizando Blazor Server o Blazor WebAssembly, pueda ser accesible desde Javascript, éste debe estar decorado con el atributo [JSInvokable]. El método puede encontrarse en el interior de una página, en una clase personalizada o donde queramos; siempre que esté decorado de esa forma, Blazor podrá localizarlo.

      El siguiente método podría valer como ejemplo:
      public class Calculator
      {
      [JSInvokable]
      public static int Sum(int a, int b)
      {
      return a + b;
      }
      }
      Para ejecutar este método desde Javascript, podríamos hacerlo de forma muy sencilla utilizando el método invokeMethodAsync() del objeto DotNet, un proxy proporcionado en el lado cliente por el framework:
      var sum = await DotNet.invokeMethodAsync("BlazorJsDemo", "Sum", 1, 2);
      alert(sum);
      invokeMethodAsync() retorna un objeto Promise, por lo que puede esperarse su retorno utilizando await o bien mediante el clásico then():
      DotNet.invokeMethodAsync("BlazorJsDemo", "Sum", 1, 2).then(sum => alert(sum));
      En cualquier caso, el primer parámetro de invokeMethodAsync() indica el nombre del ensamblado donde se encuentra el método a invocar, que normalmente coincidirá con el nombre del proyecto.
      Seguidamente hay que especificar el nombre del método, y luego los parámetros a enviarle.

      Observad que esto podría crear problemas de ambigüedad si tenemos varios métodos estáticos invocables con el mismo nombre dentro del mismo ensamblado. En este caso, la llamada anterior fallará en tiempo de ejecución si existe un método Sum() invocable en cualquier otra clase del ensamblado BlazorJsDemo, mostrando por consola un error como el siguiente:
      blazor.server.js:8 Uncaught (in promise) Error: System.InvalidOperationException: 
      The assembly 'BlazorJsDemo' contains more than one [JSInvokable] method with identifier 'Sum'.
      All [JSInvokable] methods within the same assembly must have different identifiers.
      You can pass a custom identifier as a parameter to the [JSInvokable] attribute.
      Para solucionarlo, podemos emplear el parámetro identifier del atributo [JSInvokable] y así indicar un identificador distinto en uno de los métodos:
      [JSInvokable("Add")]
      public static int Sum(int a, int b)
      {
      return a + b;
      }
      Los métodos pueden recibir y retornar cualquier tipo de datos, pues Blazor gestionará automáticamente la serialización y deserialización. Y también pueden ser asíncronos, como en el siguiente ejemplo:
      public static async Task<ProcessResult> DoSomethingComplex()
      {
      await Task.Delay(5000); // Simulate a hard job
      return new ProcessResult() { ... };
      }

      ¡Ojo al momento de ejecución!

      Un aspecto sumamente importante a la hora de hacer invocaciones de este tipo es que tenemos que asegurar que el framework Blazor haya sido inicializado previamente en la página, pues de lo contrario encontraremos errores como el siguiente en la consola del navegador:
      Blazor: No .NET call dispatcher has been set
      Esto normalmente no ocurrirá porque Blazor es inicializado en cuanto carga la página, pero si quisiéramos hacer una invocación muy temprana podríamos utilizar un código como el siguiente en Blazor WebAssembly:
      <!-- File: wwwroot/index.html -->
      ...
      <script src="_framework/blazor.webassembly.js" autostart="false"></script>
      <script>
      Blazor.start({})
      .then(async () => {
      // Blazor was initialized
      var sum = await DotNet.invokeMethodAsync("BlazorJsDemo", "Sum", 1, 2);
      alert(sum);
      });
      </script>
      Y el código equivalente en Blazor Server:
      ...
      @* File: Pages/_Host.cshtml *@
      <script src="_framework/blazor.server.js" autostart="false"></script>
      <script>
      Blazor.start({})
      .then(async () => {
      // Blazor was initialized
      var sum = await DotNet.invokeMethodAsync("BlazorJsDemo", "Sum", 1, 2);
      alert(sum);
      });
      </script>
      En ambos casos, fijaos que lo que hacemos es cargar los scripts propios de Blazor pero estableciendo el atributo autostart a false para que el framework no arranque automáticamente.

      Justo después, llamamos a Blazor.start() para arrancarlo manualmente, pero utilizamos then() para tomar el control en el momento en que finaliza su inicialización.

      A partir de ese punto ya podremos considerar Blazor cargado, por lo que podremos invocar métodos estáticos sin problema.

      Publicado en Variable not found.

      Picando Código: Ex-Zodiac – Shooter 3D con gráficos poligonales inspirado en los clásicos de los 90’s ahora en Kickstarter

      $
      0
      0

      Hace un tiempo escribía en el blog sobre Ex-Zodiac, un juego inspirado en Star Fox original escrito en Godot. Desde entonces he ido siguiendo su desarrollo y estoy muy contento de ver que hay un plan para completar su desarrollo y financiarlo a través de Kickstarter.

      Ex-Zodiac

      Ex-Zodiac es un juego shooter con gráficos estilizados que evocan juegos 3D de principios de los 90’s. La protagonista Kyuu lucha para liberar los mundos del Sistema Estelar Sanzaru, invadido por la organización terrorista intergaláctica conocida como Zodiac. Ya hay un demo disponible que podemos descargar en Steam, y en lo que va de desarrollo se puede ver el potencial y lo divertido que viene siendo el juego.

      Características generales:

      -- Estilo visual retro, colorido y con pocos polígonos emulando los juegos de la época.
      -- 12 niveles principales (más áreas secretas y caminos alternativos).
      -- Varias rutas para completar el juego.
      -- Jefes gigantes al final de cada nivel, cada uno pilotado por un miembro de Zodiac.
      -- Banda sonora estilo 16-bit por +TEK combinando FM y síntesis wavetable.

      Su desarrollador es Ben Hickling quien viene trabajando en él hace más de 2 años en su tiempo libre. El objetivo del Kickstarter es que pueda dedicarle más tiempo al desarrollo y terminarlo. Va a estar disponible inicialmente en Linux, Mac y Windows por medio de Steam, Humble e Itch.io. Dentro de las recompensas del Kickstarter además de obtener el juego, podemos obtener el soundtrack por medio de Steam o Bandcamp, y un montón de extras más.

      Pixeljam va a estar ayudando con el marketing, community management y asegurarse que la campaña en Kickstarter cumpla con sus promesas. La empresa ha creado, consultado en y publicado casi 30 juegos desde 2005, siendo Dino Run y Nova Drift sus títulos más conocidos.

      La campaña recién empezó y ya alcanzó la meta inicial de USD 25.190. Inicialmente no hay “stretch goals” planeados, con la idea de que todo el dinero recaudado por encima de la meta inicial va a ser volcado en “más juego”. Tampoco hay muchas recompensas extra, todo el contenido es digital, y el desarrollador se puede concentrar simplemente en desarrollar y terminar el juego, y no preocuparse por producir y entregar recompensas físicas. Tras haber sido quemado más de una vez por Kickstarters que prometen demasiado y terminan sin entregar nada o un producto mediocre, me parece excelente mantener expectativas y metas medidas para poder cumplirlas.

      Una buena noticia es que está en la mira la posibilidad de portar el juego a Nintendo Switch. Como se encuentra en una etapa muy temprana de desarrollo, no prometen nada. Pero una vez publicado el juego, van a tener una mejor idea de lo que se necesita para publicarlo en Switch. Cuentan con la experiencia y recursos de Pixeljam para esto.

      Visita el Kickstarter

      YouTube Video

      Bitácora de Javier Gutiérrez Chamorro (Guti): Feed en tiempo real del Instagram de @Guardatiempo

      $
      0
      0


      Cuando en publiqué Fotos de relojes en el Instagram de Guardatiempo en el que hacía referencia a la cuenta de Instagram @guardatiempo, dejé pendiente un feed en tiempo real de las últimas publicaciones. Una forma en que no hiciera falta acudir a la red social para visualizar las últimas fotografías… ¡Y finalmente, aquí lo tenéis!

      Artículo publicado originalmente en Bitácora de Javier Gutiérrez Chamorro (Guti)

      Blog Bitix: Desempaquetado y análisis de altavoz inteligente Amazon Echo con asistente Alexa

      $
      0
      0

      El altavoz es un dispositivo que también ha añadido funciones de dispositivo inteligente, al igual que ha ocurrido con los teléfonos móviles y televisores. Son controlados por voz y con la aplicación para smartphone capaces de leer noticias, el tiempo, el tráfico, música, radios o de servicios en streaming, el centro para controlar dispositivos del hogar y muchas otras funcionalidades agregables mediante skills. Amazon, Goolge y Apple tiene su versión de altavoz inteligente, pequeños con buena calidad de sonido y diferentes modelos entre los que elegir.

      Amazon

      Los altavoces que tengo los compré con el primer ordenador, el venerable AMD Athlon XP 1800+, pagado con el dinero de mi primer trabajo en el 2004, unos Creative Fourpointsurround FPS 1600 4.1. Unos altavoces con uno dedicado a los graves o subwoofer con a mi entender una muy buena calidad de sonido y potencia. Durante esta casi una década y media he estado muy contento con ellos, hasta ahora que he decidido buscar una alternativa por un problema que llevo arrastrando no desde hace días, semanas o meses sino ya algunos años.

      El problema de mis altavoces

      El problema es que aleatoriamente en ciertos periodos de tiempo cada pocos minutos el volumen sube y al cabo de un tipo vuelve a bajar, en algunas ocasiones aguanta un cuarto de hora o media hora sin variación de volumen. Para ver vídeos donde alguien está hablando y algunos sonidos no se nota pero escuchando música sí, la diferencia entre el tono alto y bajo tiene suficiente diferencia como para que sea molesto, con este problema es difícil ajustar el volumen y en algunas ocasiones requiere ajustarlo desde el mando de control y desde el volumen del entorno de escritorio del ordenador.

      El problema tiene que estar en la placa alojada en el altavoz de graves o en el potenciómetro del control de volumen que tiene. He abierto el subwoofer y la placa electrónica tiene numerosos condensadores electrolíticos pero ninguno presenta signos de estar claramente dañado, normalmente los condensadores eléctricos en mal estado se abomban en la parte superior y en los más dañados el líquido sale que al secarse genera alrededor del condensador como una arenilla.

      Seguramente los altavoces tengan arreglo pero no sé lo suficiente ni tengo las herramientas como para arreglarlos, para cambiar el potenciómetro necesitaría comprar un soldador, estaño, desoldador y un potenciómetro sustituto para el actual. Comprar el material para intentar arreglarlos sin ninguna garantía de repararlos viene siendo algo similar a comprar unos altavoces nuevos, y al final es lo que he hecho buscar unos sustitutos.

      Me dará pena tener que sustituirlos pero para escuchar música de forma cómoda no encuentro otra opción.

      Los altavoces sustitutos candidatos

      Cuando compro algo electŕonico u algún otro producto que más o menos entiendo para saber las diferencias entre un producto y otro normalmente me informo lo mejor que puedo hasta decidirme por la opción que mejor considero evaluando el precio y características de cada opción. Los artículos de las series de desempaquetado de tecnología y desempaquetado de productos no tecnológicos tienen algunos ejemplos que incluyen un Intel NUC, una PlayStation 4 o un termo de agua sanitaria Fleck o el contrato con la compañía eléctrica Holaluz.

      En esta ocasión no he evaluado muchas opciones ni he analizado en detalle todas las opciones. Sé que quería unos altavoces con una calidad como los que tenía ya que unos de los principales uso que les doy es escuchar música, esto incluye casi por necesidad que tengan altavoz de graves. Otro requerimiento es que no fuesen muy grandes y no tuviesen muchos cables, los que tenía eran 4.1 pero siempre los he usado como 2.1 por imposibilidad en mi habitación de colocar los traseros como se debiera. Un número grande de altavoces requiere muchos cables que hay que ordenar.

      Con estas opciones he buscado las primeras opciones que ofrece Amazon al buscar por altavoces y similares búsquedas (altavoces subwoofer, altavoces 2.1). Unos de la propia marca Amazon Basics, algunas otras opciones similares a los Creative en diferentes rangos de precio.

      Los básicos.

      Los similares a los que tenía.

      En el momento de buscar estas opciones y un poco lo que me ha decidido a comprarlos es que los altavoces inteligentes de Amazon estaban de oferta, entre los varios que tiene está los altavoces inteligentes Echo con asistente Alexa, bastante pequeño y compacto, con pocos cables y con buena calidad de sonido al tener altavoz de graves, mucho mejor que la de los propios altavoces del monitor o de un portátil, al menos la suficiente para mi.

      Finalmente, me he decidido por el Amazon Echo de tercera generación, por precio está en la categoría de altavoces de gama media-alta de las que he encontrado pero para lo que lo quería y este tiene es la que elegido. Mi uso será principalmente como altavoz para el ordenador y la PlayStation, posiblemente no use muchas de sus opciones como altavoz inteligente pero otras es posible que sí como la posibilidad de escuchar música de Amazon Music o la radio sin necesidad de tener el ordenador encendido o como hilo musical al dormir. Lo que pierdo es el sonido estéreo pero recupero una buena cantidad de espacio en la mesa y quito un par de cables.

      Desempaquetado de Amazon Echo (3ª generación)

      El color que he elegido ha sido el negro o antracita, otros colores son azul o añil, gris claro y gris oscuro. Es bastante compacto, las dimensiones son de 14,5 cm de alto y 9,5 cm de diámetro, requiere alimentación eléctrica e incluye un pequeño cargador rectangular a juego con el color del altavoz también de color negro.

      La caja incluye un pequeño manual de puesta en funcionamiento y configuración.

      Desempaquetado Amazon EchoDesempaquetado Amazon EchoDesempaquetado Amazon Echo

      Desempaquetado Amazon EchoDesempaquetado Amazon EchoDesempaquetado Amazon Echo

      Desempaquetado Amazon EchoDesempaquetado Amazon Echo

      Desempaquetado Amazon EchoDesempaquetado Amazon EchoDesempaquetado Amazon Echo

      Desempaquetado Amazon Echo

      Primeros pasos, funcionamiento y privacidad

      La configuración consiste básicamente en hacer que el altavoz Echo se conecte a la red WiFi de nuestra casa. Para hacer la configuración es necesario utilizar un smartphone y descargar e instalar la aplicación de Alexa para el móvil ya sea Android o iOS de sus tiendas de aplicaciones, en el proceso también se requiere activar la conexión Bluetooth en el móvil y conectarse al dispositivo Echo.

      Cuando se compra en Amazon se ofrece la posibilidad de preconfigurarlo para que se conecte a la WiFi automáticamente si ya se tiene otro dispositivo. Para hacer la configuración manualmente en la aplicación de Alexa del móvil es añadir el nuevo dispositivo y seguir los pasos, poner el altavoz en modo configuración si es necesario pulsado el botón de acción durante 6 segundos, se encenderá el anillo de luz con color naranja. Hay que buscar la red WiFi por el nombre e introducir su contraseña, si la WiFi está oculta hay que introducir además su nombre.

      Configuración Amazon Echo

      Configuración Amazon EchoConfiguración Amazon EchoConfiguración Amazon Echo

      Configuración Amazon EchoConfiguración Amazon EchoConfiguración Amazon Echo

      Configuración Amazon EchoConfiguración Amazon EchoConfiguración Amazon Echo

      Configuración Amazon EchoConfiguración Amazon EchoConfiguración Amazon Echo

      Configuración Amazon Echo

      La aplicación permite configurar el volumen, el ecualizador según las preferencias y el modo de entrada o salida de la clavija jack 3.5mm. En mi caso tengo el ordenador conectado por HDMI al monitor, por el mismo HDMI va la señal de imagen para el monitor y de sonido para los altavoces, desde el monitor con la salida de jack 3.5mm con un cable macho-macho conecto el monitor a los altavoces.

      Aplicación AlexaAplicación AlexaAplicación Alexa

      Aplicación Alexa

      Aparte de la aplicación móvil Alexa tiene una aplicación accesible con el navegador desde el ordenador, en ella se pueden realizar las acciones de configuración, añadir nuevos dispositivos y ver las listas, recordatorios, alarmas y activar o desactivar skills.

      El altavoz queda vinculado a la cuenta de Amazon y en esta se guarda todo el historial de comandos de voz, como esto es un inconveniente para los usuarios que protegen su privacidad se puede configurar para eliminar el historial de interacciones y hacerlo automáticamente para eliminar las interacciones más antiguas de 3 meses. También se puede configurar para que Amazon no utilice nuestra voz para entrenar sus algoritmos de inteligencia artificial de reconocimiento de voz. Otra opción recomendable a desactivar es la opción de realizar compras por control de voz, esto es un problema si viendo un vídeo que incluye el comando necesario para realizar la compra y se activa Alexa sin ser nosotros conscientes.

      El asistente Alexa se basa en la nube por eso requiere conexión permanente a internet, se activa por una palabra de activación que puede ser Alexa, Echo o Amazon. El altavoz está permanentemente escuchando esta palabra, si la detecta a continuación escucha el comando de voz, cuando el comando termina lo transmite a la nube para su reconocimiento y recibe la respuesta. Mientras no se detecte la palabra de activación no hay comunicación con internet ni la nube. El altavoz posee un botón para desactivar completamente la escucha, incluida la palabra de activación, este estado se identifica por el anillo luminoso rojo. Esto dota al dispositivo de privacidad salvo que contenga funciones maliciosas ocultas.

      El volumen de sonido al máximo en GNOME es muy bajo, tengo que poner el Echo al nivel 6 o 7 para tener un volumen satisfactorio, con este volumen si Alexa habla supongo es posible que se entere el vecino por eso me gustaría que Alexa tuviese el modo susurro activado siempre. En GNOME la aplicación Retoques tiene un opción para activar la sobreamplificación lo que permite subir el volumen por encima del máximo, la misma opción indica de que esto puede suponer una pérdida de calidad, no la he notado. El sobrevolumen se usa desde el mismo panel de control de volumen.

      Aplicación Retoques de GNOME para activar la sobreamplicación

      Aplicación Retoques y configuración de sonido en GNOME

      Escuchando música aún perdiendo el estéreo no noto tanta diferencia como pensaba, jugando en la PlayStation si noto diferencia en el estéreo.

      Las rutinas son una serie de comandos que son ejecutados por Alexa, se pueden activar a una cierta hora o con un comando de voz personalizado. También pueden activarse desde la aplicación del smartphone. Son muy útiles, por ejemplo para poner una radio y a un determinado volumen o bajar el volumen a una cierta hora los días entre semana.

      Programación de rutinasProgramación de rutinasProgramación de rutinas

      Programación de rutinas

      El anillo luminoso puede tomar varios colores con diferentes significados de notificaciones, error o actividad: Amarillo, Rojo, Azul, Naranja, Morado, Verde, Blanco.

      En las páginas de ayuda hay más información de los dispositivos Alexa:

      Primeras impresiones

      Se configura fácil, es bastante pequeño y requiere menos cables que otros altavoces. Para su tamaño pesa bastante aunque esto no es muy relevante, su peso y tamaño tampoco es muy grande pudiendo colocarse en cualquier lugar. En su tamaño es posiblemente el altavoz con mejor calidad de sonido y diseño. La calidad del sonido en graves es buena, yo no percibo una diferencia significativa con los altavoces 2.1 que tenía con subwoofer y estéreo, y estos eran ya bastante buenos.

      Aún sin crear un perfil de voz Alexa reconoce perfectamente la voz no me ha dado ningún problema salvo en alguna rara ocasión no reconocer la palabra de activación. Tanto en voz normal como hablando en modo susurro incluso cuando el altavoz está reproduciendo música e invocando a Alexa a unos metros de distancia en el lado más alejado de la habitación la escucha. Si está reproduciendo música y se invoca baja el volumen del altavoz para reconocer mejor el comando.

      La potencia de volumen del Echo no llega a la que podrían alcanzar los FPS1600 que tenía pero en su escala de niveles de volumen de 0 al 10 el 4-5 es el normal y el 8 ya lo considero elevado. El Echo tiene clavija jack 3.5mm que puede funionar como entrada o salida, de salida para enviar sonido a otros altavoces externos mejores o como entrada que es lo que utilizaré para escuchar el sonido del ordenador. También permite recibir sonido por conexión Bluetooth desde el móvil o desde el ordenador o tablet.

      Si se pretenden utilizar los botones físicos es recomendable ubicar el altavoz al alcance de la mano aunque el único realmente imprescindible es el de desactivar los micrófonos para que Alexa deje de escuchar la palabra de activación y el comando de voz. El volumen se puede cambiar con comandos de voz.

      Se le puede pedir música de diferentes géneros clásica, electrónica, techno y reproduce canciones de Amazon Music gratuitamente aunque sin la posibilidad de elegir un artista o cancion determinada para lo cual se necesita un servicio de suscripción. Es muy cómodo estar en la cama despertarte a la 7 y sin levantarte decirle que te ponga una alarma dentro de dos horas o pedirle que te ponga música o la radio al volumen deseado. Para personas con movilidad o visión reducida es un gran dispositivo de ayuda ya que al estar basado principalmente en la voz es accesible para estas personas aunque para algunas tareas se requiere utilizar la aplicación.

      Amazon Echo escuchando un comando de voz Cable jack 3.5mm macho-macho

      Amazon Echo escuchando un comando de voz y cable jack 3.5mm

      Funcionalidades y skills

      Para darle el mayor uso al Amazon Echo la dificultad está en conocer qué comandos de voz se pueden utilizar. Aún desconozco en gran medida las posibilidades de los altavoces inteligentes y del asistente virtual Alexa controlador por voz. Requiere conexión a internet mediante WiFi soportando redes redes 802.11 a/b/g/n/ac (WiFi 5) pero no ax (WiFi 6).

      El altavoz inteligente soporta una serie de funciones invocadas por control de voz, algunos comandos son para controlar el volumen del altavoz, para saber la hora, de información general a modo de enciclopedia, traducciones, el tiempo del día, de mañana o del fin de semana, noticias, crear listas de tareas, listas de compra, crear recordatorios, alarmas o temporizadores o escuchar música. Estos son algunos de los comandos por defecto más útiles. También es capaz de reproducir música en streaming de servicios como Amazon Music, Apple o Spotify así como leer noticias de actualidad y reproducir emisoras de radio.

      Algunos ejemplos de comandos de voz son los siguientes sin ser esta una lista completa. Hay más comandos de voz.

      Volumen, hora y meteorología.

      • “Alexa, sube el volumen”
      • “Alexa, pon el volumen al 5”
      • “Alexa, ¿qué hora es?”
      • “Alexa, ¿va a llover mañana?”
      • “Alexa, ¿qué tiempo va a hacer en Pontevedra este fin de semana?”
      • “Alexa, ¿qué tiempo hace en París?”

      Información general.

      • “Alexa, ¿cuánto mide El Teide?”
      • “Alexa, ¿cuánto es 100 entre 16?”
      • “Alexa, ¿quién es el Primer Ministro de Italia?”
      • “Alexa, ¿cómo se dice «Te quiero» en francés?”
      • “Alexa, ¿cómo se dice «Buenos días» en japonés?”
      • “Alexa, ¿cCómo se dice «Muchas gracias» en italiano?”

      Noticias.

      • “Alexa, ¿cuáles son las noticias?”

      Listas de tareas y compra

      • “Alexa, añade «sacar a pasear al perro» a mi lista de tareas”
      • “Alexa, añade «leche» a mi lista de la compra”

      Recordatorios, temporizadores y alarmas.

      • “Alexa, pon un recordatorio”
      • “Alexa, te ayuda a que no se te olvide nada”
      • “Alexa, pon un temporizador de 10 minutos para la pizza”
      • “Alexa, pon una alarma para las 8 de la mañana”
      • “Alexa, despiértame por la mañana”
      • “Alexa, pon una alarma recurrente todos los días de entre semana a las siete de la mañana”
      • “Alexa, ¿qué alarmas tengo?”
      • “Alexa, pospón…” (y disfruta de unos minutos más de sueño)

      Escuchar música por género o música utilizando el servicio gratuito Amazon Music o con suscripciones de servicios como Amazon Music Unlimited o Spotify.

      • “Alexa, pon música”
      • “Alexa, siguiente”
      • “Alexa, ¿qué canción es esta?”
      • “Alexa, para la música”
      • “Alexa, pon música de los 80”
      • “Alexa, pon música de los 60”
      • “Alexa, pon música rock”
      • “Alexa, pon música country”

      Para usar skills.

      • “Alexa, abre el diario punto es”
      • “Alexa, abre el diario punto es” y dame las noticias de política

      Las skills son nuevas funcionalidades, habilidades y comandos que Alexa es capaz de reconocer. Activar skills es muy sencillo, están organizadas en categorías: novedades, alimentos y bebidas, meteorología, coche conectado, compras, curiosidades y humor, deportes, educación y referencia, estilo de vida, hogar digital, infantil, juegos y curiosidades, local, música y audio, negocios y finanzas, noticias, películas y TV, productividad, salud y bienestar, servicios, local, viaje y transporte. Desactivar una skill es igual de sencillo, en un listado se muestran las activadas. Las skills no se instalan en el Echo simplemente se activan y desactivan, hay gran cantidad de ellas aunque las realmente útiles serán muchas menos.

      SkillsSkillsSkills

      Skills

      Además de los dispositivos inteligentes como enchufes y bombillas el Echo es capaz de controlar cualquier aparato que tenga un mando por infrarrojos o radiofrecuencia incluyendo televisores, reproductores de vídeo o aires acondicionados. Con un emisor compatible con Alexa es capaz de emitir las señal infrarrojo y radiofrecuencia que el mando del aparato electrónico original y controlarlo. Otros dispositivos controlables son enchufes para encender y apagar aparatos o bombillas regulables en color e intensidad para crear ambientes cómodas para bajar la luminosidad al ver una película.

      Problemas, algunos que he resuelto y cosas que me gustaría que tuviese

      Y ahora unos problemas y cosas que me gustaría que tuviese después de los primeros momentos de uso. Algunos son por problemas con mi ordenador otros son mejoras que podrían tener los altavoces Echo.

      La configuración que quería como altavoz para el ordenador funciona correctamente, el conector jack 3.5mm se puede configurar como entrada para el ordenador o como salida para unos altavoces externos. Pero si se le indica algún contenido que reproduzca sonido desde la nube la entrada de línea se desactiva, una vez se para el contenido de la nube hay que configurar el altavoz de nuevo, se puede hacer desde la aplicación de smartphone cambiando la salida de audio y de nuevo a la entrada de audio o con el siguiente comando de voz pero no desde la aplicación web. El sonido de la entrada de línea no se puede parar, Alexa lo que hace es silenciar el volumen por eso para recuperarlo hay que cambiarlo, una forma con su comando de voz. Esto lo pongo en la sección de problemas porque hasta después de unos días de uso pensaba que la única posibilidad era utilizar la app de móvil y no se podía hacer con un comando de voz, esto es posiblemente el mayor inconveniente que tenía el resto son mejoras que me gustaría que tuviera.

      • “Alexa, reproduce contenido desde la entrada de línea”
      • “Alexa, sube el volumen de la entrada de línea”

      En mi caso con Arch Linux en el ordenador es capaz de conectarse al Echo por Bluetooth, lo reconoce como dispositivo de audio pero luego en la configuración del sonido a veces no se puede seleccionar como dispositivo de audio de salida el altavoz Bluetooth, en vez de esto aparece como dispositivo de entrada. Esto quizá sea un problema del entorno de escritorio GNOME, a la implementación de Bluetooth en GNU/Linux o a Amazon Echo Plus as bluetooth speaker on Ubuntu 20.04. El mismo problema le pasa a algunos usuarios de Ubuntu. Después de varias pruebas he conseguido que me funcione, los pasos que he seguido están basados en el enlace anterior que han sido poner el Echo en modo emparejamiento, emparejarlos e iniciar la conexión por Bluetooth desde Linux mientras se reproduce sonido. La opción para realizar la conexión desde el panel de control de Bluetooth parece que lo intenta e inmediatamente la opción se desactiva de nuevo pero al cabo unos segundos se conecta y comienza la reproducción.

      El sonido del ordenador por Bluetooth soluciona un problema que tenía con el sonido por conexión HDMI. Por HDMI y sacando el sonido por jack 3.5mm desde el monitor ocurre que cuando se activa el protector de pantalla o la pantalla de bloqueo la señal del HDMI se apaga y afecta al sonido que también deja de emitirse, por Bluetooth la reproducción continua perfectamente. Usando mi móvil lo he conectado por Bluetooth para reproducir música perfectamente, si pongo el reproductor las canciones se escuchan sin problema y parando vuelve a reproducir la entrada de línea si se usa esta forma de conexión.

      Los dispositivos Bluetooth al gual que la WiFi tiene la opción de hacerse visibles emitiendo su nombre esto está visible para cualquier persona que se encuentre en el rango de alcance con la posibilidad de intentar el emparejamiento y cuanto menos conocer la presencia del dispositivo que suele incluir su modelo, es un problema de privacidad. El Echo solo se hace visible en el momento de emparejamiento que se inicia desde la app pero Linux por defecto se queda siempre en modo visible y posibilitando el emparejamiento, para desactivar esta visibilidad al cabo de un tiempo hay que modificar el archivo de configuración /etc/bluetooth/main.conf con los siguientes valores.

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      ...# How long to stay in discoverable mode before going back to non-discoverable# The value is in seconds. Default is 180, i.e. 3 minutes.# 0 = disable timer, i.e. stay discoverable foreverDiscoverableTimeout=180# How long to stay in pairable mode before going back to non-discoverable# The value is in seconds. Default is 0.# 0 = disable timer, i.e. stay pairable foreverPairableTimeout=180...
      main.conf

      No se puede configurar el volumen de sonido de forma independiente para cada categoría de sonido, por ejemplo me gustaría tener una configuración de volumen para la voz de Alexa, por skill, notificaciones y alarmas y para la entrada de línea. El volumen de Alexa y sus funciones me parece demasiada alta comparada con el volumen de la entrada de línea. También me gustaría poder configurar Alexa para que hablase y respondiese siempre en modo susurro y no solo cuando uno le da un comando de voz susurrando.

      Utilizar un comando de voz para subir y bajar el volumen en algunas ocasiones es cómodo y en esas ocasiones si se está a cierta distancia, tiene botones físicos pero no son tan cómodos como un potenciómetro. También me gustaría que tuviese una forma de apagarlo completamente con un botón físico, con un comando de voz que no esté permanentemente encendido. También un botón para silenciar el volumen para quitar el audio temporalmente y recuperar el volumen original más tarde sin parar la reproducción. O un botón para parar el audio o la reproducción.

      Que todo lo que se pueda hacer con la voz o con los botones físicos se pueda hacer desde una aplicación aunque sea escribiendo el texto. Ahora hay algunas funciones que hay que realizarlas con comandos de voz y otras con la aplicación de Alexa. Las rutinas al activar los skills solo permiten abrirlas, no indicarle el comando exacto de apertura, algunas skills ofrecen varias opciones.

      Estaría bien que el audio del altavoz fuera la suma de todas las fuentes de sonido ya que si le pongo a reproducir una radio se deja de el escuchar audio de la entrada de línea en este caso el ordenador.

      Configurar que el sonido no pueda ser más de un nivel a ciertas horas, estar de noche y que se active el volumen al máximo porque ha entendido mal un comando al cambiar el volumen, estar a las doce de la noche y que el altavoz se ponga al máximo volumen por una mala interpretación del comando de voz hace que el vecino se entere de ruido e inmediatamente haya que buscar el botón físico para bajar el volumen. Las rutinas en cierta medida permiten programar que a una hora o con un comando hagan una serie de acciones, entre ellas está el cambiar el volumen.

      RutinasRutinasRutinas

      Rutinas

      El Echo necesita de conexión a internet, en mi caso lo tengo enchufado a la misma regleta de conexión a la corriente que el router. Cuando enciendo la regleta con el botón el router tarda más en conectarse en alguna ocasión y proporcionar conexión a internet al resto de dispositivos el Echo incluido. Cuando el Echo se enciende y no tienen internet no sirve de nada y aún pasado varios minutos después de que el router ya proporcione internet el Echo no ha sido capaz de reconectarse a internet por si mismo, le he tenido que desenchufar y enchufar después de que el router haya terminado de inicializarse, en este momento sí el Echo ha tardado menos de 30 segundos en inicializarse.

      Dado que son unos altavoces dependiente de servicios de Amazon me surgen las siguientes preguntas, ¿Amazon dará servicio a los Echo durante al menos dos décadas? ¿ningún Echo se quedará sin soporte ni perderá sus funcionalidades pasados varios años?

      Por otro lado, si los altavoces inteligentes tienen éxito como producto con el tiempo saldrá nuevas versiones con más funcionalidades que dejarán desfasados a las versiones anteriores, espero que algunos de los problemas comentados se resuelvan también para las versiones actuales.

      Familia de altavoces inteligentes Amazon Echo y alternativas

      La familia de altavoces Echo con el asistente Alexa tiene varios miembros.

      • Echo Flex: de conexión directa al enchufe para ser colocado en cualquier estancia como baño o cocina.
      • Echo Dot: pequeño, con display formado varios 7 segmentos que permite mostrar la hora adecuado como reloj de mesilla.
      • Echo: ofrece mejor sonido que el Dot, incluye un subwofer para sonidos graves.
      • Echo Plus: como el Echo pero incorpora el controlador de dispositivos Zigbee que necesitan algunos dispositivos del hogar digital como las bombillas Philips.
      • Echo Studio: el dispositivo con el mejor sonido que ofrecen los Echo pero también ocupa más y es más caro, puede sustituir a las barras de sonido para una televisión.

      Los Echo Show incluyen una pantalla con la posibilidad de realizar videoconferencias.

      Tanto Apple como Google ofrecen sus versiones de altavoces inteligentes, Apple el HomePod y Google los Home y Nest Mini.

      Conclusión

      El Amazon Echo es pequeño, tiene buena calidad de sonido, solo necesita dos cables y tiene funciones adicionales a unos altavoces tradiciones, en algunas cosas son mejores y en otras podrían mejorar. En oferta tiene un precio similar al de unos altavoces de calidad 2.1. Se gana en comodidad, en espacio pero no se tiene sonido estéreo, salvo que se compre una pareja de ellos, y la conexión auxiliar de entrada de línea no es la predeterminada.

      Para mi caso que lo pretendo utilizar principalmente como sustituto de unos altavoces para el ordenador y PlayStation sirven pero no están diseñados específicamente para esta función pero la cumplen con suficiencia. El hecho de requerir permanentemente conexión a internet por el reconocimiento de voz en la nube hace que no sirva de nada sin ella. El mayor problema que me he encontrado ha sido conocer el comando de voz para reproducir el audio de la entrada de línea sin utilizar la app pero lo tiene, además si se está escuchando música o la radio de internet y se empieza a reproducir contenido por la entrada de línea se empieza a reproducir el contenido de la entrada de línea de forma automática.

      En algunos aspectos me gusta, se puede usar sin necesidad de ordenador, hay multitud de skills de noticias, radios o el tiempo. El servicios de Amazon Music proporciona música de forma gratuita de diferentes géneros.

      Y en otros no me convencen completamente, sobre todo los botones físicos adicionales que comentaba que echo en falta. La aplicación tiene margen para añadir más funciones que a los usuarios les serían muy útiles y entonces sí serían una opción recomendable para más usuarios.

      El Echo y Echo Studio son una buena consideración como altavoz para ver películas con una mejor calidad de sonido que la que ofrecen los altavoces integrados de las televisiones, la calidad de sonido y precio es similar que una barra de sonido pero tienen funciones adicionales.

      Cuando compré el Echo tenía dudas de si sería buena opción como sustituto de mis antiguos altavoces pero pasados unos días de uso por ahora estoy bastante contento con él.

      Vídeos

      En YouTube hay numerosos vídeos con desempaquetados, analizando los altavoces y la familia de altavoces Echo y explicando como funcionan las skills que se pueden activar..


      Blog Bitix: Hemeroteca #17

      $
      0
      0

      Hugo

      Que leáis este blog para mi significa mucho aparte de para compartir conocimiento que voy aprendiendo me permite canalizar la frustración de no poder usar en el trabajo mucho de lo que escribo, demasiados años ya. Los pocos comentarios, retweets y favoritos en twitter que recibo me hacen no perder la esperanza y en cierta medida me anima a seguir escribiendo. Aunque se que mi motivación no debería depender de esto si encima no recibiese vistas sería algo que me desmotivaría.

      En meses anteriores he mejorado el diseño del blog, aún tengo mejoras pendientes que realizar pero no el suficiente tiempo para realizarlas. En los siguientes meses quiero aprender al menos a un nivel básico más sobre temas de SEO para que el tiempo que dedico al blog tenga el máximo rendimiento. Empezaré al publicar los artículos haciendo un pequeño análisis de palabras clave y primeras posiciones de resultados para el título que ponga en el artículo en el buscador Google y en la herramienta Google Search Console. Para los nuevos artículos que escribo pero también para los más visitados que he escrito, introduciendo en ellos algunos pequeños cambios y actualizaciones. Otro aspecto de SEO que tendré en cuenta es la intención de búsqueda, es decir, lo que un usuario espera encontrar cuando hace una búsqueda, normalmente la respuesta que se espera es a la pregunta ¿qué es? o ¿cómo? con lo que incluir un párrafo la respuesta a estas preguntas posiblemente los artículos posicionan mejor en Google.

      En los cambios de diseño que he realizado están unas pequeñas modificaciones en la página de Archivo y hemeroteca donde recojo todos los artículos publicados, he añadido una pequeña descripción de cada sección y un icono junto al título del artículo que indique la temática del mismo. Esto permite identificar los artículos por tema de una forma visual y sin necesidad de leer texto si alguien busca artículos de un cierto tema.

      Supongo que habrá sido por la pandemia del COVID-19 y las medidas de confinamiento durante estos meses y ahora con el verano y las vacaciones continuará algún tiempo más, en este tiempo he notado una pequeña bajada en las visitas y una notable bajada en los ingresos de AdSense pasando de unos 30€ mensuales a 15€. Aunque no estoy completamente seguro de haya sido por los nuevos requerimientos para publicidad digital con los nuevos archivos ads.txt, en principio cumplo y en Google AdSense no tengo ninguna advertencia.

      En este semestre he seleccionado mejor la temática de los artículos a escribir y publicar siguiendo dos principios: que sean interesantes para mi o que tengan potencial de atraer visitas. Y de algunos estoy muy contento de haberlos escrito como los relativos a Testcontainers para pruebas de integración, SDKMAN para instalar múltiples versiones de Java, Storybook para sistemas de diseño con TypeScript y React, el artículo sobre el futuro de la concurrencia con Project Loom, el recolector de basura de Java, el patrón open session in view con sus ventajas y sus alternativa, programación con AspectJ e incluiría varios artículos más en este párrafo. En otras épocas los artículos que seleccionaba para escribir en algunas ocasiones lo hacía a modo de relleno sin prácticamente planificación planificaba más lo que podía escribir sin necesidad de investigar mucho. También contento con algunos de los artículos sobre GNU/Linux como Fedora Silverblue.

      También he seguido haciendo pequeños cambios en el script para instalar Arch Linux con múltiples correcciones de errores y soporte de nuevas características. El repositorio de GitHub de Arch Linux Install Script ya tiene unas 230 estrellas y más de 100 forks de gente de numerosos países Australia, EEUU, Alemania, Colombia, Suecia y de muchos otros de todo el mundo. Hay un usuario que me envía que falla en el script cuando hago cambios lo cual se lo agradezco ya que algunos a mi me pasan desapercibidos y no tengo tiempo para probar cada cambio. También he recibido algunos pull requests que me han permitido corregir algunos otros errores y añadir algunas nuevas características.

      En este semestre han sido 43 nuevos artículos de las temáticas habituales sobre Java, GNU/Linux e incluyendo como novedad el análisis de algún juego después de haber completado la historia principal. Estoy publicando casi dos artículos a la semana con la intención a ver si consigo hacer subir la curva de visitas.

      Artículos sobre Java.

      Artículos bore GNU/Linux.

      Artículos sobre desarrollo web.

      Artículos sobre juegos con la PlayStation.

      Algunos nuevos desempaquetados.

      Y una contribución al proyecto Apache Tapestry con la que actualicé el diseño de la página del proyecto a una versión más moderna y soportando múltiples dispositivos con un diseño adaptable.

      Y otros que no entran en las categorías anteriores.

      Variable not found: Enlaces interesantes 410

      $
      0
      0
      Enlaces interesantes

      Como ya adelantamos en la entrega 404 de enlaces interesantes, los códigos de estado 404 y 410 son muy similares, pues ambos permiten al servidor expresar que el recurso solicitado no existe.

      La diferencia entre ambos es que HTTP 410 (Gone) que el recurso existió pero ya no está disponible, mientras que HTTP 404 no permite distinguir entre un recurso que jamás ha existido y uno que simplemente ha desaparecido.

      Y ahora vamos con una nueva recopilación de enlaces que, como siempre, espero que os resulten interesantes. :-)

      Por si te lo perdiste...

      .NET Core / .NET

      ASP.NET Core / ASP.NET

      Azure / Cloud

      Conceptos / Patrones / Buenas prácticas

      Data

        Web / HTML / CSS / Javascript

        Visual Studio / Complementos / Herramientas

        Xamarin / MAUI

        Publicado en Variable not found.

        Variable not found: Cómo solucionar el error "Unable to connect to web server 'IIS Express'" en Visual Studio

        $
        0
        0
        Unable to connect to web server 'IIS Express'

        Va un post rapidito, pero que seguro que puede ahorrar quebraderos de cabeza a más de uno que se encuentre con este problema al iniciar desde Visual Studio una aplicación ASP.NET, ASP.NET Core MVC/Web API, Razor Pages, o incluso Blazor que utilicen por debajo IIS Express.

        El problema ocurre justo al ejecutar la aplicación con F5 o Ctrl+F5 desde el entorno de desarrollo; en ese momento, la ejecución se detiene y aparece un cuadro de diálogo con el mensaje:

        "Unable to connect to web server 'IIS Express'"

        Creo que llevo años encontrándome de vez en cuando con este problema al arrancar las aplicaciones, y nunca entendí muy bien por qué pasaba. Buscaba por la red y solo encontraba soluciones relativas a eliminar el archivo de configuración applicationhost.config que Visual Studio guarda en la carpeta ".vs" de la solución, a reiniciar el IDE o incluso la máquina, abrirlo como Administrador, o cambiar el puerto en la configuración del proyecto, normalmente algunas unidades por arriba o por abajo que el puerto asignado inicialmente.

        Esta última opción es la que más veces me ha funcionado, pero no siempre iba bien, por lo que al final tampoco la identifiqué como una clara receta para el problema que nos ocupa.

        Hoy me ha vuelto a ocurrir con un proyecto que inicio muy a menudo, que, de un día para otro, ha dejado de funcionar y ha comenzado a lanzarme a la cara el maldito cuadro de diálogo. La diferencia es que por fin he podido encontrar una respuesta satisfactoria :)

        El motivo de este error es que Windows ha reservado para su uso interno el puerto utilizado por la aplicación, por lo que no podemos utilizarlo con IIS Express. La solución es sencilla: debemos cambiarlo por otro que esté libre.

        Y claro, la cuestión es saber qué puertos están libres en el equipo.

        Para ello, podemos ejecutar la siguiente instrucción de la consola, que nos mostrará justamente lo contrario, es decir, los puertos reservados:

        C:\>netsh interface ipv4 show excludedportrange protocol=tcp

        Protocolo tcp Intervalos de exclusión de puertos

        Puerto de inicio Puerto final
        ---------- --------
        2869 2869
        4800 4800
        5357 5357
        8145 8145
        9009 9009
        50000 50059 *
        54950 55049
        55050 55149
        55454 55553
        55554 55653
        61566 61665
        61666 61765
        61766 61865
        61866 61965
        63053 63152
        63153 63252
        63353 63452
        63453 63552
        63858 63957
        63958 64057
        64058 64157
        64158 64257

        * - Exclusiones de puertos administrados.

        C:\>_

        Lo que vemos en el listado son rangos de puertos. Por ejemplo, podemos ver que están ocupados desde el 50000 hasta el 50059, o desde el 64158 hasta el 64257. Si nuestro proyecto usa uno de ellos, se generará el maldito error (que, dicho sea de paso, ya podría dar algo más de información sobre lo que ocurre...)

        Sabiendo esto, ya es fácil localizar un puerto libre. En mi caso podemos ver que el 51001 está libre, así que solo hay que modificarlo en las propiedades del proyecto:

        Cambiar el puerto del proyecto

        ¡Espero que os sea de ayuda!

        Publicado en Variable not found.

        Fixed Buffer: Contenedores Docker en Azure (aci) desde Docker Desktop

        $
        0
        0
        Tiempo de lectura:8minutos
        Imagen ornamental con el logo de Azure Container Instances (aci) para la entrada sobre como desplegar contenedores en Azure con Docker Desktop

        Se van acercando las vacaciones de verano y ya están tan cerca que se pueden sentir. Con las vacaciones de verano llega el final de la temporada y un merecido descanso. Con esa idea en mente, pensaba cerrar la temporada con una entrada sobre Dapper que se iba a publicar en lugar de esta, pero el reciente anuncio de Microsoft sobre el soporte experimental para desplegar contenedores Docker directamente en Azure Container Instances (aci de aquí en adelante) desde Docker Desktop ha trastocado un poco los planes.

        Si quieres saber como desplegar un agente de DevOps efímero sobre aci, no te pierdas la entrada ‘Azure DevOps Ephimeral Agents: Agentes de usar y tirar

        ¿Por qué es tan interesante poder desplegar aci directamente desde Docker Desktop?

        Normalmente, trabajar con Docker en local es más que suficiente para poder hacer pruebas y desarrollos que antes o después acaben desplegados por ejemplo en Kubernetes. Aunque este suele ser el proceso habitual, también existen modelos de orquestación basados en docker-compose.

        Imagina que necesitas que un equipo de testing haga pruebas sobre una web que estas desarrollando. Si bien es cierto que se podría desplegar la imagen sobre un App Service directamente, habría que gestionarlo a parte, actualizar sus imágenes,…

        Gracias a este nuevo modelo, vamos a poder tener un funcionamiento mucho más sencillo, ya que va a ser directamente desde Docker Desktop donde vamos a gestionar los despliegues a contenedores en Azure Container Instances. Nuestro flujo de trabajo sería algo como esto:

        La imagen muestra un diagrama de flujo hacia DockerHub/ACR y desde ahí hacia Azure Container Instances

        Aunque ahora mismo está en una fase muy preliminar (se hizo público el 25 de junio de 2020), la idea detras de esto es conectar el aci con por ejemplo un Storage de Azure de modo que tengamos volúmenes persistentes o que podamos por ejemplo crear un despliegue complejo utilizando directamente docker-compose.

        Esto se suma a que los recursos dejan de ser parte de nuestra máquina para empezar a ser parte de la infraestructura de Azure. De este modo, si nuestro entorno tiene un consumo que nuestra máquina no puede soportar bien, al estar alojado en Azure ese problema desaparece de raíz.

        Configurando Docker Desktop para desplegar sobre ACI

        Llegados a este punto, vamos a empezar a configurar Docker Desktop para poder desplegar sobre ACI. El primero de los requisitos es tener la versión 2.3.2.0 Edge (o superior). Esta versión se puede descargar desde le propia web de descargas de Docker Desktop:

        La imagen señala el canal Edge de MacOS y de Windows en la sección de descargas de Docker Desktop

        En caso de que todavía no este disponible la versión 2.3.2.0 en las descargas (a mí me ha pasado), puedes descargarlo utilizando uno de estos enlaces para Windows o para MacOS.

        Una vez que hemos instalado la nueva versión, lo primero que vamos a necesitar es autenticarnos con nuestra cuenta de Azure. Para esto basta con ejecutar el comando:

        docker login azure
        

        Esto nos enviará a la web de Azure para que introduzcamos las credenciales. Una vez que nos autentiquemos, en la consola recibiremos un mensaje de confirmación de que todo ha ido bien:

        PS C:\Users\jorge> docker login azure
        login succeeded
        

        Con esto, ya casi tenemos todo listo para desplegar contenedores en Azure (aci) desde Docker Desktop. Solo nos falta un paso más, crear un contexto de tipo aci en Docker Desktop. Para esto basta con ejecutar:

        docker context create aci myacicontext
        

        Este comando nos permite a día de hoy, definir ciertos parámetros adicionales para poder ajustar más la configuración. Estos parámetros son:

        • location: Permite definir la ubicación de los contenedores
        • resource-group: Permite indicar que los contenedores deben crearse en un grupo de recursos concreto (que debe existir previamente)
        • subscription-id: Permite indicar el Id de una suscripción en concreto de Azure, especialmente útil si tienes más de una suscripción en tu cuenta.

        Por ejemplo, en caso de definir los 3 parámetros el comando sería algo así:

        docker context create aci myacicontext --location westeurope --subscription-id xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx --resource-group docker-aci
        

        Con esto, ya tenemos disponible el contexto myacicontext listo para desplegar contenedores aci desde Docker Desktop. Esto podemos comprobarlo con el comando:

        docker context ls
        

        Como mínimo, este comando debería respondernos 2 contextos, el default de Docker Desktop y el que acabamos de crear.

        PS C:\Users\jorge> docker context ls
        NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT                  KUBERNETES ENDPOINT   ORCHESTRATOR
        default             moby                Current DOCKER_HOST based configuration   npipe:////./pipe/docker_engine                         swarm
        myacicontext *      aci                 docker-aci@westeurope
        

        De ahora en adelante, podemos especificar sobre que contexto queremos aplicar los comandos Docker con el argumento –context Contexto. También podemos especificar el contexto por defecto que debe utilizarse si no se informa específicamente con el comando:

        docker context use contexto
        

        Vamos a probarlo

        Una vez que tenemos el contexto listo, basta con ejecutar un run normal para que se despliegue automáticamente. Por ejemplo, podemos utilizar el comando:

        docker run -p 80:80 -d nginx
        

        Esto mostrará por consola una salida un tanto diferente a la habitual cuando se usa el contexto local:

        PS C:\Users\jorge> docker run -p 80:80 -d nginx
        [+] Running 2/2
         - sleepy-cray             Created                                         4.5s
         - single--container--aci  Done                                           20.6s
        sleepy-cray
        

        Si ahora vamos a la suscripción de Azure en el grupo de recursos que le hemos indicado, podemos encontrarnos algo como esto:

        La imagen muestra el grupo de recursos docker-aci de con un aci llamado sleepy-cray

        En caso de que cuando creásemos el contexto no le indicásemos un grupo de recursos, se crearía uno automáticamente con un GUID como nombre.

        Podemos gestionar el propio contenedor desde el mismo Docker. En este caso hemos desplegado una imagen de nginx y hemos mapeado el puerto 80, por tanto, si obtenemos la IP del contendor, deberíamos poder acceder desde cualquier navegador. Para obtener esa IP basta con que ejecutemos:

        docker ps
        

        Esto nos mostrará por consola la salida habitual del comando, indicándonos la IP del contenedor:

        CONTAINER ID        IMAGE               COMMAND             STATUS              PORTS
        sleepy-cray         nginx                                   Running             52.143.11.203:80->80/tcp
        

        Basta con navegar a la dirección IP para ver que todo funciona correctamente:

        La imagen muestra la página de bienvenida de nginx y señala la IP desde la que se ha accedido que es 52.143.11.203

        Al igual que hacemos con cualquier otro contenedor, también podríamos obtener los logs desde el aci utilizando en Docker Desktop el comando:

        docker logs id-del-contenedor
        

        Por ejemplo, para nuestro nginx:

        PS C:\Users\jorge> docker logs sleepy-cray
        /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
        /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
        /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
        10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
        10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
        /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
        /docker-entrypoint.sh: Configuration complete; ready for start up
        10.240.255.56 - - [28/Jun/2020:19:37:17 +0000] "GET / HTTP/1.1" 400 157 "-""-""-"
        10.240.255.55 - - [28/Jun/2020:19:43:13 +0000] "GET / HTTP/1.1" 200 612 "-""Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.56""-"
        10.240.255.55 - - [28/Jun/2020:19:43:13 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "http://52.143.11.203/""Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36 Edg/83.0.478.56""-"
        2020/06/28 19:43:13 [error] 27#27: *154 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 10.240.255.55, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "52.143.11.203", referrer: "http://52.143.11.203/"
        PS C:\Users\jorge>
        

        Por último, para borrar el contenedor basta con que ejecutemos el comando:

        docker rm id-del-contenedor
        

        Utilizando Docker Compose sobre aci

        Hasta ahora hemos planteado el uso de docker para levantar contenedores individuales, pero no es la única posibilidad. Gracias a Docker Compose podemos orquestar diferentes configuraciones de contenedores de manera que podamos por ejemplo levantar una web y una base de datos. Como no podía ser de otra manera, esta es una característica soportada por esta versión preview (aunque con muchas limitaciones).

        Para poder usar esta funcionalidad, basta con que creemos un fichero docker-compose.yml con un contenido como este:

        version: '3.4'
        
        services:
           db:
             image: mysql:5.7
             restart: always
             environment:
               MYSQL_ROOT_PASSWORD: somewordpress
               MYSQL_DATABASE: wordpress
               MYSQL_USER: wordpress
               MYSQL_PASSWORD: wordpress
        
           wordpress:
             depends_on:
               - db
             image: wordpress:latest
             ports:
               - "80:80"
             restart: always
             environment:
               WORDPRESS_DB_HOST: db:3306
               WORDPRESS_DB_USER: wordpress
               WORDPRESS_DB_PASSWORD: wordpress
               WORDPRESS_DB_NAME: wordpress
        

        Una vez hecho el fichero, basta con ejecutar el comando:

        docker compose -f .\docker-compose.yml up
        

        OJO. El comando es docker compose y nodocker-compose. Esto es porque se utiliza la docker cli en vez de docker-compose cli.

        Tras la ejecución del comando, podremos obtener una salida como esta:

        PS C:\Users\jorge\OneDrive\Escritorio\aci> docker compose -f .\docker-compose.yml up  
        [+] Running 4/4
         - aci                Created                                  4.4s
         - db                 Done                                    66.7s
         - wordpress          Done                                    66.7s
         - aci--dns--sidecar  Done                                    66.7s
        

        Esto va a levantar un aci con varios contenedores dentro para la base de datos, el propio wordpress y un contenedor adicional que sirve de dns entre ambos. Ahora mismo, podemos ir a la interfaz de Azure y ver que efectivamente se han creado:

        La imagen muestra la interfaz de Azure donde se ve centro del aci que se han desplegado los contenedores db, wordpress y aci--dns--sidecar

        Con esto deberíamos poder entrar en la IP que se ha asignado y usar nuestro wordpress, pero actualmente parece que o no está soportado el uso de variables o tiene algún bug por lo que nuestro escenario no es capaz de arrancar al no asignar las variables de entorno en el contenedor. Puedes seguir la información al respecto en esta incidencia.

        Conclusión

        Gracias a esta nueva vuelta de tuerca de la mano de Docker y Microsoft, se amplían mucho los posibles escenarios de desarrollo que podemos conseguir con Docker antes de llegar a desplegar en un Kubernetes. Es posible hacer despliegues de entornos de pruebas de manera muy sencilla y gestionarlo tal cual haríamos como si el entorno fuese local de nuestra máquina.

        Hay que reconocer que la línea actual de desarrollo es muy interesante en cuanto a sus ambiciones, despliegue de aci desde Docker Desktop directamente, pudiendo utilizar cuentas de almacenamiento de Azure como volúmenes para los contenedores o pudiendo desplegar con docker-compose como sistema de orquestación.

        Llegados a este punto, es también muy importante señalar que es un producto extremadamente reciente, que no está recomendado para su uso en producción y que su documentación actualmente brilla por su ausencia. Si bien es cierto que se detalla cómo crear un contenedor, no hay nada de información sobre cómo utilizar los volúmenes, o solución de errores.

        Aun así y con todo, pienso que apunta muchas maneras para tener su hueco en el día a día del desarrollo con Docker una vez que madure un poco.

        **La entrada Contenedores Docker en Azure (aci) desde Docker Desktop se publicó primero en Fixed Buffer.**

        Picando Código: Actualización a Montevideo Bicis con datos de accidentes 2019

        $
        0
        0

        Montevideo Bicis es un sitio web que presenta información “objetiva” para gente que quiera circular en bicicleta en Montevideo. Aprovecha Datos Abiertos de la Intendencia de Montevideo y y UNASEV. Recientemente UNASEV liberó los datos de accidentes de tránsito de 2019, así que el sitio está actualizado con los datos de accidentes de tránsito hasta 2019.

        También corregí un error en el JavaScript del comportamiento de los botones de “Datos completos”. Ahora se muestran bien las tablas con los datos completos de todos los años. Hay información desde 2012 hasta 2019, y mientras sigan abriendo los datos, seguiré actualizando.

        Aproveché la actualización también para hacer algunas mejoras en el código, más que nada en cuanto a legibilidad para que me sea más fácil de mantener. En algún momento hasta le dedicaré un tiempo más para eliminar todo lo que va quedando del “Hackathon Driven Development” que le dio vida.

        Estaciones Movete

        El sitio incluye un mapa con información también de datos abiertos de la Intendencia de Montevideo. Uno de los datos son las estaciones Movete, estaciones de préstamo de bicicletas públicas en Montevideo. Lamentablemente me enteré que este servicio dejó de funcionar. En parte lo atribuyen a la reducción importante de usuarios, que yo atribuiría a la falta de infraestructura y educación vial contra la cual pretende luchar el sitio. Pero no entremos en una verborragia violenta que bastantes problemas tiene el mundo hoy para andar enojándose porque una ciudad no es amigable para los ciclistas…

        Los datos del mapa están basados en datos de la Intendencia de Montevideo, pero según la fuente no se actualizan desde marzo de 2018. Tengo entendido que se han creado varias ciclovías nuevas desde entonces, pero necesito que se actualicen los datos fuente. Ya agregué a mano una vez el estacionamiento del MNAV (¡gracias Eduardo de Informática en MNAV!), pero no tengo información (y paciencia) para agregar a mano las rutas.

        En fin, si les interesa el tema, pueden visitar MontevideoBicis.com, y ver el código fuente en GitHub. Y si tienen ideas o críticas sobre el sitio, son más que bienvenidos a compartirlos en los comentarios de este post.

         

        Montevideo Bicis 2019

        Viewing all 2715 articles
        Browse latest View live