Weblogs Código

Picando Código

R-Ladies Madrid y NoLegalTech: Legal Analytics para democratizar el acceso a las leyes

noviembre 17, 2018 08:10

Les dejo la información del próximo evento de R-Ladies Madrid y NoLegalTech en Madrid, a realizarse el martes 20 de noviembre. Todo lo que ayude a democratizar el acceso a información del estado y le de poder a la gente sobre sus gobernantes es bueno:

 R-Ladies Madrid y NoLegalTech: Legal Analytics para democratizar el acceso a las leyes

A la democracia le ha llegado la hora tecnológica

El Boletín Oficial del Estado (BOE) es el diario oficial para la publicación de las normas y las leyes en el territorio español. Recogida en el artículo 9 de la Constitución, la publicidad de las normas es un principio básico de seguridad jurídica de los ciudadanos. Solo, pues, si los ciudadanos conocen las normas pueden ejercer sus derechos y deberes dentro de la sociedad.

Si bien este principio nace como esencial en una sociedad democrática, hoy en día la cantidad de normas aplicables en tiempo y lugar se ha multiplicado hasta tal punto que ni la profesión del abogado puede llegar a alcanzarlas en su plenitud. Desde las directivas y reglamentos que dicta la Unión Europea, hasta las diversas normas que pueden dictar tanto las Cortes Generales como el propio Gobierno (Leyes Orgánicas, Leyes Ordinarias, Reales Decretos…) el puzzle del sistema legal español llega a ser incomprensible bajo los sistemas tradicionales de ejercer la profesión.

En los últimos años el Estado de Derecho, empleando una estructura burocrática alejada del ciudadano y un lenguaje ininteligible e incluso enigmático, se ha ido alejando cada vez más del objetivo principal de la sociedad: la convivencia normativa y la sujeción de los poderes públicos a la norma.

Sin embargo, con la irrupción de la tecnología en nuestras vidas y la generación de datos a lo largo y ancho del globo, el estudio y el aterrizaje de las normas al ciudadano ya es posible.

Habiéndose generado en los dos últimos años más datos que en toda la historia de la humanidad, a través del machine learning y el entrenamiento de textos jurídicos masivos (leyes, normas, disposiciones…), se puede crear lo que por primera vez en España puede traer poder legislativo: conocimiento real de las leyes a través de la ciencia y al margen de los intereses políticos. Hoy en día podemos alcanzar a conocer la normativa vigente española (tan solo conocer qué normas y qué preceptos están en vigor en todas las ramas del Derecho ya es un paso adelante), detectar incompatibilidades normativas… e incluso comprobar, con un análisis social, si una ley es efectiva en el tiempo y lugar en que se ha aplicado.

El próximo 20 de noviembre, bajo el paraguas de la comunidad R-Ladies Madrid y NoLegalTech, celebramos el primer evento Legal Analytics, donde tendrán cabida todas estas cuestiones.

Programa

18:00 Presentación
18.30 Bárbara Román (Nolegaltech): charla “Panorama legal y datos”
19.30 Mesa redonda moderada por Bárbara Román: Gloria Sánchez (Banco Santander), Sara Molina (Marketingnize) y Elen Irazábal (R-Law Geeks)
20.30 Presentación del grupo R-Law Geeks. Será una presentación tanto jurídica como técnica, como un ejemplo de legal analytics.
21.15 Preguntas
21.30 Despedida

Más información e inscripciones

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

Blog Bitix

El agotamiento de direcciones IPv4 y el protocolo de internet IPv6

noviembre 16, 2018 06:00

Las direcciones IP de IPv4 que identifican a las computadoras de forma uniquivoca para comunicarse entre si son limitadas y ya se han agotado. Aún siendo casi 4300 millones con el auge de internet en la última década y la que se espera en los próximos años con los dispositivos móviles y los elementos conectados del internet de las cosas la presión sobre el número de direcciones IP será aún mayor. Hasta ahora varias medidas paliativas han permitido mitigar la presión y extender la disponibilidad de direcciones IP. Uno de los principales motivos del desarrollo del protocolo IPv6 es aumentar en varios órdenes de magnitud el número de las direcciones IP disponibles, siendo en este protocolo direcciones de 128 bits en vez de 32 como IPv4. Sin embargo, hay varios motivos por los que aún IPv6 no se está usando ya de forma masiva.

El protocolo mediante el cual las computadoras intercambian datos mayoritariamente es el protocolo IPv4. Este protocolo identifica a cada máquina conectada a internet mediante una dirección IP que es un número de 32 bits normalmente escrito en cuatro grupos decimales para ser más fácilmente recordados por los humanos, por ejemplo, 192.169.1.56. La dirección IP pública que tenemos asignada la podemos obtener de páginas como cual-es-mi-ip. Un número binario de 32 bits da un máximo teórico de 4.294.967.296 posibles direcciones IP. En la época en que fué desarrollado las primeras redes de computadoras este número parecía adecuado y suficiente, sin embargo, con el éxito de internet y el crecimiento del número de dispositivos conectados el número de direcciones IP libres ya se ha agotado.

11111111.11111111.11111111.11111111
255.255.255.255
127.0.0.1 (dirección loopback)

El problema de agotamiento de direcciones IPv4 no es nuevo y ya desde hace alguna década se estaba advirtiendo e implementando medidas en el protocolo para retrasar durante algún tiempo el llegar a agotar las direcciones. Algunas de estas medidas son CIDR y NAT. CIDR permite aprovechar mejor o no desperdiciar tantas direcciones IP en los grupos de direcciones estableciendo una máscara para determinar cuál es la parte de dirección de red y cual es la dirección del host dentro de esa red, con CIDR las direcciones IP se identifican con el siguiente formato 255.255.255.255 /8 donde el último número determina que número de bits corresponden a la dirección de red y que parte a los hosts de esa red, en este caso 24 bits corresponden a la red y 8 bits a identificar al host hasta llegar a 32 bits. NAT permite a los dispositivos de una red compartir la misma dirección IP al conectarse a otras redes o internet, de esta forma en vez de requerirse una dirección pública por cada dispositivo de la red solo es necesaria una para todos los equipos, en los hogares es el enrutador o router el dispositivo el que hace NAT y el que tiene la dirección IP pública asignada. NAT no está exento de inconvenientes ya que los equipos externos a una red no pueden contactar directamente a los equipos internos de una red en la que se hace NAT, para ello hay que usar activación o reenvío de puertos que los routers ofrecen en su panel de administración. Incluso algunos proveedores de internet están implementado un NAT a nivel de proveedor conocido como CG-NAT para usar una o unas pocas direcciones IP para todos los usuarios del proveedor. A este nivel está llegando la escasez de direcciones IP del protocolo IPv4.

Panel de un router para hacer NAT

La solución a largo plazo para la escasez de direcciones IP del protocolo IPv4 es usar una nueva versión de protocolo conocida como IPv6. En IPv6 las direcciones IP son números de 128 bits dando lugar a 2128, aproximadamente 3.4×1038 o 340,282,366,920,938,463,463,374,607,431,768,211,456 combinaciones teóricas posibles, esto son unas 7.9×1028 veces más direcciones IP posibles que en IPv4. Las direcciones IPv6 se escriben en ocho grupos de cuatro números hexadecimales, pudiéndose hacer una contracción omitiendo grupos de ceros contiguos con ::. El enorme número de direcciones de IPv6 hace innecesaria el NAT y CG-NAT y los problemas que estos ocasionan. Estas son unas comparaciones:

  • La tierra tiene 4500 millones de años, si se asignan direcciones IPv6 al ritmo de 1000 millones por segundo desde que se formó la tierra hast ahora se usado una trillonésima parte del espacio de direcciones.
  • Se puede asignar 4 trillones de direcciones por cada uno de los 510 billones de metros cuadrados de la tierra.

11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111:11111111
FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF
::1 (dirección loopback)

IPv6 ha sido desarrollado hace varios lustros sin embargo su adopción está siendo tan lenta ya que los proveedores de internet y servidores de internet necesitan soportar el nuevo protocolo, los sistemas operativos y equipos de red ya hace tiempo que fueron adaptados y cualquiera que se compre actualmente ya lo soportará. Usar IPv6 puede ocasionar que algunos servidores que no soporten este protocolo aún no puedan ser accedidos. El servicio de nombres de internet ya fue adaptado a IPv6 y seguramente los servidores de los servicios de empresas importantes como Google, Amazon, Facebook, Microsoft o Apple también lo soportan.

Algunas otras ventajas de IPv6 sobre IPv4 son:

  • Procesado simplificado en los enrutadores: la cabecera de los paquetes de IPv6 aunque son más grandes son más simples. Los paquetes no son fragmentados en el tránsito por los routers sino por el equipo emisor. Se ha eliminado el checksum de la cabecera que ahora se considera garantizado a nivel de capa de enlace de modo que los routers no deben recalcular cuando los campos de la cabecera cambian.
  • Opciones extensibles: la cabecera de los paquetes tienen una extensión de 40 octetos con opciones adicionales implementadas como extensiones. Esto posibilita extender el protocolo en el futuro sin afectar a el núcleo de la estructura del paquete.
  • Jumbograms: IPv4 limita el tamaño de los paquetes a una carga de 65.535 (2^16−1) octetos (64 KiB). IPv6 opcionalmente puede manejar paquetes de hasta 4.294,967.295 (2^32−1) octetos (4 GiB).
  • Privacidad: ofrece mecanismos para que la dirección IP que permite a cualesquiera dos equipos comunicarse no sea usado potencialmente para el rastreo.
  • Multicast: transmitir un paquete a múltiples destinos en una única operación de envío, en IPv4 era opcional pero en IPv6 está dentro de la especificación.

Algunos motivos por los que IPv6 no se ha adoptado aún son:

  • Es necesario tiempo para implementar el estándar: IPv6 fué publicado en 1998 pero llevó tiempo implementarlo y probarlo pasando varios años hasta que fué incorporado en los equipos. Los operadores necesitan tiempo para aprender cómo trabajar con esta nueva tecnología.
  • Falta de claro beneficio: los usuarios no experimentarán una mejora de rendimiento o redes más fiables. Implementarlo requiere esfuerzo económico y humano. Por ejemplo, si hay software o hardware que no soporta IPv6 es necesario reemplazarlo.
  • Falta de incentivo: sin demanda de los usuarios y sin un beneficio claro los proveedores de software no han realizado el cambio.
  • Dependencia sobre IPv4: la transición a IPv6 es un esfuerzo distribuido en la que intervienen múltiples organizaciones. Por un tiempo IPv4 y IPv6 coexistirán. Algunos programas no soportan el nuevo protocolo.

Para añadir el soporte de IPv6 en un servidor web hay que usar la siguiente dirección IP en Apache y Nginx para escuchar peticiones en todas las interfaces de red.

# Apache
Listen [::]:80

# Nginx
listen [::]:80 default_server;

En una máquina con GNU/Linux se puede ver la dirección IPv6 asignada a cada interfaz de red con el comando ip -6 addr show.

Amazon soporta en algunas regiones IPv6 con lo que si usamos este servicio de computación en la nube se puede usar el nuevo protocolo. Otros actores importantes de la nube también lo soportan como Digital Ocean (1) o Linode (2). En las entidades de registro de dominios basta con añadir un registro AAAA a las zonas DNS.

Los siguientes dos artículos son interesantes, hablan de la evolución de las direcciones de internet y del agotamiento de direcciones, el estado de despliegue y por qué aún no se ha adoptado de forma masiva:

Aún queda tiempo hasta que IPv6 sea el protocolo mayoritariamente utilizado, cuanta más escasez de direcciones IPv4 haya más necesidad de migrar habrá pero para ello el soporte en los servidores, hardware y software es necesario. En algunos países el tráfico sobre IPv6 se ha doblado respecto a otros años, en otros países aún no ha sido desplegado de forma significativa. ¿Llegará un momento en que Google tenga en cuenta para el SEO aquellos sitios que soporten IPv6 como ya hace con HTTPS en vez de HTTP?

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

Variable not found

Localizar errores de validación del binding en ASP.NET Core MVC

noviembre 16, 2018 03:27

