DNM+ Online
dotnetmania 2.0
Bluetooth en .NET
Hace tiempo me planteé desarrollar una aplicación Bluetooth para mi Smartphone. Las pocas alternativas libres de pago que existían y la búsqueda de cómo funciona realmente Bluetooth me llevaron a plantearme la creación de una librería .NET para Windows Mobile, que posteriormente pasé también a Windows XP.

Este artículo presenta una aproximación al modelo de desarrollo seguido durante la implementación de esas librerías.

Orígenes y necesidades
de Bluetooth Bluetooth surgió en la multinacional sueca Ericsson, en 1994, a partir de un estudio inicialmente orientado a la búsqueda de alternativas a las comunicaciones de dispositivos con el teléfono móvil en un contexto inalámbrico. La utilización de Bluetooth como tecnología de comunicación entre dispositivos tuvo una buena acogida entre la comunidad tecnológica, y tanto es así que en 1998 se creó un SIG (Special Interest Group), compuesto por las multinacionales IBM, Intel, Nokia, Toshiba y Ericsson, para el desarrollo conjunto y expansión de dicha tecnología. Unos meses más tarde se anunció también la entrada de Microsoft, Agere Systems (después Lucent), 3Com y Motorola, sentándose así las bases para la generalización del uso de Bluetooth en un futuro no muy lejano, como efectivamente ha ocurrido.

Especificación Bluetooth es una tecnología de radio de corto alcance. Su finalidad es la comunicación entre dispositivos, sean teléfonos móviles, agendas electrónicas, ordenadores, portátiles, manos libres, GPS y otros, utilizando la frecuencia libre de licencia 2,4 Ghz ISM (Industries, Scientific and Medical). Su desarrollo se orientó hacia el bajo precio, el bajo consumo y el uso de la tecnología de radio. No necesita la proximidad que requiere IrDA, por ejemplo, con lo que su alcance puede incluso "atravesar" obstáculos como paredes (según tipo y profundidad). Soporta tanto la conexión punto a punto como multipunto, lo cual puede dar lugar a redes PAN (Personal Area Network), también conocidas como Bluetooth Piconet. El precio de Bluetooth como módulo independiente era en el año 2000 de 20$, y ha bajado en la actualidad hasta los 5$ (datos orientativos). Su consumo es mínimo, unos 0.3 mA en modo espera y 30 mA durante su funcionamiento, otra razón por la que una gran cantidad de teléfonos móviles utilizan hoy en día Bluetooth. Su tasa de transferencia no es muy alta, y como en cualquier tipo de comunicación inalámbrica, depende del medio. Ésta no supera 1 Mbps (la velocidad óptima de rendimiento es de 721 Kbps), y aunque baja, supera la velocidad soportada por los actuales puertos serie (hasta ocho veces más rápido) y paralelo (el triple de rápido).