ASP.NET Core MVCEn el framework ASP.NET Core MVC es muy sencillo establecer los mensajes de error de validación de campos utilizando propiedades de las data annotations, como en el siguiente ejemplo:
namespace LocalizationDemo.ViewModels
{
public class PersonViewModel
{
[Required(ErrorMessage ="The name is required")]
public string Name { get; set; }
}
}
Incluso es bastante fácil hacer que este texto aparezca traducido atendiendo a la cultura del hilo actual. Para ello, simplemente debemos configurar los servicios de localización apropiadamente, e indicar en la propiedad ErrorMessage la clave del mensaje de error en el archivo RESX asociado a la clase:
// En Startup.cs:
public void ConfigureServices()
{
...
services.AddLocalization(opt=>opt.ResourcesPath="Resources");
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization()
.AddDataAnnotationsLocalization(); // Esta línea añade la localización
// de data annotations
}

// En el view model:
namespace LocalizationDemo.ViewModels
{
public class PersonViewModel
{
// El mensaje de error está definido
// en Resources/ViewModels.PersonViewModel.resx
[Required(ErrorMessage ="NameIsRequired")]
public string Name { get; set; }
}
}
Esto es así de fácil para las validaciones que declaramos de forma explícita en nuestro código, mediante atributos de anotaciones de datos. Sin embargo, existen otro tipo de validaciones que se realizan de forma implícita (por ejemplo, cuando en un campo entero introducimos un valor no numérico) y cuyos mensajes de error no son tan sencillos de establecer.

Por ejemplo, observad el siguiente view model:
public class PersonViewModel
{
[Required(ErrorMessage = "NameIsRequired")]
[Display(Name="Name")]
public string Name { get; set; }

[Display(Name="Height")]
public int Height { get; set; }

[Display(Name="Birthdate")]
public DateTime Birthdate { get; set; }
}
Las propiedades Height y Birthdate son de tipo valor y por tanto intrínsecamente obligatorias, por lo que el framework mostrará un texto de error por defecto si se dejan en blanco. Sin embargo, podemos cambiar fácilmente este mensaje de error utilizando la anotación [Required] y estableciendo en ella el parámetro ErrorMessage, tal y como hacemos con el campo Name.
Ojo, que esto también se puede automatizar, es decir, podemos asignarle automáticamente a las propiedades de tipo valor un atributo [Required] con los mensajes ya preconfigurados, o incluso definir los textos por defecto para ese tipo de validadores. Pero para no extendernos mucho, lo veremos en este otro post: https://www.variablenotfound.com/2018/11/establecer-textos-por-defecto-y.html.
Pero además, existen otros problemas de validación que pueden darse cuando el binder intente establecer el valor de estas propiedades a partir de los datos que llegan en la petición, por ejemplo, si el usuario introduce "abc" en el campo numérico o de fecha.

En estos casos, por mucho que hayamos configurado el sistema de localización y todo funcione aparentemente bien, podremos encontrarnos con pantallas como la siguiente, donde los mensajes de error generados durante el binding están en el idioma por defecto (inglés), mientras que los que establecemos nosotros mediante anotaciones han sido traducidos al idioma actual:

Formulario donde aparecen traducidos algunos errores de validación, pero otros aparecen en el idioma por defecto

Para solucionarlo tendremos que cambiar la forma en que el binder busca los textos de estos errores :)

¿De dónde obtiene el binder esos mensajes de error?

Como otros muchos mecanismos del framework, la obtención de estos mensajes se basa en proveedores, es decir, existe un componente especializado en proveer al binder de estos mensajes.

Por defecto, el proveedor mensajes de validación para el binder se implementa en la clase DefaultModelBindingMessageProvider, cuya única instancia es depositada durante el arranque de la aplicación en la propiedad ModelBindingMessageProvider del objeto MvcOptions que configura el comportamiento del framework MVC.
services.AddMvc(opt=>
{
// opt.ModelBindingMessageProvider tiene una instancia
// de DefaultModelBindingMessageProvider
})
DefaultModelBindingMessageProvider dispone de métodos para establecer y obtener las factorías que generarán los textos de cada uno de los errores que el binder es capaz de detectar, que son los siguientes:
  • MissingBindRequiredValue
  • MissingKeyOrValue
  • MissingRequestBodyRequiredValue
  • ValueMustNotBeNull
  • AttemptedValueIsInvalid
  • NonPropertyAttemptedValueIsInvalid
  • UnknownValueIsInvalid
  • NonPropertyUnknownValueIsInvalid
  • ValueIsInvalid
  • ValueMustBeANumber
  • NonPropertyValueMustBeANumber
Cada uno de estos mensajes de error dispone de un método en la clase DefaultModelBindingMessageProvider donde es posible establecer su factoría que, en la práctica, es un delegado que se encargará de retornar el texto de error para cada caso. Por ejemplo, el siguiente método de esta clase permitiría establecer el mensaje a mostrar cuando el binder encuentre un valor no válido para la propiedad que intenta establecer:
services.AddMvc(opt=>
{
opt.ModelBindingMessageProvider.SetValueIsInvalidAccessor(
value => $"El valor '{value}' no es válido"
);
[...] // Establecer el texto para el resto de errores
});
Observad que cada tipo de error soportado dispone de un método para establecer su factoría, de la forma Set{ErrorType}Accessor(), como en SetValueIsInvalidAccessor() o SetValueMustNotBeNullAccessor().

Establecer los textos desde recursos localizados

Visto lo anterior, si hacemos eso mismo con todos los mensajes de error soportados, y aprovechamos para personalizar o localizar el texto en cada caso, tendremos el trabajo hecho.

Pero vamos a dar una vuelta de tuerca más: ¿qué ocurre si queremos que los textos a emplear en cada caso provengan de archivos RESX? Pues no es mucho más complicado que lo que hemos visto antes: bastará con obtener acceso al IStringLocalizer apropiado y utilizarlo para tener acceso a los recursos.

En el siguiente bloque de código vemos cómo podríamos obtenerlos, por ejemplo, del archivo de recursos localizados "ModelBindingDefaultMessages.resx" presente el proyecto "LocalizationDemo":
services.AddMvc(opt=>
{
var stringLocalizerFactory = mvcBuilder.Services
.BuildServiceProvider().GetService<IStringLocalizerFactory>();
var loc = stringLocalizerFactory
.Create("ModelBindingDefaultMessages", "LocalizationDemo");

opt.ModelBindingMessageProvider.SetMissingBindRequiredValueAccessor(
prop => loc["MissingBindRequired", prop]);
opt.ModelBindingMessageProvider.SetMissingKeyOrValueAccessor(
() => loc["MissingKeyOrValue"]);
opt.ModelBindingMessageProvider.SetMissingRequestBodyRequiredValueAccessor(
() => loc["MissingRequestBodyRequired"]);
opt.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
prop => loc["ValueMustNotBeNull"]);
opt.ModelBindingMessageProvider.SetAttemptedValueIsInvalidAccessor(
(value, prop) => loc["AttemptedValueIsInvalid", value, prop]);
opt.ModelBindingMessageProvider.SetNonPropertyAttemptedValueIsInvalidAccessor(
value => loc["NonPropertyAttemptedValue", value]);
opt.ModelBindingMessageProvider.SetUnknownValueIsInvalidAccessor(
prop => loc["UnknownValueIsInvalid", prop]);
opt.ModelBindingMessageProvider.SetNonPropertyUnknownValueIsInvalidAccessor(
() => loc["NonPropertyUnknownValueIsInvalid"]);
opt.ModelBindingMessageProvider.SetValueIsInvalidAccessor(
value => loc["ValueIsInvalid", value]);
opt.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(
prop => loc["ValueMustBeANumber", prop]);
opt.ModelBindingMessageProvider.SetNonPropertyValueMustBeANumberAccessor(
()=> loc["NonPropertyValueMustBeNumber"]);
});
Tras esto, podemos crear los archivos de recursos "Resources/ModelBindingDefaultMessages.resx" con los textos para cada uno de estos errores:

Contenido del archivo de recursos
Obviamente, para que esto funcione debemos tener registrados y configurados los servicios de localización de .NET Core para que los recursos sean tomados de la carpeta /Resources.

Pero, ¿y no es esto demasiado código para Startup?

Pues sí, y para mejorarlo podríamos usar llevarnos el código a un extensor de IMvcBuilder como el siguiente:
public static class MvcOptionsExtensions
{
public static void ConfigureModelBindingMessages(this IMvcBuilder mvcBuilder,
string resourceName = null, string resourceLocation = null)
{
mvcBuilder.Services.Configure<MvcOptions>(opt =>
{
// By default, the Resx file name is ModelBindingDefaultMessages.resx:
resourceName = resourceName ?? "ModelBindingDefaultMessages";

// By default, resources live in same assembly that the Startup class does:
resourceLocation = resourceLocation
?? Assembly.GetExecutingAssembly().GetName().Name;

var stringLocalizerFactory = mvcBuilder.Services
.BuildServiceProvider().GetService<IStringLocalizerFactory>();
var loc = stringLocalizerFactory.Create(resourceName, resourceLocation);

opt.ModelBindingMessageProvider.SetMissingBindRequiredValueAccessor(
prop => loc["MissingBindRequired", prop]);
opt.ModelBindingMessageProvider.SetMissingKeyOrValueAccessor(
() => loc["MissingKeyOrValue"]);
opt.ModelBindingMessageProvider.SetMissingRequestBodyRequiredValueAccessor(
() => loc["MissingRequestBodyRequired"]);
opt.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor(
prop => loc["ValueMustNotBeNull"]);
opt.ModelBindingMessageProvider.SetAttemptedValueIsInvalidAccessor(
(value, prop) => loc["AttemptedValueIsInvalid", value, prop]);
opt.ModelBindingMessageProvider.SetNonPropertyAttemptedValueIsInvalidAccessor(
value => loc["NonPropertyAttemptedValue", value]);
opt.ModelBindingMessageProvider.SetUnknownValueIsInvalidAccessor(
prop => loc["UnknownValueIsInvalid", prop]);
opt.ModelBindingMessageProvider.SetNonPropertyUnknownValueIsInvalidAccessor(
() => loc["NonPropertyUnknownValueIsInvalid"]);
opt.ModelBindingMessageProvider.SetValueIsInvalidAccessor(
value => loc["ValueIsInvalid", value]);
opt.ModelBindingMessageProvider.SetValueMustBeANumberAccessor(
prop => loc["ValueMustBeANumber", prop]);
opt.ModelBindingMessageProvider.SetNonPropertyValueMustBeANumberAccessor(
()=> loc["NonPropertyValueMustBeNumber"]);
});
}
}
De esta forma ya podríamos usarlo desde ConfigureServices() como sigue, dejando el código de inicialización de la aplicación mucho más limpio:
services.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1)
.AddViewLocalization()
.AddDataAnnotationsLocalization()
.ConfigureModelBindingMessages(); // Por defecto los recursos estarán en el archivo
// de recursos ModelBindingDefaultMessages.resx,
// en el ensamblado actual.

En resumen

En este post hemos visto cómo podemos modificar los mensajes de error generados por el sistema de binding, normalmente debido a problemas encontrados a la hora de asignar los valores presentes en la petición a los parámetros de entrada de nuestras acciones.

Para ello, sólo hemos tenido que acceder al proveedor de mensajes de este componente, DefaultModelBindingMessageProvider y modificar el delegado de generación de dichos mensajes. En este caso, dado que queríamos conseguir una versión localizada de los mensajes, hemos hecho uso de un IStringLocalizerFactory para obtener acceso a los recursos RESX y poder mostrar en cada caso la traducción correspondiente.

En un artículo posterior continuaremos profundizando en este tema y veremos cómo modificar los mensajes por defecto de las anotaciones estándar (Required, Range, etc.), evitando así el tener que personalizar los textos de error en cada una de dichas anotaciones, y aprovechando para ver cómo localizar estos mensajes.

Publicado en: www.variablenotfound.com.

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

Bitácora de Javier Gutiérrez Chamorro (Guti)

Casio fx-CP400 (Operaciones)

noviembre 14, 2018 11:07