Bluetooth utiliza el espectro de frecuencia 2,4 Ghz, como otros aparatos tan cotidianos como los teléfonos inalámbricos o routers WiFi LAN (IEEE 802.11), y otros que no lo son tanto y se utilizan en medicina o en laboratorios. Si está usted pensando en si tanto dispositivo no podría interferir en las comunicaciones Bluetooth (dado que la lista de productos cotidianos es grande; podríamos incluir el microondas, o el mando a distancia de un garaje...), le diré que Bluetooth transmite paquetes muy pequeños a una velocidad tan rápida (hasta 1600 paquetes por segundo) que prácticamente hace imposible la interferencia entre ellos. Sin embargo, existen algunas connotaciones en el caso de interferencia con las redes WiFi. A la pregunta si pueden coexistir, la respuesta es sí, y el porqué lo explicaré de manera muy ligera: Bluetooth utiliza una modulación  de señal de espectro expandido por salto de frecuencia (FHSS) y el estándar WLAN IEEE 802.11b. WiFi, además de la mencionada, también utiliza una modulación de señal de espectro expandido por secuencia directa (DSSS), y puede utilizar ambas modulaciones, no dando lugar a interferencias. La técnica de modulación FHSS conforma en sí una parte importante de la seguridad de conexión entre dispositivos Bluetooth debido a su funcionamiento. Este tema, aunque  interesante, es algo complejo. Su comprensión puede ayudar a entender mejor tanto la comunicación entre dispositivos como la seguridad entre ellos. Podrá saber más acerca de este tema en los enlaces recomendados al final de este artículo. El rango de alcance de Bluetooth está catalogado en 3 clases en función de su potencia. En la clase 1 podemos obtener rangos de unos 100 metros a una potencia de antena nominal de 20 dBm. En la clase 2 y la clase 3, los rangos son ambos de unos 10 metros, con la diferencia de que en la clase 2 la potencia de antena es de 4 dBm, mientras que en la clase 3 es de 0 dBm. Además de la adaptabilidad y flexibilidad, Bluetooth posee varias capas de seguridad de encriptación de hasta 128 bits y de autenticación de usuarios. Bluetooth utiliza un número de identificación personal (PIN) que junto a su propia dirección se utiliza para la detección y autenticación de otros dispositivos a su alcance. A quien tenga dudas de si Bluetooth es realmente seguro, le puedo asegurar que sí lo es. Además de la encriptación, en este contexto entra en escena la modulación FHSS antes mencionada, que provee una técnica de comunicación segura por la que un dispositivo sólo aceptará paquetes de una dirección Bluetooth remota específica. Ver figura 1. En este mismo contexto, también me gustaría resaltar lo que en la especificación oficial de Bluetooth se conoce como Baseband, que describe la especificación del controlador de enlace de Bluetooth (Bluetooth Link Controller) que es quien administra los protocolos e implementa la capa física de Bluetooth, siendo el encargado de las transmisiones por canales mediante FHSS. Además, es importante desde el punto de vista programático, ya que es quien especifica las direcciones de los dispositivos Bluetooth, como más adelante veremos. Maneja dos tipos de conexiones, SCO (Synchronous Connection-Oriented), conexiones punto a punto síncronas, y ACL (Asynchronous Connection-Less), conexiones multipunto asíncronas; de ésta última veremos un ejemplo de conexión mediante una función por código. Esta base física es la interfaz del protocolo L2CAP (Logical Link Control and Adaptation Protocol), que se encarga de "interpretar" y administrar los paquetes en transmisión. El protocolo RFCOMM (Serial Cable Emulation Protocol) ofrece una emulación de un puerto serie (basado en RS232) para la comunicación con L2CAP; desde Windows CE podremos crear un puerto virtual que interactúe con RFCOMM. Este punto también es interesante, puesto que RFCOMM será en muchas ocasiones nuestro conducto de comunicación con los perfiles Bluetooth. Digo muchas veces, ya que también se puede interactuar mediante SDP (Service Discovery Protocol), el cual ofrece un mecanismo de interconexión a Bluetooth para el descubrimiento de dispositivos a nuestro alcance y sus servicios.
Desarrollo
Como hemos visto hasta ahora, toda la especificación Bluetooth definida desde el SIG hace de esta tecnología un estándar de mercado que permite utilizarla siguiendo dichas especificaciones; sin embargo, desde el punto de vista de desarrollo dicha estandarización no es tal. Me explico: las maneras de acceder a Bluetooth desde nuestras aplicaciones vienen determinadas por la "pila" que el sistema operativo implementa. Windows Mobile (a partir de la versión 4.0), por ejemplo, viene con Microsoft Bluetooth Stack (así como Windows XP SP1 y versiones posteriores, incluyendo evidentemente Windows Vista); sin embargo, y como sucede con otras características del sistema operativo, los fabricantes de dispositivos móviles, sean smartphones o PDA, tienen la opción de mantener dicha "pila" o sustituirla sin que podamos hacer nada, puesto que ésta reside en la memoria ROM del dispositivo. Por esta razón, podemos encontrar dispositivos cuya "interfaz" o "pila" es la que implementa Microsoft, y otros que la sustituyen por otras disponibles en el mercado, como por ejemplo BROADCOM/WIDCOMM. El problema que se plantea es que, en función de la interfaz, desarrollaremos código específico o propietario para dicha interfaz, no pudiendo reutilizarla luego en otros dispositivos con una interfaz distinta. Por poner un par de ejemplos, los PC de bolsillo de Hewlett Packard vienen en su totalidad equipados con WIDCOMM, no siendo esto así en algunos modelos del fabricante Qtek. Aún habiendo pilas distintas éstas se comunican perfectamente entre sí; es decir, que podemos crear dos aplicaciones específicas, cada una para cada pila diferente, y a la hora de comunicarse lo harán a la perfección, ya que no olvidemos que Bluetooth es un estándar, aunque las librerías utilizadas serán distintas. En este artículo nos centraremos en el desarrollo para Microsoft Bluetooth Stack. No entraré en detalles de qué interfaz o pila es mejor o peor y por qué. La principal razón para utilizar ésta es que Microsoft Bluetooth Stack está implementada no solo en Windows Mobile, sino además en los sistemas operativos Windows XP SP1 y posteriores, para los que muchas de las funciones y librerías aquí mencionadas funcionarán (eso sí, con sus particularidades). El planteamiento y/o alcance de la aplicación nos permite saber qué servicio de un dispositivo Bluetooth remoto queremos utilizar. Por ejemplo, si nuestra intención es conectarnos con un GPS, este planteamiento será distinto al caso en que queramos conectarnos a un dispositivo manos libres. Aunque los mecanismos de descubrimiento y emparejado sean iguales, la forma de comunicarse puede ser distinta. Lo que debemos tener bien claro es que Bluetooth es un medio de transmisión, no el fin de nuestra aplicación, y que en él nos basaremos para comunicarnos con otro dispositivo. Por último, en Windows CE, la principal vía de acceso a Bluetooth es mediante sockets que exponen el protocolo RFCOMM, aunque también puede hacerse directamente con las interfaces Bluetooth sin pasar por sockets (posibilidad ésta algo más compleja). Para el seguimiento de este artículo, el código al que se hace referencia está escrito por Microsoft y puede encontrarse en Windows Embedded Source Tools for Bluetooth Technology [3]. Además de hacer referencia a dicho código, mostraremos otras funcionalidades que éste no ofrece y que están basadas en el criterio de desarrollo con el que se implementó la librería gratuita Bluetooth de desarrolloMobile.NET. 1. Inicializar la radio Bluetooth Al activar la radio es un buen momento para inicializar también la librería WS2.dll, encargada de las comunicaciones por sockets. La función de inicialización requiere dos parámetros: el primero es de tipo int e indica la versión más alta, mientras que el segundo es un puntero a la estructura de datos WSDATA que recibirá los detalles de la implementación de Windows Sockets. Como se ve en el fuente 1, esta estructura se implementa como una matriz de tipo byte de 512 posiciones. Esta función debe devolver cero para que la inicialización sea satisfactoria. Del mismo modo que en el constructor hemos inicializado WS2, en el destructor la descargaremos.

  1. Obtención de información
    de la radio local Pese a que no es un paso estrictamente necesario, podemos llegar a obtener la clase de dispositivo, la versión e incluso el fabricante de nuestro dispositivo. Esta información la obtenemos de las funciones BthReadCOD para la clase de dispositivo y BthReadLocalVersion para la versión y código del fabricante (ver fuente 1). Estas funciones tienen sus análogas para la petición de dicha información para un dispositivo remoto, BthReadRemoteVersion. En la obtención del fabricante y clase de dispositivo se devuelven identificadores únicos asignados por la especificación Bluetooth SIG.
    Hasta aquí y como se muestra en el fuente 1, hemos hecho que la clase BTHRadio englobe tanto la gestión del estado de la radio como la información del dispositivo.
  2. Mostrar los dispositivos
    ya emparejados Una vez que hemos realizado la inicialización de la radio, se trata de contactar o emparejar con el dispositivo remoto. Para ello tenemos dos escenarios posibles. El primero parte de que dicho dispositivo ya haya sido emparejado anteriormente, mientras que el segundo implica empezar una búsqueda de todos los dispositivos que estén en nuestro alcance. En lo que a los dispositivos ya emparejados se refiere, esta información se almacena en el Registro (clave HKEYLOCALMACHINE\SOFTWARE\Microsoft\Bluetooth\Device), donde se guardan el nombre y la dirección del dispositivo remoto. El recorrido por los valores guardados bajo esa clave para obtener los dispositivos emparejados nos crea la necesidad de tener una clase BluetoothDevice. Si se estudia la clase BluetoothRadio del ejemplo de Microsoft, se verá que realiza una iteración por cada uno de los dispositivos y construye para cada uno un nuevo objeto BluetoothDevice con dos parámetros: dirección y nombre. La función que devuelve los dispositivos emparejados es miembro de la clase BluetoothRadio y devuelve una matriz de BluetoothDevice, lo cual da lugar a la posibilidad de contemplar una nueva clase BluetoothDeviceCollection que implemente las interfaces IList e ICollection.
    Suponiendo que el emparejamiento se haya producido mediante autenticación por medio de un PIN, que es lo más probable, y ninguno de los dos dispositivos lo haya revocado, no será necesario volvernos a autenticar para comunicarlos entre ellos. Sobre los aspectos relacionados con la seguridad hablaremos más adelante.
  3. Descubrir dispositivos a mi alcance Cuando lo que queremos es descubrir los dispositivos Bluetooth con estado activado o detectable que estén bajo nuestro radio de acción, según especificaciones, la cosa cambia. Hasta ahora en el código que hemos visto había una llamada más o menos simple a alguna API de Bluetooth que nos ayudaba; sin embargo, en el proceso de búsqueda de dispositivos tenemos que echar mano de la librería de sockets. Para la búsqueda de dispositivos, debemos inicializar una consulta a través de sockets, especificando en una estructura de datos (nativa) que lo que buscamos son dispositivos Bluetooth. De un modo similar también podemos buscar las conexiones  de red disponibles en nuestro sistema.
    La referencia de Bluetooth en MSDN [2] indica que, a pesar de que las tres funciones claves para esta enumeración son BthNsLookupServiceBegin, BthNsLookupServiceNext y BthNsLookupServiceEnd, para conservar la compatibilidad con Win32 se recomienda utilizar WSALookUpServiceBegin, WSALookUpServiceNext y WSALookUpServiceEnd, respectivamente. Mediante la función WSALookUpServiceBegin inicializamos la búsqueda; debemos suministrarle una estructura WSAQUERYSET con los parámetros deseados para la búsqueda de dispositivos o redes disponibles, como dije antes. Recomiendo estudiar los documentos relativos a WSAQUERYSET (Bluetooth and WSAQUERYSET for Device Inquiry, Bluetooth and WSAQUERYSET for Service Inquiry), para así entender mejor la complejidad e importancia de esta estructura. ¿Cómo indicamos la búsqueda de dispositivos Bluetooth? Dentro de la estructura WSAQUERYSET hay otras dos estructuras, CSADDRINFO y BLOB; ésta última a su vez apunta a otra estructura, BTHQUERY_ DEVICE (si lo que buscamos son servicios, esta estructura es BTHQUERYSERVICE). Es en este punto donde debemos utilizar los sockets orientados a las peculiaridades de Bluetooth. Por ejemplo, cuando desde nuestro código creemos un objeto del tipo System.Net.Sockets.Socket, al constructor deberemos indicarle que la familia de direcciones es Bluetooth (mediante el valor 32 en AddressFamily), que el tipo de socket utilizado es SocketType.Stream, y el tipo de protocolo es RFCOMM (su valor es 0x0003).
    El puntero a CSADDRINFO es de salida, y a través de él la función devolverá la información de la dirección "bluetooth socket" del resultado de la búsqueda según las especificaciones comentadas. El puntero a BLOB, por su parte, contendrá el tamaño del puntero a BTHQUERY_DEVICE y el puntero al mismo, en el que debemos especificar a la entrada los parámetros de búsqueda que queremos llevar a cabo mediante el flag LAP (su valor es 0x9e8b33). Hasta aquí hemos identificado las estructuras y funciones que intervienen. El funcionamiento es el siguiente:

    •  Creación de la estructura BLOB y BTHQUERYSERVICE
    •  Creación de la estructura WSAQUERYSET •  Llamada a WSALookUpServiceBegin •  Llamada a WSALookUpServiceNext mientras se vayan detectando dispositivos (hay un límite por defecto de 16 dispositivos). •  Al detectar un dispositivo, creamos un objeto de tipo BluetoothDevice en base a la dirección recibida. •  Lo añadimos a nuestra lista BluetoothDeviceCollection. •  Al finalizar la búsqueda, llamada a WSALookUpServiceEnd.

    Siento no poder profundizar más sobre este tema. La explicación en detalle del mecanismo de descubrimiento requeriría probablemente un artículo íntegro debido a su complejidad. La codificación en .NET es compleja y requiere de un ejercicio importante de interoperabilidad con las API y de utilización de los cálculos de referencia en cuanto a asignación dinámica de estructuras de memoria y destrucción de las mismas. La utilización de lo que en un marco de código nativo conocemos como estructuras debería ser reemplazada por el uso de clases en código manejado. Otra opción es manipularlo todo en una librería nativa, un entorno más propenso para desarrollos a más bajo nivel. Lamentablemente, Microsoft no ofrece un ejemplo de código específico, pero en la clase BluetoothService, método PublishService, encontrará un ejemplo de publicación de servicio en el que se tratan estructuras BLOB desde .NET; también puede encontrar librerías totalmente funcionales que implementan el descubrimiento de dispositivos como 32feet, InTheHand o desarrolloMobile.NET.

  4. Emparejar El proceso de emparejado requiere del conocimiento de la dirección del dispositivo Bluetooth con el que queremos emparejarnos y consta de un secuencia de pasos. En primer lugar, establecemos nuestro PIN mediante una llamada a BthSetPin. A continuación, creamos una conexión ACL mediante BthCreateACLConnection (para una conexión SCO se utiliza BthCreateSCOConnection), de donde obtendremos el manejador (handle) de la conexión que utilizaremos. Luego debemos llamar a la función BthAutenticate para forzar el emparejamiento. Será en esta llamada donde el dispositivo remoto cuya dirección se indica recibirá el aviso de petición de emparejamiento. Si el PIN es correcto, el emparejamiento se realizará satisfactoriamente (fuente 2). Por último, podemos cerrar la conexión. En Windows Mobile 5, la función BthPairRequest engloba todas estas llamadas en una, haciéndose cargo ella misma del establecimiento del PIN, la conexión ACL, la autenticación y el cierre.

  5. Recibir petición
    de emparejamiento En el otro extremo, y al hilo del anterior apartado, está el dispositivo que recibe una petición de emparejamiento. Para obtener la dirección del dispositivo que ha realizado una petición se utiliza BthGetPINRequest. En caso de que queramos rechazar la petición, nuestra función es BthRefusePinRequest. Microsoft Bluetooth Stack incorpora un mecanismo de evento que permite mediante una interfaz gráfica mostrar una petición entrante por pantalla. Se trata de un notificador de tipo globo (balloon) que permite al usuario aceptar o rechazar la petición. La función encargada de esta llamada es BthSetSecurityUI.

  6. Seguridad En estos últimos apartados hemos visto diferentes contextos y funciones que ofrece Bluetooth en cuanto a seguridad. Hay dispositivos GPS Bluetooth por ejemplo, en los que no son necesarios PIN para el emparejamiento. También cabe la posibilidad de que dos aplicaciones se comuniquen por Bluetooth sin ningún tipo de establecimiento de PIN. Bluetooth es, gracias al modelado de frecuencia que utiliza, una tecnología muy segura, que hace muy difícil interceptar comunicaciones ajenas. De todas formas, y más aún en la utilización de servicios específicos de los dispositivos, no está de más establecer un número PIN para evitar usos no deseados. Perfiles Si la especificación nos muestra cómo trabaja Bluetooth, los perfiles nos muestran cómo se utiliza Bluetooth, es decir, con qué fin (figura 2). Existe una gran cantidad de perfiles, en muchos casos dependientes entre sí. Por ejemplo, Obex Push (OBject EXchange) depende del Perfil de acceso genérico (Generic Access Profile), del Perfil de puerto serie (Serial Port Profile) y del Perfil genérico de intercambio de objetos (Generic Object Exchange Profile). Aún así, a día de hoy siguen apareciendo nuevos perfiles debido al gran impacto que está teniendo esta tecnología en el mundo y a las nuevas utilidades que se le está dando. Como se puede ver en la figura 2, podemos encontrar perfiles de acceso a redes PAN, distribución de audio y vídeo, así como de comunicación con manos libres, auriculares e intercambio de objetos, entre otros.

    Desarrollo (perfiles) Hasta ahora tenemos todas las herramientas para poder detectar y emparejar dispositivos desde código. Es hora utilizar algún servicio. Pero antes debemos conectarnos. En este punto ambos dispositivos se reconocerán mutuamente y podrán comunicarse entre sí. Es en este momento donde la utilización de uno u otro servicio marca el alcance de nuestra aplicación. Evidentemente, éste será distinto si lo que queremos es obtener información de un GPS, redirigir voz a un manos libres, crear una plataforma de intercambio de archivos o tarjetas de visita, etcétera. Aquí presentaremos la conexión simple mediante sockets. En el apartado dedicado a cómo descubrir dispositivos Bluetooth hablamos de una clase Socket con unos parámetros específicos en el constructor. Una clase Socket necesita de otra clase EndPoint (punto terminal), que no es más que la dirección remota a la que quiere conectarse. Pero como es habitual, la clase EndPoint para Bluetooth tiene algunas particularidades. Dichas peculiaridades son definidas por la estructura SOCKADDR_BTH:

    En la que se indica la familia de direcciones que se utilizará, la dirección remota (BTHADDR) del dispositivo a conectar, el servicio a conectar (definido por un valor GUID) y el puerto remoto de conexión. Si se indica cero como puerto remoto, el servicio indicado obtendrá el puerto remoto a conectar, por lo cual aconsejo mantener siempre dicho valor. Los identificadores de servicio (GUID) están identificados en la especificación Bluetooth del SIG (vea la clase StandardServices del ejemplo de Microsoft). En cualquier caso, si no queremos utilizar ningún servicio, digamos estándar, podemos crear un GUID propio para que dos aplicaciones creadas por nosotros se comuniquen vía sockets. Un ejemplo típico que podemos encontrar en el código de Microsoft es una aplicación para chat.
    Una vez que hayamos obtenido la dirección remota, la pasamos mediante el método Connect conjuntamente con el GUID del servicio deseado en forma de BluetoothEndPoint (clase heredada de EndPoint) y, si todo es correcto, la conexión se establecerá. Observación: si estudia la clase BluetoothEndPoint de Microsoft, verá dos métodos reemplazados: Serialize y Create. Como hemos dicho, Bluetooth utiliza la clase EndPoint siguiendo la estructura SOCKADDR
    BTH, con lo que estos dos métodos contemplan, en un ámbito EndPoint, las peculiaridades de Bluetooth. Ahora lo que tenemos es una conexión por sockets; es decir, que con dos aplicaciones a ambos lados de la conexión, utilizando los métodos Write y Read de una clase de tipo Stream (System.IO), obtenida de la conexión preestablecida,  podremos enviar y recibir datos. Incluso si el dispositivo remoto es un GPS, verá que en la lectura se apreciarán fragmentos de información del estándar NMEA (National Marine Electronics Association) remitidos por el GPS. Pese a que esta técnica funciona, en algunos casos nos interesará crear un puerto COM virtual para bien obtener o bien enviar información al dispositivo (por ejemplo, si éste es un teléfono móvil y queremos enviar los comandos AT para el envío de un SMS, por ejemplo). En el ejemplo de Microsoft se puede ver cómo el método Connect devuelve un NetworkStream, en el que nos basaremos para las comunicaciones. Además, la clase BluetoothService ofrece las posibilidades que brinda la clase Socket en el contexto de Bluetooth (figura 3).

    Conclusión Este artículo es resultado del desarrollo de las librerías desarrolloMobile.NET para interoperar con Bluetooth, tanto en la versión para Windows Mobile como para Windows XP, que podrán encontrar en [1]. Espero que el lector haya podido hacerse una idea aproximada de cómo utilizar Bluetooth en sus aplicaciones. Como se habrá podido apreciar, la implementación de Bluetooth en su totalidad es muy extensa y algo difícil desde .NET, así que el primer paso a dar es determinar cómo y con quién queremos comunicarnos y qué perfil o servicio satisfará mejor esas necesidades. Si se decide profundizar en el funcionamiento de Bluetooth a través de Microsoft Bluetooth Stack, es aconsejable tener conocimientos de programación con sockets; de todos modos, no deja de ser muy satisfactorio desarrollar una aplicación utilizando Bluetooth y sacar el máximo provecho a éste. Quisiera agradecer a David Carmona de Microsoft Ibérica, a Peter Foot (MVP Windows Mobile) y a Daniel Bouie y especialmente a Mikel Zintel (.NET Compact Framework Group Manager) de Microsoft Corp. por la ayuda e intercambio de impresiones que seguramente han contribuido de forma positiva al desarrollo de este artículo.

blog comments powered by Disqus
autor
referencias