En la anterior entrega sobre la calculadora CAS y gráfica fx-CP400, habíamos dado un repaso superficial a sus características. En el artículo de hoy, trabajaremos en serio con ella, al igual que hicimos con en Casio fx-CG50 (Operaciones). Si te lo perdiste, ahora es el momento de que leas el artículo anterior: Casio fx-CP400 (Introducción […]

La entrada Casio fx-CP400 (Operaciones) aparece primero en Bitácora de Javier Gutiérrez Chamorro (Guti).

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

Picando Código

Cerveza de DATA – Donde Pinta 2.0

noviembre 13, 2018 10:00

¿Se acuerdan de “Dónde Pinta” Aplicación sobre cerveza artesanal basada en datos abiertos? ¡Volvió! En forma de Cerveza de DATA.

Los “Café de DATA” (o en este caso “cerveza”) son una excusa para acercarse a la comunidad de DATA, y la más amplia comunidad de Datos Abiertos de Uruguay y América Latina. La oportunidad para conocer gente y limpiar un poco el karma negativo obtenido del trabajo que hacemos a diario alimentando este sucio sistema capitalista con trabajo voluntario para hacer del mundo un lugar mejor. Porque de entre todas las cosas que aprendimos gracias a Stan Lee, una fue que el super poder no hace al héroe, sino cómo lo usa. Así que si tenés ganas de aportar a un proyecto con un fin tan solemne y honrado como Dónde Pinta de forma voluntaria (o de hecho cualquiera de los proyectos de DATA), el primer paso es acercarte.

Si -como yo- sos de esas personas a las que les cuesta esa interacción inicial con desconocidos, una buena cerveza Índica, Davok, o tantas de las nuevas marcas que vienen surgiendo en el mundillo uruguayo de la cerveza artesanal pueden servir como catalizador social. Y si no tomás alcohol (o la cerveza no es lo tuyo), no hay problema. Mandate igual, DATA es un excelente grupo humano que te va a recibir de Brazos Abiertos™. Dejo la invitación:

Cerveza de Data - Dónde Pinta 2.0

DondePinta.uy Es una aplicación web que permite encontrar productores, bares, restaurantes y tiendas donde se pueda conseguir cerveza artesanal uruguaya.

El proyecto fue gestado junto a Underground Beer Club y Mirá Mamá y fue llevado adelante por la comunidad de los Café de DATA. Es un proyecto 100% voluntario y comunitario y su intención es que sea adoptado por los cerveceros como una forma de dar a conocer su oferta y compartir la información como Datos Abiertos.

La plataforma ya tiene un par de años, por eso creemos que ya es hora de volver a revisarlo ¡y hacerle algunas mejoras!
Para eso nos vamos a juntar en Shelter Patio Cervecero (Andresito Guacarí 1806) a las 17:00 a empezar el trabajo y discutir ideas. No dejes de sumar tus ideas en Meetup, o de llevarlas al evento.

Algunas ideas para discutir:

  • Calendario de eventos cerveceros
  • Mejoras de interfaz
  • Herramientas para colaboración de usuarios (sumar locales, correcciones, etc.).
  • Pasar a Ionic para publicar en appstores
  • Nuevas categorías de información (ej. café de especialidad)
  • Categoría “insumos para cerveza casera” en el mapa
  • Hacer al sitio accesible (estándar W3C)
  • Cambiar mapa por uno abierto y agregar geolocalización y búsqueda de direcciones

Se busca:

Este proyecto necesita gente con ganas de sumarse y destinar algo de su tiempo libre durante el sábado 1º y algunos ratos extra en las semanas siguientes, que seguramente seguiremos puliendo detalles vía web.
Todo por la excelente causa de facilitarnos a todos encontrar más y mejor cerveza artesanal, allí donde se encuentre en Uruguay 😁🍻

Precisamos gente con muchos talentos, como:

  • Desarrollo web/HTML/Javascript
  • Trabajo con mapas
  • Diseño web/maquetado/frontend
  • Ionic/Desarrollo app móviles
  • Recolección y subida de datos

¿Qué va a pasar el sábado?

Vamos obviamente a trabajar en la app, aunque es probable que no terminemos el mismo sábado. El objetivo es acordar el plan, dar los primeros pasos y marcar cómo avanzamos de ahí en adelante. Quienes quieran y puedan comprometerse para seguir, seguiremos en contacto mediante Slack para terminar el proyecto y lanzarlo.

La pregunta que todos se están haciendo:

¡Claro que va a haber cerveza! Shelter nos va a invitar la primera ronda 👏 y DATA la segunda 😎

¿Te divierte?

Ya podés empezar a participar entrando a dondepinta.uy, probándola, pensando qué más te gustaría que tuviera y proponiendo ideas en los comentarios 😉

Inscribite Ya

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

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

Si llevo Kanban, también llevo los principios ágiles

noviembre 13, 2018 07:11

Recientemente se está hablando de si kanban es una metodología ágil o no. Creo que la primera vez que me lo plantee fue al leer una serie de magníficos posts de Michael Sahota sobre la cultura de las organizaciones. NOTA: Es realmente curioso que un tema tan específico haya llegado a una revista como Forbes. Steve Denning, al que debeis seguir atentamente si no lo haceis ya, está "fusilando" las

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

Variable not found

Establecer textos por defecto y localizados en validaciones de ASP.NET Core MVC

noviembre 13, 2018 07:55

ASP.NET Core MVCEn el post anterior veíamos cómo personalizar los mensajes de error generados durante el proceso de binding, y cómo aprovechar las posibilidades que nos brinda el framework para introducir textos localizados en esos puntos.

Hoy seguiremos profundizando en este tema, pero esta vez nos centraremos en modificar los textos por defecto de las anotaciones de datos y hacer que simplemente decorando una propiedad con un atributo como Required consigamos obtener mensajes de error localizados y a nuestro gusto, sin necesidad de establecer el ErrorMessage de forma explícita y, en algunos casos, ni siquiera tener que indicar dicho atributo.

Personalizando los proveedores de metadatos de validación

Los proveedores de metadatos de validación son componentes que se registran durante la configuración de los servicios de MVC, y son invocados por el framework para obtener las anotaciones o atributos de validación de propiedades.

Estos componentes, que implementan IValidationMetadataProvider, tienen la siguiente pinta:
public class CustomValidationMetadataProvider : IValidationMetadataProvider
{
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
// Configurar metadatos de validación aquí
}
}
El método CreateValidationMetadata() es invocado una única vez para cada objeto, parámetro o propiedad (el resultado es cacheado) del que se desee obtener información de validación. A través del argumento context podremos conocer información sobre el elemento evaluado y establecer sus metadatos de validación.

Esto podemos usarlo, por ejemplo, para añadir una anotación [Required] preconfigurada a todas las propiedades de tipo valor (int, long, DateTime, etc.) que no hayan sido previamente decoradas con dicha anotación:
public class CustomValidationMetadataProvider : IValidationMetadataProvider
{
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
// Lets add a [Required] annotation to all value types
if (context.Key.ModelType.GetTypeInfo().IsValueType
&& !context.ValidationMetadata.ValidatorMetadata.OfType<RequiredAttribute>().Any())
{
context.ValidationMetadata.ValidatorMetadata.Add(
new RequiredAttribute() { ErrorMessage = "Hey, este campo es obligatorio" }
);
}
}
}
Fijaos que de esta forma tan sencilla estamos modificando el mensaje por defecto del framework para obligatoriedad implícita en este tipo de campos. Si quisiéramos aplicar los cambios, simplemente deberíamos añadir el siguiente código al ConfigureServices() de nuestra aplicación:
services.AddMvc(opt =>
{
opt.ModelMetadataDetailsProviders.Add(new CustomValidationMetadataProvider());
});

Estableciendo mensajes de error por defecto para las anotaciones

Jugando con el ejemplo anterior un poco, podemos ver que establecer un mensaje por defecto para las validaciones más habituales no es nada complicado. En la colección _context.ValidationMetadata.ValidatorMetadata podemos encontrar las anotaciones de validación asociadas al elemento evaluado, por lo que simplemente tendremos que recorrerlas y establecer sus propiedades como más nos convenga:
public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
// First, lets add a [Required] annotation to all value types
if (context.Key.ModelType.GetTypeInfo().IsValueType
&& !context.ValidationMetadata.ValidatorMetadata.OfType<RequiredAttribute>().Any())

// Now, set the ErrorMessage for all validation attributes
var validationAttributes = context.ValidationMetadata.ValidatorMetadata.OfType<ValidationAttribute>();
foreach (var validationAttribute in validationAttributes)
{
if(validationAttribute is StringLengthAttribute)
{
validationAttribute.ErrorMessage = "Hey, este campo es obligatorio";
}
else if(validationAttribute is RangeAttribute)
{
validationAttribute.ErrorMessage = "Introduce valores entre {1} y {2}";
}
[...] // Set ErrorMessage for other validations
}
}
Y obviamente, si le damos una vuelta de tuerca más podemos utilizar este mismo enfoque para establecer los mensajes de texto localizados, aunque esto nos costará un poco más de trabajo.

Localizando los mensajes de validación por defecto

Lo primero que vamos a hacer es crear un archivo RESX con los mensajes de validación al que, por ejemplo, llamamos SharedResources.Resx:

El archivo de recursos

Hay varias cosas a tener en cuenta:
  • La ubicación del archivo puede ser cualquiera. En nuestro caso, lo introduciremos en una carpeta llamada "Resources" en el raíz del proyecto.
     
  • En las propiedades del archivo archivo RESX debemos establecer su custom tool al valor "PublicResXFileCodeGenerator". Esto hará que se genere automáticamente una clase que facilita el acceso a los recursos localizados en el archivo {NombreDeTuResx}.Designer.cs; en nuestro ejemplo, el nombre de la clase es SharedResources.
     
  • Por convención, las claves de los recursos coincidirán con el nombre del atributo de validación, pero eliminando el sufijo "Attribute". Así, para la anotación [Required], el texto localizado lo encontraremos bajo la clave "Required" en el RESX, "Range" para la anotación [Range] y así con todos.
Obviamente, podemos añadir traducciones en archivos RESX adicionales, como SharedResources.en.resx, SharedResources.fr.resx, etc. En estos casos, no será necesario modificar su custom tool.
A continuación, vamos a hacer que el proveedor de metadatos de validación configure correctamente los mensajes por defecto atendiendo a las convenciones y acciones anteriores.

Como puede verse en el siguiente código, estamos modificando nuestro proveedor de metadatos para que reciba en el constructor el tipo de la clase asociada al archivo RESX donde hemos depositado nuestros mensajes de error, y usamos esta información para configurar el mensaje por defecto del validador:
public class CustomValidationMetadataProvider : IValidationMetadataProvider
{
private readonly ResourceManager _resourceManager;
private readonly Type _resourceType;

public CustomValidationMetadataProvider(Type type)

{
_resourceType = type;
_resourceManager = new ResourceManager(type.FullName, type.GetTypeInfo().Assembly);
}

public void CreateValidationMetadata(ValidationMetadataProviderContext context)
{
if (context.Key.ModelType.GetTypeInfo().IsValueType
&& !context.ValidationMetadata.ValidatorMetadata.OfType<RequiredAttribute>().Any())
{
context.ValidationMetadata.ValidatorMetadata.Add(new RequiredAttribute());
}

var validationAttributes = context.ValidationMetadata
.ValidatorMetadata.OfType<ValidationAttribute>()
foreach (var validationAttribute in validationAttributes)
{
if (validationAttribute.ErrorMessageResourceName == null
&& validationAttribute.ErrorMessageResourceType == null)
{
// By convention, the resource key will coincide with the attribute
// name, removing the suffix "Attribute" when needed
var resourceKey = validationAttribute.GetType().Name;
if (resourceKey.EndsWith("Attribute"))
{
resourceKey = resourceKey.Substring(0, resourceKey.Length - 9);
}

// Patch the "StringLength with minimum value" case
if (validationAttribute is StringLengthAttribute stringLength
&& stringLength.MinimumLength > 0)
{
resourceKey = "StringLengthIncludingMinimum";
}

// Setup the message if the key exists
if (_resourceManager.GetString(resourceKey) != null)
{
validationAttribute.ErrorMessage = null;
validationAttribute.ErrorMessageResourceType = _resourceType;
validationAttribute.ErrorMessageResourceName = resourceKey;
}
}
}
}
}
Hecho esto, ya sólo nos queda suministrar la clase apropiada durante el registro de servicios:
services.AddMvc(opt =>
{
opt.ModelMetadataDetailsProviders.Add(
new CustomValidationMetadataProvider(typeof(SharedResources)));
// "SharedResource" is the class generated from the RESX file
})

Por último

Por si queréis ver todo esto en funcionamiento, o incluso copiar y pegar algo de código para vuestros proyectos, he dejado en Github una demo funcional de lo descrito en este artículo y el anterior, por lo que tenéis una solución con la localización y personalización de mensajes resuelta por completo :)

https://github.com/VariableNotFound/i18n-validations

Publicado en Variable not found.

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

Variable not found

Enlaces interesantes 337

noviembre 12, 2018 08:05

Enlaces interesantes Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

Web / HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Otros

Publicado en: www.variablenotfound.com.

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

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

Scrum master a tiempo completo: 42 Tareas

noviembre 11, 2018 09:18

Uno de los artículos que más referencio en mi curso de Scrum cuando hablo de las labores del Scrum Master es: 42-tasks-for-a-scrum-masters-job. Por alguna razón, todo el mundo parece entender que el Product Owner es un trabajo a tiempo completo, o ser miembro de un equipo también, pero que probablemente el rol del Scrum Master puede ser realizado a media jornada o incluso menos. El scrum master

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

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

Agile Fluency Model en castellano

noviembre 11, 2018 09:17

He publicado el modelo de fluidez ágil en castellano. Este modelo desarrollado por James Shore y Diana Larsen proporciona una interesante perspectiva sobre el camino al agilismo por los equipos. Modelo de fluidez ágil.

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

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

Marco Scrum

noviembre 11, 2018 09:17

Marco de Scrum Curso de Scrum: Contactanos si estás interesado.

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

Navegapolis

Motivación laboral, cultura empresarial y estilo de liderazgo

noviembre 11, 2018 03:01

motivadoLa motivación laboral es el estímulo que anima a la persona a producir el resultado esperado de su trabajo. Es un factor clave —quizá el más importante— del éxito de las empresas del conocimiento. 

En el siguiente esquema intento resumir en un vistazo relaciones para tener en cuenta, entre estilos de liderazgo y cultura empresarial y la división habitual de motivación extrínseca e intrínseca.  

motivacion laboral curltura de empres y liderazgo 

 

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

Blog Bitix

Desempaquetado y análisis de monitor Benq PD2700Q

noviembre 10, 2018 09:00

La siguiente pieza que he adquirido para completar lo que será mi ordenador personal en casa es el monitor que usaré en mayor medida para tareas ofimáticas, navegar por internet, quizá ver alguna película y en menor medida quizá también algún juego. De entre las opciones que buscaba inicialmente era que fuese IPS por los colores, de 27 pulgadas y con ese tamaño de pantalla con al menos resolución QHD. Hay muchos modelos entre los que elegir y con variaciones de precios notables en aparentemente las mismas características, de 32 pulgadas, ultrapanorámicos, UHD, orientados más a gaming, …

Benq

Aún no se está comercializando en España el ordenador que he decidido comprar, un Intel NUC entre las opciones entre portátil, ITX o NUC que evalué hace ya más de seis meses. Espero que no quede mucho hasta que haya disponibilidad en Amazon del Intel NUC8i5BEK Bean Canyon, al menos está listado y en Amazon EEUU ya hay disponibilidad. A pesar de esto ya estoy comprando las otras piezas que en cualquier caso necesito como teclado y ratón inalámbrico, alfombrilla para la mesa y ahora otra pieza más importante, el monitor.

Ya con unas semanas habiendo usado el teclado y ratón V7 CKW200 acepto que el ratón no sea totalmente fiable y un peligro hacer cosas importantes con él al arrastrar y soltar ya que falla en ocasiones, quizá compre un ratón tradicional con cable si me da muchos problemas. He comprobado que se pueden tener dos ratones conectados simultáneamente, funcionado ambos. El teclado funciona perfectamente y estoy encantado con él, solo por el teclado no los he devuelto. Sin embargo, en el monitor con algún defecto de píxeles muertos o retención de imagen si haría la devolución, por fortuna una vez me ha llegado y lo he comprobado con unas imágenes de prueba para detectar píxeles muertos o vagos no he visto ninguno así que el panel me ha llegado perfecto estado.

He mirado varias opciones y modelos de monitor y aún así tampoco estoy seguro de que la que finalmente he elegido sea la mejor en el mercado. Leyendo comentarios y opiniones de otros usuarios en todos los modelos siempre hay alguno que ha tenido problemas, en el caso de los monitores de píxeles muertos, vagos, fugas de luz, hasta retención de imágenes. Las fugas de luz es algo normal en la tecnología actual de monitores y televisores con retroiluminación led en los bordes de la pantalla, apreciable en condiciones de poca luz e imágenes oscuras.

Opciones

Partía de unos requerimientos de que fuera IPS por su calidad de colores, de 27 pulgadas y con resolución 2K o 2560x1440 en un rango de precio de hasta unos 300€. En otro orden de cosas menos importantes que valoraba era que tuviese conexión HDMI y DisplayPort, marcos finos y compatibilidad con soporte VESA. Hay opciones en formato ultrapanorámico de 29 pulgadas y resolución 2560x1080. He visto alguno de estos pero finalmente he preferido que tenga más resolución vertical y no sea tan ancho para no tener que girar la cabeza.

Con estos requerimientos iniciales me quedé con unos pocos candidatos. Básicamente monitores 2K en formato de 27 pulgadas e IPS muy similares en cuanto a características, son IPS de resolución QHD 2560x1440, tienen entrada HDMI 1.4 y DisplayPort 1.2, soporte VESA 100x100, con algunas pequeñas diferencias en cuanto a especificaciones. El Benq PD2500Q es de 25 pulgadas, panel de 8 bits y tiene sensor para adaptar automáticamente el brillo a la luz ambiental, posee un hub USB 3.1 de cuatro puertos, el Benq PD2700Q posee un hub USB 2.0 de dos puertos y un panel de 10 bits. Los dos modelos de Benq son muy parecidos sino iguales en cuanto al panel de monitor, varía en cuanto al marco que en el caso del GW s brillante y en el PD mate, también cambian las conexiones que tienen el GW tiene D-Sub, DVI-DL (Dual Link), DisplayPort, HDMI y el PD DisplayPort, mini DisplayPort y HDMI. Ambos soportan 100% Rec.709 y sRGB, sin parpadeos, son regulables en altura, gira, inclinación y pivotable para ponerlo en modo vertical. Tienen un precio similar.

De las opciones con las que me quedé todos con panel de 10 bits (u 8 + 2, salvo el PD2500Q que es de 8), función de no parpadeo, filtro de luz azul y altavoces sencillos. El PD2700Q es un modelo más antiguo, los PD2500Q y PD2700U son más modernos y se notan en los biseles más reducidos y el hub con una versión mayor de USB.

Tanto el Viewsonic VG2719-2K como el V7 L27HAS2K-2E tienen un precio más económico pero en cuanto a especificaciones técnicas no eran malas. Sin embargo, de estos modelos no hay mucha información ni de análisis en páginas web, desempaquetados en Youtube, ni opiniones en Amazon. Del V7 hay dos opiniones en Amazon alemania y ambas no son buenas y coinciden en que el texto y la imagen se ve borrosa. La falta de información me ha hecho tener reticencias con estos modelos.

El Viewsonic es un modelo del 2015 con dos HDMI 1.3 y DisplayPort 1.2. El Benq PD2700Q es un modelo del año 2016, tiene HDMI 1.4 y DisplayPort 1.2, no es muy relevante ya que de HDMI 1.3 a 1.4 varía la resolución soportada pero en estos monitores en ambos casos es suficiente cualquiera de ellas en esta resolución y a 60 Hz. El Viewsonic es una opción algo menor pero considerando su precio y el del Benq hubiese sido otra buena elección. El Viewsonic en contra tiene la falta de información aún habiendo un comentario positivo en Amazon.

Los otros candidatos destacables que he visto han sido alguno en formato panorámico.

Los monitores UHD para un tamaño de 27 pulgadas a máxima resolución el texto y elementos gráficos se ven muy pequeños con lo que para uso ofimático hay que escalar a una más pequeña. Hay en un rango de precios muy amplio, desde 200€ a 700+€. Dos modelos en 4K de los anteriores son el Benq PD2700U que es el que hubiese elegido en caso de haber optado por UHD, el Benq BL2711U, el LG 27UK600-W o el LG 27UK650-W, nótese la U en estos modelos. Estos monitores aún siendo UHD no llegan a los 192 dpi, se quedan en 163, para considerarse HiDPI ni tienen una pantalla retina. Otras opciones son optar por 32” UHD y en formato panorámico.

En monitores de 27 pulgadas la recomendación en varios foros es de 2K. Menos con 1K o full hd es ver píxeles como centollos, más es ver las cosas pequeñas a máxima resolución para tareas ofimáticas y es habitual tener que escalar a una resolución más baja aunque tenga mayor definición aprovechando la mayor densidad de píxeles o DPI. Estos son unos hilos en foros y dos artículos que me han parecido interesantes y que me han resuelto alguna duda o confirmado lo que ya suponía.

En PcComponentes los precios no varían tanto pero en Amazon en algunos productos si hay notables diferencias entre pocos días o alguna semana, sobre todo en productos de tecnología. Para conocer si el precio actual de un producto es alto, bajo o está bajando la página CamelCamelCamel permite introduciendo la URL del producto de Amazon mostrar una gráfica con la evolución del precio.

Evolución del precio del monitor Benq PD2700Q

Benq PD2700Q

Teniendo en cuenta todo finalmente me he decidido por el Benq PD2700Q. Tiene buena conectividad, sin conexiones obsoletas como en el caso del Benq GW. Es un monitor orientado a diseñadores aunque mi uso es principalmente en tareas ofimáticas y posiblemente algún juego ocasional. Los monitores ultra anchos no los he usado pero se me hacen demasiado anchos obligando a mover la cabeza para cubrir su área de visualización. Ya con 27” el monitor se me hace grande aunque me acostumbraré a este tamaño pasado un tiempo, para la gente que aún quiere un monitor más grande los hay de 32” aunque en estos ya hay que ir a una resolución de UHD 3840x2160.

Sus especificaciones más destacables son:

  • Tamaño de la pantalla, 27
  • Tipo de panel, IPS
  • Brillo, 350 cd/㎡
  • Colores de la pantalla, 1.07 billion colors
  • Área de la pantalla (mm), 596.74(H) x 335.66(V)
  • Distancia entre píxeles (mm), 0.2331 y PPP, 108.79
  • Profundidad de color, 10bits
  • HDMI, HDMI (v1.4) x 1
  • DisplayPort, DisplayPort (v1.2) x 1
  • Mini DisplayPort, Mini DisplayPort (ver1.2) x 1
  • Concentrador USB 2.0, upstream x 1 y downstream x 2
  • Fuente de alimentación, built-in
  • Dimensiones (altura × anchura × profundidad, mm) (sin base), 379.5 x 641.0 x 56.5. Con base (posición alta): 547.3 x 641.0 x 239.6. Con base (posición baja): 417.3 x 641.0 x 239.6

Con 27 pulgadas y una resolución de 2560x1440 su área de visualización sus PPI son:

  • Display size (27”, QHD): 23.53” × 13.24” = 311.5in² (59.77cm × 33.62cm = 2009.68cm²) at 108.79 PPI, 0.2335mm dot pitch, 11834 PPI²

Comprando con un monitor de 23 pulgadas y una resolución de 1920x1080 y con un monitor de 3840x2160 en 27 pulgadas estos números son:

  • Display size (23”, FHD): 20.05” × 11.28” = 226.04in² (50.92cm × 28.64cm = 1458.33cm²) at 95.78 PPI, 0.2652mm dot pitch, 9174 PPI²
  • Display size (27”, UHD): 23.53” × 13.24” = 311.5in² (59.77cm × 33.62cm = 2009.68cm²) at 163.18 PPI, 0.1557mm dot pitch, 26627 PPI²

Hay algo de confusión cuando se menciona 2K en monitores, en realidad el término más apropiado es QHD (quad hd), que es 4 veces la resolución HD (1280x720), dos en alto y dos en ancho. Lo mismo ocurre con los monitores 4K que en realidad la denominación más acertada es UHD (ultra hd) y son cuatro veces la resolución FHD, dos en cada medida, 3840x2160.

En YouTube hay unos cuantos vídeos analizando este mismo monitor u otros parecidos que me han servidor para aclarar dudas.

Desempaquetado

El monitor está perfectamente embalado en una caja impresa con las principales características del monitor, dentro lleva sus respectivos protectores e incluye los siguientes elementos. Que incluya una cable DisplayPort, HDMI y USB es de agradecer para no tener que comprarlos aparte y poder usar el monitor desde el primer momento.

  • Monitor PD2700Q.
  • Guía de inicio y CD con software.
  • Cable de alimentación.
  • Cable HDMI.
  • Cable Mini DisplayPort a DisplayPort.
  • Cable USB para conectar al monitor y ordenador.
  • Gancho para colgar cascos en la parte trasera.
Desempaquetado
Contenido de la caja

El monitor posee unos biseles bastante amplios de 2 cm sin embargo al ser el marco negro con acabado mate no distraen mucho ni son molestos estéticamente. Aún así la tendencia es que estos biseles sean reducidos pero este modelo de monitor ya tiene algunos años.

Pantalla
Pantalla parte trasera

Este modelo de monitor incluye dos puertos USB 2.0 en la parte trasera, tiene utilidad en un ordenador de escritorio que esté debajo de la mesa para tener unos puertos USB más cerca del escritorio con un concentrador USB.

Conexiones

Los botones para ajustar los parámetros del monitor y OSD están detrás del monitor y el piloto indicador de encendido en un lateral de modo que en un ambiente de luz oscuro no molesta.

Configuración en pantalla (OSD)

Habitualmente estoy trabajando con un portátil MacBook Pro con pantalla retina y anteriormente he usado un Sony VAIO E 14P con panel TN de resolución 1600x900 y un monitor LG 2351 también TN de resolución 1920x1080. Con respecto a los dos paneles TN que he usado hasta el momento la mejora es apreciable en sus ángulos de visión, colores más intensos, este es el motivo de requerir un panel tipo IPS. Si tengo que destacar algo del MacBook es la pantalla, la pantalla del Benq cumple pero al no ser retina la diferencia es clara, aún conservando buena vista la definición del texto no es mala pero no llega a la calidad de la pantalla del MacBook, eso si en cuanto a colores no tiene nada que envidiar a la pantalla del Mac.

Análisis

Después de un tiempo de uso ya veré como son las impresiones, las primeras impresiones son buenas aunque viniendo de una pantalla retina no tanto. Si el texto resultase demasiado pequeño según las preferencias el MacBook permite reducir la resolución a 2048x1152, 1600x900, 1080p o 720p. Si se desea un monitor UHD que tenga HDMI 2.0 a 60 Hz y en esa resolución casi pensando en las 32”. Con el monitor externo no tengo que tener la pantalla tan cerca ni tener la cabeza agachada como con el portátil con lo que la postura corporal y de espalda es mejor para evitar cansancios y dolores que con el uso continuado pueden surgir, la verdad es que lo he notado ya desde el primer momento. Otra cosa que me gusta es que su fuente de alimentación está integrada con lo que no hay un transformador encima de la mesa y sus respectivos cables.

Con la resolución de 2560x1440 se puede visualizar más información al mismo tiempo y es más cómodo trabajar en una misma aplicación a pantalla completa o poder ver dos aplicaciones en primer plano. Se nota y mucho el espacio extra en la pantalla al trabajar con varias de las aplicaciones que uso habitualmente como editor de textos, navegador web y terminal.

La primera impresión es que un monitor de 27” es grande pero ya me han comentado que es cuestión de acostumbrase y que dentro de poco me parecerá lo normal o incluso pequeño, de hecho ya hay monitores de 32” para esa gente que aún lo quiere más grande. A la espera del Intel NUC tendré monitor que me parece grande pero un equipo que cabe en la palma de la mano. Comparado con la pantalla retina del MacBook las cosas no se ven tan definidas, un monitor de 27” con pantalla retina no he visto salvo los Mac. Con 27” en UHD tampoco se considera HiDPI y se queda en 163 dpi contra los 226 del Mac. Para que un monitor de 27” tenga similar definición que con una pantalla retina la resolución debería ser del doble, 5120x2880, con esto alcanzaría unos 216 dpi y ya se consideraría HiDPI aunque habría que escalar la resolución en la configuración del ordenador ya que en la resolución nativa el texto se vería muy pequeño.

Imagen en la pantalla

Este monitor tiene los botones de ajustes detrás del monitor marcados con unos indicadores en el frontal para encontrar su posición, en algún sitio se comenta que esto es algo incómodo para acertar a pulsar el botón adecuado. En lo que probado no me ha resultado nada incómodo y los botones me han parecido perfectos. Dado que el monitor tiene altavoces se le puede enviar sonido por el mismo cable HDMI que envía la señal de vídeo y luego con un jack de 3.5mm desde el monitor enviarlo a unos altavoces que den mejor calidad de sonido, esto me permite conectar un cable menos al ordenador y que la mesa no quede cubierta de cables. Aunque con esta opción al menos un Mac no permite ajustar el volumen hay que hacerlo desde los altavoces, por fortuna tengo unos altavoces con regulador de volumen.

Las fugas de luz o bleeding es otro de los posibles problemas de un monitor, por la tecnología de retoiluminación led hace que en condiciones oscuras los negros aparezcan como grises y que en los bordes de la pantalla se noten zonas un poco iluminadas al no bloquearse por completo la retoiluminación. Ya era consciente de este problema y no me sorprende que el que me ha llegado a mi tenga, no se si será mucho o poco porque no puedo comparar pero no me ha parecido excesivo. Sin píxeles muertos o vagos sin mucha fuga de luz me doy por contento. Por el momento tampoco he notado retención de imagen.

Bleeding en ambiente oscuro

Con una resolución QHD hay más espacio que en una Full HD que agradecen los programadores, diseñadores y gente que pasa mucho tiempo delante de una pantalla trabajando en tareas relacionadas con múltiples documentos o aplicaciones. Por ejemplo, con un IDE es posible visualizar el código fuente de dos archivos o visualizar en primer plano el IDE, navegador y terminal al mismo tiempo. Las personas que trabajan con gráficos pueden visualizar imágenes más grandes ya sea en Photoshop, GIMP, Inkscape, OpenShot y herramientas similares.

Espacio en resolución QHD

En un futuro probablemente aparezcan nuevos monitores que soporten HDR y con resolución retina que supondrá otro salto en la calidad de la imagen. Me va a quedar la duda de si un monitor con resolución UHD de 27 pulgadas bajando la resolución sería una buena opción en cuanto a definición de texto, se suele comentar que no, usar una resolución escalada del panel no es lo mismo que en la resolución nativa pero me gustaría experimentarlo. Al menos en el Mac de resolución nativa 2560x1440 cambiar a 1600x900 se sigue viendo perfectamente el texto y en este Benq de 27” bajando la resolución no aprecio que el texto se vea peor. En cualquier caso los monitores UHD por el momento tienen un precio mayor, más del doble o al menos unos cientos de euros más según el modelo comparado, y este Benq PD2700Q es de buena calidad acorde a su precio mayor que algunos otros QHD y que muchos Full HD pero de todas formas asequible para alguien que le da buen uso.

Detección de píxeles muertos o vagos

Es recomendable realizar una primera prueba al monitor para buscar píxeles muertos, que no se encienden, o vagos, que permanecen encendidos todo el tiempo con el color de un componente. El siguiente test se compone de varias imágenes en los colores básicos (negro, blanco, azul, rojo y verde) que visualizadas individualmente a pantalla completa muestran si algún pixel es defectuoso. Que haya un pixel defectuoso es molesto ya que basta para que haya solo uno y sepas donde está entre los casi 3 millones que tiene el monitor para que estés fijándote constantemente en él, más si se encuentra en el área central de la pantalla. También conviene informarse antes de comprar acerca de la garantía que ofrece cada fabricante respecto a los píxeles muertos, algunos toleran y no aceptan la devolución con cierto número de píxles que permanecen encendidos, apagados o algún subpixel estropeado.

Vídeos de prueba

Para probar la calidad del monitor (o televisor) en cuanto a colores o resolución en YouTube hay varios, uno de ellos de ejemplo es este pudiendo seleccionar la calidad desde 720p (HD), 1080p (FHD, 1K), 1440p (QHD, 2K) y 2160p (UHD, 4K). Lógicamente para apreciar las diferencias de calidad con respecto a una resolución más baja hay que tener un monitor o televisor que soporte la resolución más alta. Esto da una idea de como se ve una película, vídeo o imágenes. Las tareas ofimáticas no es lo mismo que ver vídeos, en UHD y usando escalado el texto gracias a su mayor PPI en el mismo tamaño que un QHD se ve más definido y claro. Sin embargo, el escalado aún presenta problemas ya que no todas las aplicaciones está preparadas.

Soporte VESA

En todos los modelos he buscado que sea compatible con la norma VESA 100x100 por si en un futuro lo pongo en uno soporte de modo que el pie no ocupe sitio en la mesa. La peana de algunos monitores no soportan ajustes en altura, inclinación, ajuste laterales o modo vertical pero con un soporte compatible con VESA se puede suplir la carencia además de evitar que la peana ocupe espacio. Con un soporte tienen la ventaja de que se puede ajustar la posición del monitor muy fácilmente. Estos soportes funcionan con una mordaza para anclarlos a la mesa.

Algunos soportes compatibles con VESA que he mirado son los siguientes.

Hub de puertos USB

Teniendo el monitor un hub USB 2.0 no merece la pena un hub externo USB 3.0 o 3.1, sin embargo, con otros monitores puede ser una opción como con el PD2500Q o el PD2700U. Los discos duros externos que tengo son USB 2.0 y por limitaciones del disco duro si fuesen 3.0 el rendimiento adicional no se si sería mucho mayor. Hay hubs de 4, 7 y 10 puertos pero con esta candidad de puertos es recomendable que sea alimentado sobre todo si se conectan discos duros externos sin alimentación propia, también hay algunos con interruptor individual de encendido por puerto.

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

Blog Bitix

Novedades de Java EE 8

noviembre 09, 2018 04:00

Java EE
Java

La publicación de Java 9 en agosto de 2017 con la importante novedad de los módulos ha hecho que la publicación de Java EE 8 haya pasado desapercibida. También ha contribuido el hecho de que Oracle haya entregado el desarrollo de futuras nuevas especificaciones de Java EE a la fundación Eclipse con el objetivo de que sea más abierto a otras empresas, Java EE a partir de ahora bajo la fundación Eclipse se denominará Jakarta EE. Pasados algunos meses los servidores de aplicaciones ya están implementando las nuevas novedades de Java EE 8. Algunos de los servidores que ya soportan estas nuevas especificaciones y novedades son Wildfly, Payara Kumuluz o Tomcat 9.

Hay algunas novedades de Java EE 8 entre ellas:

  • Java Servlet 4.0 API con soporte para el protocolo HTTP/2, incluido el soporte para enviar recursos desde el servidor sin que el cliente los haya pedido aún a través del método newPushBuilder() de la clase HttpServletRequest.
  • Soporte para JSON mejorado incluyendo una nueva API de binding.
  • Eventos CDI asíncronos.
  • Una nueva API de seguridad simple, estandarizada y modernizada.
  • Soporte para las nuevas capacidades de Java 8 (pe. Date & Time API, Streams API, mejoras en las anotaciones).

El soporte para HTTP/2 y sus importantes novedades hace que desde Java se puedan aprovechar las mejoras en la segunda versión del protocolo HTTP y se aprovechan las novedades de Java 8. También en Java EE 8 se han actualizado de versión algunas de las especificaciones e incluido alguna nueva como la de la nueva API de seguridad que han de soportar los servidores de aplicaciones para ser compatibles:

  • JSR 366 – Java EE 8 Platform
  • JSR 365 – Contexts and Dependency Injection (CDI) 2.0
  • JSR 367 – The Java API for JSON Binding (JSON-B) 1.0
  • JSR 369 – Java Servlet 4.0
  • JSR 370 – Java API for RESTful Web Services (JAX-RS) 2.1
  • JSR 372 – JavaServer Faces (JSF) 2.3
  • JSR 374 – Java API for JSON Processing (JSON-P)1.1
  • JSR 375 – Java EE Security API 1.0
  • JSR 380 – Bean Validation 2.0
  • JSR 250 – Common Annotations 1.3
  • JSR 338 – Java Persistence 2.2
  • JSR 356 – Java API for WebSocket 1.1
  • JSR 919 – JavaMail 1.6

Hay un tutorial de Java EE 8 y con el ejemplo FirstCup se puede adquirir un buen conocimiento para desarrollar aplicaciones con el lenguaje Java. En el artículo What’s new in Java EE 8 hay unos pocos ejemplos de código con varias de estas novedades. Finalmente, con el traspaso de Java EE a la fundación Eclipse y por motivos de marca registrada el proyecto ha sido renombrado a Jakarta EE. Java EE o ahora Jakarta EE, Microprofile y Spring junto con algunos frameworks especializados son las opciones más utilizadas para realizar aplicaciones en Java en el lado del servidor.

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

Fixed Buffer

Rompiendo los limites: Mocking en las Pruebas Unitarias .Net

noviembre 06, 2018 09:00

MockingRompiendo los limites: Mocking en las Pruebas Unitarias .Net

Hoy por fin os traigo la unión entre las Pruebas Unitarias y la Inyección de Dependencias, el “Mocking“. ¿Que es el “Mocking” te preguntarás?, pues es la técnica utilizada para simular objetos en memoria con la finalidad de poder ejecutar pruebas unitarias.

Esto, es especialmente útil cuando utilizamos recursos externos como bases de datos o servicios de mensajería, o cualquier cosa en general que no queramos o no podemos ejecutar durante las pruebas unitarias.

Sin mas preámbulos, ¡vamos con ello! En primer lugar, he reutilizado el proyecto Entity Framework Core “Code First” para partir de tener el contexto de datos creado. Ademas, he añadido una clase “GeneradorInformes” (la cual cumple el patrón de Inyección de Dependencias en el constructor) y una clase “EmailSender” que implementa la interfaz “IEmailSender”:

Proyecto

//IEmailSender.cs
namespace PostMocking.Model
{
    public interface IEmailSender
    {
        bool Enviar(string Destinatario, string Mensaje);
    }
}

//EmailSender.cs
namespace PostMocking.Model
{
    public class EmailSender : IEmailSender
    {
        public bool Enviar(string Destinatario, string Mensaje)
        {
            //{...}
            return true;
        }
    }
}

//GeneradorInformes.cs
using Microsoft.EntityFrameworkCore;
using PostMocking.Data;
using System.Linq;
using System.Text;

namespace PostMocking.Model
{
    public class GeneradorInformes
    {
        //Propiedad con la dependencia
        private IEmailSender emailSender { get; set; }
        private PostMockingDbContext context { get; set; }

        public GeneradorInformes(PostMockingDbContext context, IEmailSender emailSender)
        {
            this.context = context;
            this.emailSender = emailSender;
        }

        public bool GenerarInforme(string NombreProfesor, string Email)
        {
            //Obtenemos mediante LinQ los datos del profesor
            var Profesor = context.Profesores.Where(x => string.Compare(x.Nombre, NombreProfesor, true) == 0)
                                                .Include(x => x.Cursos)
                                                .ThenInclude(x => x.Alumnos)
                                                .FirstOrDefault();
            //En casode no encontrar nada, salimos
            if (Profesor is null)
                return false;

            //Generamos el informe de alumnos y cursos
            StringBuilder sb = new StringBuilder();
            sb.AppendLine($"El profesor {Profesor.Nombre} imparte los siguientes cursos:");
            foreach (var curso in Profesor.Cursos)
            {
                sb.AppendLine($"\t*{curso.Nombre} con los alumnos:");
                foreach (var alumno in curso.Alumnos)
                {
                    sb.AppendLine($"\t\t*{alumno.Nombre}");
                }
            }

            emailSender.Enviar(Email, sb.ToString());
            return true;
        }
    }
}

//Program.cs
using PostMocking.Data;
using PostMocking.Model;
using System;

namespace PostMocking
{
    class Program
    {
        static void Main(string[] args)
        {
            using (PostMockingDbContext context = new PostMockingDbContext())
            {
                EmailSender emailSender = new EmailSender();
                GeneradorInformes generador = new GeneradorInformes(context, emailSender);
                if (generador.GenerarInforme("FixedBuffer", "jorge_turrado@hotmail.es"))
                    Console.WriteLine("Informe enviado con éxito");
                else
                    Console.WriteLine("Problema al enviar el informe");
            }
        }
    }
}

El resumen del funcionamiento básico, es que GeneradorInformes recibe las dependencias del contexto de datos y el servicio de correo, y al llamar al método “GenerarInforme(string,string)” se obtiene el informe del cursos y alumnos del profesor indicado, y se envía al correo indicado. En caso de que el informe se genere correctamente y se envíe, retornamos un true, en caso contrario un false.

Pruebas Unitarias y Mocking

Generaremos un proyecto de pruebas unitarias en nuestra solución y añadimos a nuestro proyecto de pruebas el siguiente paquete:

Moq.Net

O por consola:

PM-> Install-Package Moq -ProjectName “NombreProyectoPruebas”

Este paquete esta alineado con .NetStandard, en concreto .NetStandard 1.3, por lo que se puede utilizar indistintamente en .Net Framework o en .Net Core. Una vez que lo tenemos todo listo, vamos a crear nuestra prueba unitaria para GeneradorInformes.

using Microsoft.EntityFrameworkCore;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using PostMocking.Data;
using PostMocking.Model;
using System;
using System.Collections.Generic;
using System.Linq;

namespace PruebasUnitarias
{
    [TestClass]
    public class GeneradorInformesTests
    {
        // Como queremos reutilizar los Mock, los declaramos a nivel de clase
        Mock<IEmailSender> emailSender;
        Mock<PostMockingDbContext> DbContext;

        // Creamos los objetos de Mock en el constructor de la clase, para reutilizarlos
        public GeneradorInformesTests()
        {
            // Creamos el mock sobre nuestra interfazde envio de mensajes
            emailSender = new Mock<IEmailSender>();
            // Ante la llamada a su metodo enviar, retornamos un true, pero ademas serializamos al output del test el informe
            emailSender.Setup(m => m.Enviar(It.IsAny<string>(), It.IsAny<string>()))
                       .Returns((string destinatario, string mensaje) =>
                       {
                           Console.WriteLine(mensaje);
                           return true;
                       });

            // Creamos la coleccion que devolverá nuestra base de datos Mockeada
            var profesor = new Profesor { Nombre = "Jorge Turrado", IdProfesor = 1 };
            var curso = new Curso { IdProfesor = profesor.IdProfesor, Ciudad = "Vitoria", Nombre = "Mocking", Profesor = profesor };
            var alumno = new Alumno { IdCurso = curso.IdCurso, Curso = curso, Nombre = "Andres Garcia" };
            curso.Alumnos.Add(alumno);
            profesor.Cursos.Add(curso);
            var Profesores = new List<Profesor>()
            {
                 profesor
            }.AsQueryable();

            // Creamos el mock para la base de datos
            var mockSet = new Mock<DbSet<Profesor>>();
            mockSet.As<IQueryable<Profesor>>().Setup(m => m.Provider).Returns(Profesores.Provider);
            mockSet.As<IQueryable<Profesor>>().Setup(m => m.Expression).Returns(Profesores.Expression);
            mockSet.As<IQueryable<Profesor>>().Setup(m => m.ElementType).Returns(Profesores.ElementType);
            mockSet.As<IQueryable<Profesor>>().Setup(m => m.GetEnumerator()).Returns(Profesores.GetEnumerator());

            // Asignamos el mock de la base de datos al contexto
            DbContext = new Mock<PostMockingDbContext>();
            DbContext.Setup(c => c.Profesores).Returns(mockSet.Object);
        }

        [TestMethod]
        public void GenerarInformeValido()
        {
            // Creamos nuestra clase a testear y le pasamos los objetos mock
            GeneradorInformes generador = new GeneradorInformes(DbContext.Object, emailSender.Object);
            var result = generador.GenerarInforme("Jorge Turrado", "");

            //Comprobamos el resultado
            Assert.AreEqual(true, result, "No se ha podido generar el informe");
        }

        [TestMethod]
        public void GenerarInformeInvalido()
        {
            // Creamos nuestra clase a testear y le pasamos los objetos mock
            GeneradorInformes generador = new GeneradorInformes(DbContext.Object, emailSender.Object);
            var result = generador.GenerarInforme("Pedro Mayo", "");

            //Comprobamos el resultado
            Assert.AreEqual(false, result, "Se ha podido generar el informe");
        }
    }
}

Analicemos la clase de pruebas. En primer lugar, tenemos 2 objetos de tipo “Mock<T>” , declarados a nivel de clase, esto es debido a que vamos a utilizarlos en las 2 pruebas, y así nos ahorramos tener que construirlos 2 veces, aligerando así la carga.

Mock<IEmailSender>

Lo siguiente que tenemos, es el constructor. En él, se inicializan los objetos Mock, vamos de uno en uno:

// Creamos el mock sobre nuestra interfazde envio de mensajes
emailSender = new Mock<IEmailSender>();
// Ante la llamada a su metodo enviar, retornamos un true, pero ademas serializamos al output del test el informe
emailSender.Setup(m => m.Enviar(It.IsAny<string>(), It.IsAny<string>()))
     .Returns((string destinatario, string mensaje) =>
     {
         Console.WriteLine(mensaje);
         return true;
     });

Mediante el método “Setup”, le estamos indicando el comportamiento que debe tener cuando llamemos el método Enviar(string,string) de la interfaz. Cabe destacar que “It.IsAny<string>()” esta indicando que este Mock se aplicara ante cualquier entrada de tipo string, pero podríamos indicarle un string concreto, por ejemplo:

// Creamos el mock sobre nuestra interfazde envio de mensajes
emailSender = new Mock<IEmailSender>();
// Ante la llamada a su metodo enviar, retornamos un true, pero ademas serializamos al output del test el informe
emailSender.Setup(m => m.Enviar("FixedBuffer", It.IsAny<string>()))
     .Returns((string destinatario, string mensaje) =>
     {
         Console.WriteLine(mensaje);
         return true;
     });

Con este segundo código, solo aplicaría el Mock si el primer string es “FixedBuffer”, pudiendo definir así diferentes comportamientos ante diferentes entradas ya que podemos llamar tantas veces al método Setup como queramos.

Mock<PostMockingDbContext>

En este caso, no vamos a generar generar un comportamiento en concreto, sino que vamos a generar un contexto de datos falso. Lo primero para eso, es crear una colección de datos:

var profesor = new Profesor { Nombre = "Jorge Turrado", IdProfesor = 1 };
var curso = new Curso { IdProfesor = profesor.IdProfesor, Ciudad = "Vitoria", Nombre = "Mocking", Profesor = profesor };
var alumno = new Alumno { IdCurso = curso.IdCurso, Curso = curso, Nombre = "Andres Garcia" };
curso.Alumnos.Add(alumno);
profesor.Cursos.Add(curso);
var Profesores = new List<Profesor>()
{
    profesor
}.AsQueryable();

Como se puede ver, simplemente estamos creando la colección que luego convertiremos en el contexto de datos. Una vez que tenemos los datos, vamos a crear el Mocking del DbSet, que como se puede ver, simplemente consiste en relacionar la colección que acabamos de crear con el objeto Mock<DbSet<Profesor>>.

var mockSet = new Mock<DbSet<Profesor>>();
mockSet.As<IQueryable<Profesor>>().Setup(m => m.Provider).Returns(Profesores.Provider);
mockSet.As<IQueryable<Profesor>>().Setup(m => m.Expression).Returns(Profesores.Expression);
mockSet.As<IQueryable<Profesor>>().Setup(m => m.ElementType).Returns(Profesores.ElementType);
mockSet.As<IQueryable<Profesor>>().Setup(m => m.GetEnumerator()).Returns(Profesores.GetEnumerator());

En el caso de necesitar mockear más tablas, solo tendríamos que repetir el proceso con todas las tablas que nos interese. Una vez que hemos acabado de generar todos los datos que tendrá nuestro contexto, asignamos los DbSet mokeados, vamos a crear por fin nuestro “Mock<PostMockingDbContext>”:

DbContext = new Mock<PostMockingDbContext>();
DbContext.Setup(c => c.Profesores).Returns(mockSet.Object);

Esto lo conseguimos diciéndole en el Setup que ante un acceso a la tabla “Profesores”, devuelva el objeto mock que acabamos de crear.

Una vez que tenemos creados nuestros dos objetos “Mock<T>” para las pruebas, veamos las pruebas:

[TestMethod]
public void GenerarInformeValido()
{
    // Creamos nuestra clase a testear y le pasamos los objetos mock
    GeneradorInformes generador = new GeneradorInformes(DbContext.Object, emailSender.Object);
    var result = generador.GenerarInforme("Jorge Turrado", "");

    //Comprobamos el resultado
    Assert.AreEqual(true, result, "No se ha podido generar el informe");
}

[TestMethod]
public void GenerarInformeInvalido()
{
    // Creamos nuestra clase a testear y le pasamos los objetos mock
    GeneradorInformes generador = new GeneradorInformes(DbContext.Object, emailSender.Object);
    var result = generador.GenerarInforme("Pedro Mayo", "");

    //Comprobamos el resultado
    Assert.AreEqual(false, result, "Se ha podido generar el informe");
}

Como se puede ver, el funcionamiento es exactamente igual que sería en nuestro proyecto en producción, pero en vez de pasarle el PostMockingDbContext y EmailSender, las cuales pueden no estar disponibles para las pruebas, le pasamos sus respectivos Mock, de modo que siempre podemos prever el comportamiento, pudiendo hacer pruebas que sean fiables, sin necesidad de que se tenga acceso a recursos externos. Esto es especialmente útil si se emplean herramientas de CI como Travis o AppVeyor, ya que no van a tener acceso a esos recursos. De hecho, os dejo el enlace a una colaboración que hice hace unos meses hablando sobre AppVeyor y la integración continua.

Ademas, como dato adicional, podemos ver la salida de consola del test unitario, donde ademas de saber que se ha ejecutado correctamente, podríamos ver el reporte:

report

Como habitualmente, dejo el enlace de GitHub para descargar el proyecto y poder probarlo, en este caso, desarrollado en .Net Core. Para ampliar información, dejo también la documentación de Moq.Net.

**La entrada Rompiendo los limites: Mocking en las Pruebas Unitarias .Net se publicó primero en Fixed Buffer.**

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

Variable not found

Enlaces interesantes 336

noviembre 05, 2018 08:34

Enlaces interesantes
Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en: www.variablenotfound.com.

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

Blog Bitix

Formatear con color sentencias SQL o código fuente en la terminal con Java y Jansi

noviembre 03, 2018 10:30

Java

Usando la librería Jansi es posible hacer que un programa Java emita texto en color en la terminal. Emitir color en la terminal sirve para identificar más fácilmente y visualmente ciertas partes del texto, por ejemplo, emitiendo en rojo mensajes importantes o con color amarillo de fondo algún dato. Una utilidad práctica es formatear con color una sentencia SQL que se va a ejecutar en una aplicación a modo de traza o con colores el código fuente de un archivo Java u otro tipo de archivo de texto.

Una forma sencilla para formatear con colores un archivo de código fuente Java o una sentencia SQL sin llegar a hacer un procesador de sintaxis de ese lenguaje o formato es utilizar expresiones regulares y grupos de captura. En ambos casos hay partes que son palabras claves, números o cadenas en definitiva elementos que se deseen destacar. Con las clases Pattern y Matcher de Java se van obteniendo los diferentes grupos de captura de forma secuencial. Según sea el grupo capturado del elemento actual se utiliza Jansi para formatearlo con el color que le corresponde.

Una formateador simple para cada uno de estos casos serían los siguientes donde se utiliza una expresión regular con diferentes grupos de captura con nombre. El primer caso es para formatear con color una sentencia SQL sencilla en el método printSql() y el segundo el programa Hola mundo de Java en el metodo printJava().

El resultado en la terminal es el siguiente.

Formateado de sentencia SQL y código Java

Este ejemplo es una aplicación útil de las expresiones regulares. En este caso he usado Jansi para emitir en la terminal texto con color pero de forma similar esto se puede usar para formatear en una web el mismo texto transformándolo y generando el HTML con las clases CSS adecuadas.

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 el comando ./gradlew installDist && ./build/install/JavaRegexFormatter/bin/JavaRegexFormatter.

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

Fixed Buffer

¡Reconocimiento MVP Award 2018!

noviembre 01, 2018 07:12

MVP Award

Hoy por la tarde he recibido un correo de Microsoft que decía:

mvp

Estoy muy ilusionado de recibir este reconocimiento a la labor de este tiempo en MSDN y estas ultimas semanas con el blog, solo espero poder seguir aprendiendo y estar a la altura de este galardón.

Es todo un honor para mi poder entrar a formar parte de este grupo de grandes profesionales, entre los que están muchas personas que admiro por su gran labor, como el gran Leandro Tuttini, el señor Williams Morales o don José M. Aguilar, cuyo blog Variable not found (de obligada lectura, mis dieces) me incito a abrir el mio.

Muchas gracias al programa MVP de Microsoft por darme este premio.

Muchas gracias a los compañeros que me aguantáis día a día me aguantáis hablando sobre proyectos interesantes, algunos incluso sin poder huir durante la ida y vuelta al trabajo (Eso lo sabe bien Rubén de MascandoBits, la otra persona que incito a abrir un blog, incluso haciendo algunas colaboraciones en el suyo, mis dieces también).

Pero sobre todo, millones de gracias a mi familia que me ha apoyado, y en especial a mi novia que es la que mas tiene que aguantar los días pegado a la pantalla del ordenador y la que más me apoya, sin ella, nada de esto sería posible.

 

**La entrada ¡Reconocimiento MVP Award 2018! se publicó primero en Fixed Buffer.**

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

Picando Código

Hack2Progress – Hackathon en la Universidad de Cantabria, Santander

octubre 31, 2018 11:00

Los días 16 y 17 de noviembre se celebra en Santander la IV Edición de Hack2Progress. La cita en es la Casa del Estudiante – Torre A – Universidad de Cantabria.

Hack2Progress IV

¿Qué es Hack2Progress?

Se trata de un Hackathon social que te permite impulsar tu carrera profesional, conocer tus límites, compartir conocimientos y desarrollar una idea. En esta edición la idea debe estar relacionada con el sector energía (eficiencia, consumo, ….) y cómo ámbito tecnológico conductor el uso del servicio cloud de Azure (cuentas gratuitas disponibles) o si dispone de una cuenta de otro proveedor (AWS, Google Cloud, …). Todo en un ambiente muy creativo y amigable.

Hace mucho que no voy a una hackathon, pero son muy divertidas y enriquecedoras. En esta ocasión la asistencia es gratuita, y se ofrece comida, bebida, snacks, material de oficina y lo necesario para desarrollar la idea. El jurado estará para enseñar, aconsejar y valorar a los participantes y habrá gente de la organización disponible para que todos se sientan cómodos. Serán 24 horas de hackathon, con 3 premios que suman 4.800 €. Es un hackathon colaborativo y no competitivo, de mis preferidos. ¡Suena muy interesante!

Edición pasada

La III Edición de Hack2Progress congregó a 80 personas repartidas en 21 equipos de 2 a 5 personas, con diferentes perfiles: estudiantes universitarios y de FP, trabajadores en activo y en paro.

Pueden visitar el sitio web para ver más información o directamente registrarse.

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

proyectos Ágiles

Refactorización organizativa Agile – Parte 1

octubre 30, 2018 07:00

La refactorización “obligatoria” de la organización, necesaria para ser más ágil, tiene muchos beneficios pero, como cualquier diseño organizativo, también tiene problemas típicos que se deben esperar (por ejemplo, efecto silo en equipos, no tener una base tecnológica lista, cómo tratar con los mandos intermedios). A continuación se muestran algunas opciones para lidiar con estas situaciones antes de que se vuelvan difíciles. Otro tema que también hay que considerar es que no tiene sentido el concepto de “transformación corporativa” y qué hacer si te encuentras en esta situación.

Versión en español

English version

Presentaciones relacionadas

Artículos relacionados:

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

Blog Bitix

IBM adquiere Red Hat, el gigante de Linux y código abierto

octubre 29, 2018 07:00

Red Hat
IBM

Hace unos pocos días recordaba una de las adquisiciones más relevantes entre empresas de software y de tecnología, la adquisición de Sun Microsystems por parte de Oracle en enero del 2010. En este octubre del 2018 acaba de anunciarse la adquisición de Red Hat por parte de IBM.

La adquisición de Sun Microsystems por parte de Oracle fue relevante por el conjunto de productos que poseía Sun entre ellos el lenguaje de programación más usado, Java, pero también MySQL, competencia directa de la base de datos Oracle, el paquete ofimático OpenOffice que originaría LibreOffice, el software de virtualización VirtualBox, Solaris y otras tecnologías relevantes como ZFS y DTrace. Por su parte la adquisición de Red Hat por parte de IBM también es relevante por el conjunto de productos de Red Hat que ha desarrollado, adquirido o contribuido como su sistema operativo basado en GNU/Linux denominado Red Hat Enterprise Linux (RHEL) pero también las variantes comunitarias del proyecto Fedora o la versión equivalente comunitaria CentOS basada en RHEL. Pero también por otros productos como el servidor de aplicaciones JBoss y Wildfly, la herramienta de automatización Ansible adquirida no hace mucho, OpenShift como plataforma de computación en la nube basada en contenedores, la versión de Red Hat de OpenStack para disponer de una infraestructura para la computación en la nube, Gluster y Ceph para el almacenamiento, CoreOS a añadir a sus soluciones para contenedores entre otros de sus productos.

Entre las empresas Sun Microsystems y Red Hat hay algún punto en común como que las dos han tenido en especial consideración las tecnologías de código abierto y el software libre, son dos de las empresas que más han contribuido al desarrollo del software libre. Sun dotando a Java de una licencia de software libre como GPL en el año 2006 junto con los otros proyectos que fue adquiriendo y Red Hat con sus contribuciones al núcleo de Linux y otros proyectos como el entorno de escritorio GNOME, NetworkManager, los controladores gráficos, el servidor gráfico de nueva generación Wayland que sustituirá a X.Org Server o Flatpak como nueva forma de distribuir software para todas las distribuciones GNU/Linux directamente por los desarrolladores de los programas, el software de virtualización KVM o el sistema de inicio systemd.

Sin embargo, también hay diferencias en la adquisición de Sun y Red Hat. Una de ellas significativa es que Sun no pasaba por buenos momentos cuando fue adquirida por Oracle aún con las importantes tecnologías que poseía incapaz de tener un modelo de negocio rentable sobre el que basarse para pedurar. Oracle se deshizo de los que no le eran provechosos y muchas personas abandonaron Sun/Oracle. En cambio Red Hat si posee un modelo de negocio rentable basado en el código abierto y software libre con una facturación de 2000 millones. Sun fue adquirida por 7400 millones de dólares lo que en su momento parecía una gran cantidad de dinero y ahora parece que fue muy barato, Red Hat ha sido adquirida por 34000 millones, una enorme diferencia entre ambas operaciones aún habiendo pasado casi una década.

Anuncio en la página de Red Hat

Conocida la operación en la página de Red Hat se publican varias notas de prensa.

A razón de estas notas de prensa después de haberlas leído el motivo principal por el que IBM adquiere Red Hat es para convertirse en el proveedor número uno de computación en la nube híbrida y el motivo por el que Red Hat acepta la operación es porque estando dentro de una compañía del tamaño y recursos disponibles de IBM puede acelerar el crecimiento y proporcionar a una mayor audiencia innovación basándose en el código abierto a una escala mucho mayor conservando su cultura única y compromiso decidido con el código abierto, muchos años antes de lo que les costaría hacerlo solos. Red Hat en el 2008 eran 2200 empleados en el 2018 son 12600, IBM tiene 300000 empleados. IBM se mantiene comprometida con el gobierno abierto de Red Hat, las contribuciones al código abierto, la participación en la comunidad de código abierto y modelo de desarrollo. Red Hat seguirá siendo Red Hat como una unidad distinta dentro de IBM, mantiene su misión comprometida con el código abierto y valores de colaboración, transparencia, participación y meritocracia, incluso esperando que estos valores se transladen a IBM.

Red Hat se fundó en 1993 liberando en 1994 la primera versión de Red Hat Linux, en el 2001 vendía en comercios por $29.95 cajas con el sistema operativo entre entusiastas o descargable gratuitamente de sitios FTP. Pero con solo entusiastas del código abierto no haría de Red Hat (o Linux) un jugador en el software empresarial. En el año 2002 se hace la apuesta por separar los productos comerciales de su inversión continuada en construir proyectos de código abierto. Se deja de lado la descarga gratuita y las cajas con Red Hat Linux siendo reemplazadas con un modelo de suscripción empresarial, reteniendo los principios del código abierto de libertad a la vez que creando un modelo de negocio sostenible a largo plazo, publicándolo como Red Hat Enterprise Linux. El punto importante es que encontró un modelo de negocio exitoso para Linux y el código abierto continuando invirtiendo e innovando en la comunidad con proyectos como Fedora, siendo aún a día de hoy su modelo pero mucho más expandido. Desde el día que presentaron RHEL su misión ha sido llevar el código abierto a la empresa. Lo hicieron para Lixux, para Java EE, para OpenStack y lo están haciendo para Ansible, Kubernetes y más proyectos de código abierto.

Historia de Red Hat

IBM es una compañía mucho más grande que Red Hat y tiene importantes valores como los servidores mainframe IBM Z, los procesadores POWER, además de su propia nube, software de analítica, internet de las cosas y soluciones para varios sectores de la industria y gubernamentales. El proyecto Eclipse surgió de IBM y ha estado ampliamente relacionada con Java con los servidores WebSphere u OpenLiberty. Ambas son dos de las empresas que más contribuyen al kernel de Linux.

En la nube hay negocio con varios actores importantes Amazon Web Services, Microsoft Azure, Google Cloud, Oracle Cloud uniéndose ahora IBM y Red Hat entre otros varios actores más pequeños. En este auge de la nube y que seguirá creciendo todos quieren estar presentes, sin embargo, con la cantidad de ellos que quizá con el paso del tiempo solo haya dos o tres exitosos muy grandes que concentren la mayor parte del mercado. O quizá surja una nueva tecnología disruptiva que cambie de nuevo las reglas de juego. IBM que parecía pasar más desapercibida en los últimos tiempos con la adquisición de Red Hat toma una nueva relevancia que puede ser beneficiosa para ambas si son capaces de aprovechar esta oportunidad. IBM realiza nueva inversión en el código abierto que se suma a las ya anteriores de 1000 millones en Linux.

Al mismo tiempo se abre un periodo de incertidumbre considerando precedentes similares anteriores como el comentado de Sun pero también de oportunidades. Si no es exitoso Canonical y Ubuntu o SUSE pueden ser beneficiados pasando a ocupar el espacio que ahora ocupa Red Hat. Está por ver si IBM abraza los valores por los que Red Hat ha tenido éxito, de momento parece que va a mantener a Red Hat como una unidad distinta dentro de IBM.

¿IBM se convertirá en el gigante rojo o Red Hat se convertirá en Blue Hat?.

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

Variable not found

Enlaces interesantes 335

octubre 29, 2018 09:34

Enlaces interesantes
Ahí van los enlaces recopilados durante la semana pasada. Espero que os resulten interesantes. :-)

Por si te lo perdiste...

.NET / .NET Core

ASP.NET / ASP.NET Core

Azure / Cloud

Conceptos / Patrones / Buenas prácticas

Data

HTML / CSS / Javascript

Visual Studio / Complementos / Herramientas

Xamarin

Otros

Publicado en Variable not found.

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

Blog Bitix

IBM adquiere RedHat, el gigante de Linux y código abierto

octubre 29, 2018 04:00

RedHat
IBM

Hace unos pocos días recordaba una de las adquisiciones más relevantes entre empresas de software y de tecnología, la adquisición de Sun Microsystems por parte de Oracle en enero del 2010. En este octubre del 2018 acaba de anunciarse la adquisición de RedHat por parte de IBM.

La adquisición de Sun Microsystems por parte de Oracle fue relevante por el conjunto de productos que poseía Sun entre ellos el lenguaje de programación más usado, Java, pero también MySQL, competencia directa de la base de datos Oracle, el paquete ofimático OpenOffice que originaría LibreOffice, el software de virtualización VirtualBox, Solaris y otras tecnologías relevantes como ZFS y DTrace. Por su parte la adquisición de RedHat por parte de IBM también es relevante por el conjunto de productos de RedHat que ha desarrollado, adquirido o contribuido como su sistema operativo basado en GNU/Linux denominado RedHat Enterprise Linux (RHEL) pero también las variantes comunitarias del proyecto Fedora o la versión equivalente comunitaria CentOS basada en RHEL. Pero también por otros productos como el servidor de aplicaciones JBoss y Wildfly, la herramienta de automatización Ansible adquirida no hace mucho, OpenShift como plataforma de computación en la nube basada en contenedores, la versión de RedHat de OpenStack para disponer de una infraestructura para la computación en la nube, Gluster y Ceph para el almacenamiento, CoreOS a añadir a sus soluciones para contenedores entre otros de sus productos.

Entre las empresas Sun Microsystems y RedHat hay algún punto en común como que las dos han tenido en especial consideración las tecnologías de código abierto y el software libre, son dos de las empresas que más han contribuido al desarrollo del software libre. Sun dotando a Java de una licencia de software libre com GPL en el año 2006 junto con los otros proyectos que fue adquiriendo y RedHat con sus contribuciones al núcleo de Linux y otros proyectos como el entorno de escritorio GNOME, NetworkManager, los controladores gráficos, el servidor gráfico de nueva generación Wayland que sustituirá a X.Org Server o Flatpak como nueva forma de distribuir software para todas las distribuciones GNU/Linux directamente por los desarrolladores de los programas, el software de virtualización KVM o el sistema de inicio systemd.

Sin embargo, también hay diferencias en la adquisición de Sun y RedHat. Una de ellas significativa es que Sun no pasaba por buenos momentos cuando fue adquirida por Oracle aún con las importantes tecnologías que poseía incapaz de tener un modelo de negocio rentable sobre el que basarse para pedurar. Oracle se deshizo de los que no le eran provechosos y muchas personas abandonaron Sun/Oracle. En cambio RedHat si posee un modelo de negocio rentable basado en el código abierto y software libre con una facturación de 2000 millones. Sun fue adquirida por 7400 millones de dólares lo que en su momento parecía una gran cantidad de dinero y ahora parece que fue muy barato, RedHat ha sido adquirida por 34000 millones, una enorme diferencia entre ambas operaciones aún habiendo pasado casi una década.

Anuncio en la página de RedHat

Conocida la operación en la página de RedHat se publican varias notas de prensa.

A razón de estas notas de prensa después de haberlas leído el motivo principal por el que IBM adquiere RedHat es para convertirse en el proveedor número uno de computación en la nube híbrida y el motivo por el que RedHat acepta la operación es porque estando dentro de una compañía del tamaño y recursos disponibles de IBM puede acelerar el crecimiento y proporcionar a una mayor audiencia innovación basándose en el código abierto a una escala mucho mayor conservando su cultura única y compromiso decidido con el código abierto, muchos años antes de lo que les costaría hacerlo solos. RedHat en el 2008 eran 2200 empleados en el 2018 son 12600, IBM tiene 300000 empleados. IBM se mantiene comprometida con el gobierno abierto de RedHat, las contribuciones al código abierto, la participación en la comunidad de código abierto y modelo de desarrollo. RedHat seguirá siendo RedHat como una unidad distinta dentro de IBM, mantiene su misión comprometida con el código abierto y valores de colaboración, transparencia, participación y meritocracia, incluso esperando que estos valores se transladen a IBM.

RedHat se fundó en 1993 liberando en 1994 la primera versión de Red Hat Linux, en el 2001 vendía en comercios por $29.95 cajas con el sistema operativo entre entusiastas o descargable gratuitamente de sitios FTP. Pero con solo entusiastas del código abierto no haría de RedHat (o Linux) un jugador en el software empresarial. En el año 2002 se hace la apuesta por separar los productos comerciales de su inversión continuada en construir proyectos de código abierto. Se deja de lado la descarga gratuita y las cajas con Red Hat Linux siendo reemplazadas con un modelo de suscripción empresarial, reteniendo los principios del código abierto de libertad a la vez que creando un modelo de negocio sostenible a largo plazo, publicándolo como Red Hat Enterprise Linux. El punto importante es que encontró un modelo de negocio exitoso para Linux y el código abierto continuando invirtiendo e innovando en la comunidad con proyectos como Fedora, siendo aún a día de hoy su modelo pero mucho más expandido. Desde el día que presentaron RHEL su misión ha sido llevar el código abierto a la empresa. Lo hicieron para Lixux, para Java EE, para OpenStack y lo están haciendo para Ansible, Kubernetes y más proyectos de código abierto.

IBM es una compañía mucho más grande que RedHat y tiene importantes valores como los servidores mainframe IBM Z, los procesadores POWER, además de su propia nube, software de analítica, internet de las cosas y soluciones para varios sectores de la industria y gubernamentales. El proyecto Eclipse surgió de IBM y ha estado ampliamente relacionada con Java con los servidores WebSphere u OpenLiberty. Ambas son dos de las empresas que más contribuyen al kernel de Linux.

En la nube hay negocio con varios actores importantes Amazon Web Services, Microsoft Azure, Google Cloud, Oracle Cloud uniéndose ahora IBM y RedHat entre otros varios actores más pequeños. En este auge de la nube y que seguirá creciendo todos quieren estar presentes, sin embargo, con la cantidad de ellos que quizá con el paso del tiempo solo haya dos o tres exitosos muy grandes que concentren la mayor parte del mercado. O quizá surja una nueva tecnología disruptiva que cambie de nuevo las reglas de juego. IBM que parecía pasar más desapercibida en los últimos tiempos con la adquisición de RedHat toma una nueva relevancia que puede ser beneficiosa para ella y RedHat si son capaces de aprovechar esta oportunidad. IBM realiza nueva inversión en el código abierto que se suma a las ya anteriores de 1000 millones en Linux.

Al mismo tiempo se abre un periodo de incertidumbre considerando precedentes similares anteriores como el comentado de Sun pero también de oportunidades. Si no es exitoso Canonical y Ubuntu o SUSE pueden ser beneficiados pasando a ocupar el espacio que ahora ocupa RedHat. Está por ver si IBM abraza los valores por los que RedHat ha sido exitoso, de momento parece que va a mantener a RedHat como una unidad distinta dentro de IBM.

¿IBM se convertirá en el gigante rojo o RedHat se convertirá en BlueHat?.

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

Israel Perales

Habilitar el completado de comandos de docker en el bash de SLES

octubre 25, 2018 11:51

Habilitar el completado de comandos de docker en el bash de SLES

Siempre que trabajamos con el bash nunca esta de mas una ayuda, en este caso el completado de los comandos nos sera útil, después de escribir docker im presionamos tab y nos sales algunas opciones.

Como súper usuario ejecutamos el siguiente comando, salimos y volvemos a entrar a la consola.

curl https://raw.githubusercontent.com/docker/docker-ce/master/components/cli/contrib/completion/bash/docker -o /etc/bash_completion.d/docker.sh

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

Israel Perales

Instalar nano en SLES

octubre 25, 2018 11:35

Instalar nano en SLES

Para instalar en nano es necesario tener la contraseña del usuario root y ejecutar los siguientes comandos

Si estas en SLES 12 SP 3

zypper addrepo https://download.opensuse.org/repositories/editors/SLE_12_SP3/editors.repo

zypper refresh

zypper install nano

Si estas en SLES 15

zypper addrepo https://download.opensuse.org/repositories/editors/SLE_15/editors.repo

zypper refresh

zypper install nano

Aunque siempre debemos conocer un poco como funciona vi, no sabemos cuando nos puede ser útil.

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

Meta-Info

¿Que es?

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

rss subscripción

Sponsors

Puedes utilizar las siguientes imagenes para enlazar PlanetaCodigo:
planetacodigo

planetacodigo

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

Idea: Juanjo Navarro

Diseño: Albin