Aplicaciones multiplataforma con QT4 y C++

Desarrollando aplicaciones opensource multiplataforma con las librerías QT4 y C++

El lanzamiento de las nuevas librerías QT versión 4 por parte de la compañía Noruega Trolltech marcó un hito bastante importante en el mundo del código abierto, ya que presentaban la novedad de estar disponibles como software libre en varias plataformas. Esto no ocurría con las versiones anteriores, en las que la programación opensource bajo este paquete estaba vedada para entornos Linux.

No pretendemos realizar una análisis exhaustivo sobre la problemática de la programación multiplataforma, el propósito de este artículo es el de resolver algunos problemas que el programador se puede encontrar. Nos centraremos en los sistemas operativos Linux y Windows por ser los de mayor difusión en la actualidad.

 

Desarrollo de una sencilla aplicación multiplataforma

Para la confección de este ejemplo necesitaremos el kit de desarrollo para las QT4. Si trabajamos con Linux nos hará falta el compilador gcc-c++ y la herramienta Make. Por el contrario, si queremos desarrollar bajo Windows nos proveeremos del paquete MinGW.

MinGW es una implementación de los compiladores GCC para la plataforma Win32 que permite migrar la capacidad de este compilador en entornos Windows. Ello nos permite generar ejecutables y librerías usando la API de Windows.

Vamos a elaborar una pequeña utilidad que nos permitirá calcular los días que transcurren entre dos fechas. Nos ayudaremos del QT-Designer para diseñar la pantalla principal; partiremos del diseño de una ventana de diálogo vacía, tal y como se muestra en la Figura 1

figura1

Para completar nuestro diseño pincharemos y arrastraremos dos objetos de tipo QCalendarWidget hacia la ventana que titularemos “Días entre fechas”. También añadiremos varias etiquetas de texto, y una línea de edición de texto. Todos estos elementos los podremos encontrar en la ventana principal del Qt Designer. El resultado nos quedará, más o menos, de la forma de la Figura 2, y lo grabaremos bajo el nombre de “fechas.ui”

figura2

El siguiente paso será implementar algo de código para hacer funcional la ventana que hemos creado con el diseñador de las QT. Elaboraremos para ello los ficheros “fechas.h” y “fechas.cpp” utilizando cualquier editor de texto, según los Listados 1 y 2:

// ————–
// fechas.h
// ————–
#include <QtGui>
#include “ui_fechas.h”

class fechas : public QDialog {
Q_OBJECT
public:
fechas();

private:
Ui::fechas ui;

private slots:
void calcula_diferencia();

};

// —————————-
// fechas.cpp
// —————————-
#include “fechas.h”

fechas::fechas() : QDialog() {
ui.setupUi(this);

calcula_diferencia();
connect(ui.calendarWidget,SIGNAL(selectionChanged()),
this,SLOT(calcula_diferencia()));
connect(ui.calendarWidget_2,SIGNAL(selectionChanged()),
this,SLOT(calcula_diferencia()));

}


void fechas::calcula_diferencia()
{
int dias=ui.calendarWidget->selectedDate().daysTo(
ui.calendarWidget_2->selectedDate());
QString cadnum;
cadnum.setNum(dias);
ui.lineEdit->setText(cadnum);

}

En “fechas.h” hemos creado una subclase derivada de QDialog. Una característica que llama la atención es que hemos definido el objeto privado ui, que se va a encargar de suministrar el código necesario para la configuración y el manejo del cuadro de diálogo creado con el Qt Designer.

Como en toda aplicación C++ necesitamos escribir unas cuantas líneas de código para implementar la función main( ), según el Listado 3:

// ———————————-
// Fichero main.cpp
// ———————————-
#include <QApplication>

#include “fechas.h”


int main(int argc, char *argv[])
{
QApplication app(argc, argv);

fechas f;
f.exec();

return 0;
}

El último paso será crear un fichero de proyecto, en nuestro caso “dif_fechas.pro”, cuya confección se realiza de forma fácil e intuitiva (ver Listado4).

# ———————–
# Fichero dif_fechas.pro
# ———————–
CONFIG += qt
HEADERS += fechas.h
SOURCES += main.cpp \
fechas.cpp
FORMS += fechas.ui

Ya estamos en condiciones de generar el “Makefile” de la aplicación para cualquier plataforma, utilizando el comando qmake; para ello nos bastará escribir desde una consola de texto (Linux) o desde el símbolo del sistema (Windows):

qmake dif_fechas.pro

Ya solo quedará compilar:

make

El ejecutable se habrá generado; para arrancar el programa, bastará con escribir “./dif_fechas” bajo Linux, o bien simplemente “dif_fechas” bajo Windows.

Como vemos, no ha hecho falta ningún esfuerzo especial para adaptar el código fuente y que el programa funcione de forma satisfactoria tanto en Linux como en Windows. No obstante, como vamos a ver en los siguientes puntos, se pueden producir algunos problemas debidos, sobre todo, a que la codificación de caracteres que emplean los sistemas operativos puede ser distinta.

La codificación de caracteres en el código fuente

La Wikipedia define la codificación de caracteres como el “método que permite convertir un carácter de un lenguaje natural (alfabeto o silabario) en un símbolo en otro sistema de representación, como un número o una secuencia de pulsos eléctricos en un sistema electrónico, aplicando normas o reglas de codificación”.

Si programamos una aplicación para distintas plataformas, puede ocurrir que estas normas de codificación varíen. El problema aparece si utilizamos, por ejemplo, caracteres distintos a los del abecedario inglés, ya que la información que los componen será interpretada de forma distinta según la codificación que el sistema operativo emplee; esto da lugar a la aparición de mensajes con caracteres extraños e inesperados cuando ejecutamos programas confeccionados en una plataforma distinta.

Para paliar este problema es necesario que todo el código fuente que forma la aplicación que estemos diseñando se edite utilizando la misma codificación. Últimamente se está imponiendo en los entornos Linux la codificación UTF-8; si disponemos de miembros de nuestro equipo que programen bajo Windows sería interesante que utilizaran editores de texto que permitan esta codificación.

Por otra parte, tenemos que asegurar que las cadenas de texto que se van a cargar en el programa se interpreten utilizando la regla de codificación adecuada. Esto lo podemos establecer al principio de la aplicación (función main()), utilizando la clase QTextCodec; concretamente la función:

QTextCodec::setCodecForTr( QTextCodec::codecForName(“utf8”) )

A lo largo del programa utilizaremos la función tr(), que proviene de la clase QObject, cada vez que hagamos mención a alguna cadena literal. Esto nos va a permitir que la codificación de los caracteres se interprete de forma correcta, y además nos ayudará a traducir el programa con el módulo QT-Linguistic.

Archivos de texto y codificación de caracteres
Si pensamos utilizar archivos de texto para guardar la configuración o datos del programa en ejecución, también nos afectará el problema de la codificación. Las librerías QT definen el tipo de datos QString que se encarga de almacenar las cadenas de caracteres que el usuario introduce, por ejemplo, a través de una tabla o línea de introducción de texto.
El tipo QString siempre almacena la información en codificación Unicode. A la hora de abrir un fichero de texto para lectura o escritura tendremos esto en cuenta; deberemos asegurar que la información del flujo de texto (stream) viaja como utf-8 y no bajo la codificación local.
Algo parecido ocurre con las rutas y los ficheros. En este caso el problema es distinto ya que tendremos que convertir la cadena de caracteres con la ruta y/o nombre de fichero a la codificación local para que el sistema operativo la interprete correctamente. En el Listado 5 podemos observar como se han tratado de evitar todos estos inconvenientes para la plataforma Windows.

QString directorio;
directorio=directorio.fromLocal8Bit(getenv(“USERPROFILE”));
directorio.append(QDir::separator());
directorio+=tr(”configuración.txt”);
QFile fichero( QFile::encodeName(directorio) );
if ( !fichero.open( QIODevice::WriteOnly ) )
{
QMessageBox::warning( this, tr(“Grabación de fichero”),
tr(“Error: Imposible grabar fichero”));
return false;
}
QTextStream stream( &fichero );
stream.setCodec(“UTF-8”);
stream << tr(“Ésta es una prueba”) << “\n”;

fichero.close();

En la segunda línea obtenemos el directorio personal del usuario actual bajo Windows; para ello consultamos la variable de entorno USERPROFILE. Como podemos observar, nos hemos cuidado de convertir la codificación de caracteres ASCII de ocho bits a Unicode empleando el método fromLocal8Bit de la clase QString.
En la siguiente línea hay una función separator(), perteneciente a la clase Qdir, que nos proporciona el separador de rutas para la plataforma que estemos utilizando.
Más adelante, cuando definimos la variable “fichero” realizamos la conversión del nombre a la codificación local mediante la función Qfile::encodeName.
Por último, al definir el flujo de texto con la instrucción stream.setCodec(“UTF-8”), especificamos la codificación de caracteres a utilizar.
Como podemos observar a lo largo del código fuente utilizamos la función tr cada vez que queremos suministrar una cadena de caracteres. Esto nos asegura que el compilador interprete de forma correcta la codificación, y además nos evita introducir caracteres especiales mediante secuencias de códigos.

Controlador Windows para la base de datos MySQL
Cuando se porta a Windows una aplicación realizada bajo el entorno Linux nos podemos llevar la sorpresa de que la conexión con la base de datos que tan estupendamente funcionaba, no logra ni siquiera cargar los drivers necesarios. Esto es debido a que las QT4 para Windows no incorporan los controladores para las bases de datos compilados y enlazados con las librerías correspondientes. En la mayoría de las distribuciones “Linux” esto no ocurre ya que los empaquetadores de software se encargan de realizar esta tarea.
Para solucionar este problema, partiremos de la instalación de los fuentes de las librerías QT y del paquete MySQL para Windows. A la hora de instalar MySQL, indicaremos como destino de instalación C:\mysql; de esta forma evitaremos nombres de directorios que contengan espacios, tales como “Archivos de programa”, ya que esto dificultaría la compilación bajo MinGW.
Para simplificar, vamos a suponer que las fuentes de las librerías QT van a estar en el directorio c:\qt\src; muchas veces las podremos encontrar en otras rutas, como C:\Qt\4.3.3\src
También nos harán falta varias herramientas del paquete mingw-utils, a las que haremos mención más adelante, y que podremos descargar de su página de proyecto en http://sourceforge.net/projects/mingw/
Las librerías de tipo cliente que acompañan a MySQL (libmysql.dll y libmysql.lib) están compiladas con el compilador de Microsoft y no las podemos utilizar con MinGW; esto es debido a que Microsoft utiliza un nuevo estilo de librerías de importación no compatible. Este problema lo solucionaremos desde la línea de comandos (símbolo del sistema del Windows) de la siguiente forma:

> cd c:\mysql\lib\opt
> reimp -d libmysql.lib
> dlltool -k –input-def libmysql.def –dllname libmysql.dll –output-lib libmysql.a

reimp es una herramienta que convierte el nuevo sistema de librerías de importación de Microsoft en otro compatible con los puertos WIN32 de las herramientas GNU (MinGW y Cygwin); lee la librería original y escribe todos los “import” en el fichero libmysql.def, que será procesado por dlltool para crear la librería libmysql.a compatible con GNU.

Tanto reimp como dlltool forman parte de las utilidades mingw-utils

El siguiente paso será construir el plugin dll para QMYSQL. Para ello necesitaremos tener instalado el paquete MySQL C Include Files/Lib Files :

> cd c:\qt\src\plugins\sqldrivers\mysql
> qmake -o Makefile “INCLUDEPATH+=C:\MYSQL\INCLUDE” “LIBS+=-LC:\MYSQL\LIB\OPT -lmysql” mysql.pro
> mingw32-make

Al final de este proceso tendremos en el directorio c:\qt\plugins\sqldrivers los archivos qsqlmysql.dll, qsqlmysqld.dll, libqsqlmysql.a, libqsqlmysqld.a

Ya sólo nos quedará proceder a la compilación de las librerías QT. Para ello no situaremos en el directorio principal de los fuentes y ejecutaremos la herramienta configure, a la que añadiremos el modificador -plugin-sql-mysql para que el driver figure como un plugin a la hora de ejecutar el programa compilado. Una vez realizado este proceso, compilaremos con make y obtendremos nuestras librerías con soporte para MySQL.

Asignar icono a la aplicación
Este proceso varía bastante según la plataforma en que nos encontremos. En Windows el icono forma parte del archivo ejecutable, mientras que en Linux permanece independiente del binario en cuestión.

Para incorporar un archivo .ico a nuestro programa compilado bajo Windows, crearemos un fichero de texto con extensión .rc al que añadiremos la siguiente línea:

IDI_ICON1 ICON DISCARDABLE “keme.ico”

Por último, añadiremos el siguiente contenido a nuestro proyecto qt (archivo con extensión .pro):

RC_FILE = fichero.rc

Sustituiremos fichero.rc por el nombre del archivo .rc creado con anterioridad.
La compilación del programa bajo Windows generará el ejecutable con el icono asignado.

En Linux la problemática es distinta, ya que tendremos que crear un archivo de tipo “.desktop”. Para ello abriremos el menú contextual del escritorio (KDE ó GNOME) pulsando con el botón derecho del ratón en cualquier parte del mismo, y elegiremos la opción “Crear Nuevo –> Enlace a aplicación”. Bajo KDE se abrirá el cuadro de diálogo que se muestra en la Figura 3.

figura3

Al pulsar el botón con el gráfico de la rueda dentada, aparecerá un nuevo cuadro de diálogo en el que podremos suministrar el icono correspondiente (ver Figura 4).

figura4

Para terminar la definición de nuestro elemento de escritorio tendremos que suministrar más información en la pestaña “Aplicación”, en el diálogo de propiedades. Concretamente añadiremos una descripción del programa y el comando que utilizamos para llamar al mismo.

A la hora de la distribución del programa, tendremos que asegurarnos que el fichero gráfico del icono estará disponible en el ordenador de destino y en la misma ruta que hemos especificado a la hora de seleccionar el icono.

Distribución de la aplicación
Bajo el sistema operativo “Linux” es habitual distribuir los fuentes del programa bajo un archivo de tipo .tar.gz . Cuando se descomprime queda listo para la compilación; normalmente figura un script con órdenes para instalación; proceso que ubicará los archivos en su lugar correspondiente.

Otra forma de distribución es la de paquetes con los binarios adaptados a cada distribución linux. Esto facilita enormemente la difusión de nuestra aplicación ya que el usuario final se despreocupa de aspectos complicados tales como la compilación del programa o la ejecución de scripts con privilegios de administrador. Además, en el mismo paquete se establecen las reglas de dependencia, con lo cual se evitan conflictos con otro software, o simplemente que el programa no funcione debido a que falte algún componente.

Bajo Windows lo ideal sería disponer de una utilidad que nos permitiera confeccionar un setup para la instalación del programa.
Existen varias aplicaciones en software libre, algunas muy conocidas como InstallJammer o Inno. La primera es multiplataforma aunque su ámbito de utilización se circunscribe más bien para entornos Linux; mientras que la segunda se emplea sólo en Windows. Esta última merece una mención especial por sus prestaciones y facilidad de utilización.

Inno Setup está programado en Delphi, y es un claro rival de muchos instaladores comerciales, e incluso los supera tanto en características como en estabilidad. Esta utilidad nos va a permitir, entre otras cosas, crear grupos e iconos de programas en el menú de Inicio, realizar entradas en ficheros INI y modificar el registro de acuerdo a las necesidades del programa instalado.

Inno posee dos modos de utilización: el más completo incluye un motor basado en scripts al estilo del lenguaje de programación Pascal, mientras que el segundo es mucho más intuitivo ya que parte de la utilización de un Setup Wizard. La Figura 5 muestra una ventana que nos aparece en la pantalla inicial de la aplicación, en la que podremos elegir entre uno u otro sistema.

figura5

Si seleccionamos el botón de radio correspondiente al Script Wizard, la aplicación nos mostrará una ventana de bienvenida (ver Figura 6) a partir de la cual iremos completando los pasos necesarios para llegar a la compilación del archivo de instalación.

figura6

Bajo Windows nos aseguraremos de adjuntar todos los archivos de librerías necesarios, concretamente no debe de faltar mingwm10.dll, QtCore4.dll, QtGui4.dll. Si nuestra aplicación utiliza el módulo de conexión con bases de datos (MySQL), deberemos de incluir libmysql.dll y QtSql4.dll.

También es conveniente que efectuemos una prueba de instalación en un ordenador distinto al que empleamos en el desarrollo, para asegurarnos de que no hemos omitido ningún elemento.

Conclusión
En este artículo hemos dado un breve repaso a algunas incidencias que nos podemos encontrar con la programación multiplataforma utilizando las librerías QT. Hemos descubierto que con poco esfuerzo podemos hacer que nuestro código fuente funcione en cualquier sistema operativo. Esto sin duda hace posible que las aplicaciones lleguen a más usuarios finales, con el consiguiente ahorro de trabajo y tiempo.
Por otra parte, siempre se ha achacado a la programación Opensource el ser algo rudimentaria por falta de herramientas visuales orientadas al desarrollador. Esto ha sido superado gracias a Trolltech con la publicación de QTCreator, un verdadero entorno de programación que permiten la integración del qt-designer junto con las librerías y herramientas de las QT; de esta forma podemos disfrutar de programación visual adaptada a cualquier plataforma.

 

 

21 comments on “Aplicaciones multiplataforma con QT4 y C++
  1. julissa dice:

    hola, me gusto

  2. jesus fernando arista sales dice:

    por favor requiero instalar q,tcore4dll porque mi maquina tiene problemas, indica “ERROR AL INICIAR LA APLICACION PORQUE NO SE ENCONTRO QtCore4dll, LA REINSTALACIO DE LA APLICACION PUEDE SOLUCIONAR EL PROBLEMA”

  3. dibosa dice:

    Hola Fernando, parece que te falta un archivo de las librerías de las QT en la aplicación que quieres arrancar. Para solucionarlo tienes que identificar la versión de las QT con las que trabaja tu programa y conseguir este fichero y copiarlo al directorio donde esté instalado el binario que quieres arrancar, que podría ser algo así como “c:\Program Files\programa”.

    En una instalación de las librerías en Windows, puedes encontrar este archivo que necesitas en un directorio parecido a (depende de la versión):

    c:\qt\4.4.3\lib

    Un saludo,

    José Manuel

  4. Juan Carlos dice:

    Hola muy buena la info, me intersa la parte de compilar los driver para base de dato, podrias agregar una guia de como crear el plugin para postgres, gracias

  5. lucas dice:

    A mi no me parece que QT haya marcado un hito importante en el mundo del codigo abierto, porque ya habia librerias graficas multiplataformas q ademas son libres.

    • dibosa dice:

      No se está cuestionando la existencia de otras librerías, ya se conocían por ejemplo las “gtk” con el diseñador “glade” – simplemente se menciona que para los que estábamos desarrollado para las QT, fue un avance muy importante.

      Un saludo,

      José Manuel

  6. Alvaro dice:

    hola,
    tengo un problema con el Desarrollo de una sencilla aplicación multiplataforma para linux me sale un error:

    cvr19:~/Desktop/download alvaro/prueba/fechas # make
    g++ -c -m64 -pipe -O2 -fmessage-length=0 -O2 -Wall -D_FORTIFY_SOURCE=2 -fstack-protector -funwind-tables -fasynchronous-unwind-tables -g -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_GUI_LIB -DQT_CORE_LIB -DQT_SHARED -I/usr/share/qt4/mkspecs/default -I. -I/usr/include/QtCore -I/usr/include/QtCore -I/usr/include/QtGui -I/usr/include/QtGui -I/usr/include -I. -I. -I. -o fechas.o fechas.cpp
    In file included from fechas.cpp:5:
    fechas.h:16: error: ‘fechas’ in namespace ‘Ui’ does not name a type
    fechas.cpp: In constructor ‘fechas::fechas()’:
    fechas.cpp:8: error: ‘ui’ was not declared in this scope
    fechas.cpp: In member function ‘void fechas::calcula_diferencia()’:
    fechas.cpp:16: error: ‘ui’ was not declared in this scope
    make: *** [fechas.o] Error 1
    cvr19:~/Desktop/download alvaro/prueba/fechas #

    me puenden ayudar…….

  7. dibosa dice:

    Hola Álvaro,

    Por lo que veo parece que no ha entrado el compilador uic, comprueba que el contenido del archivo .pro sea correcto. No olvides antes de ejecutar make ejecutar qmake,

    Un saludo

  8. Luis dice:

    Hola José Manuel,

    Estoy intentando compilar la versión 2.5.1 de keme con los parches disponibles y tengo el siguiente problema.
    Instalo los archivos de las librerias necesarias desde un .zip, añadiendo el directorio destino al PATH del sistema, pero cuando ejecuto la aplicación no me deja crear ninguna empresa (se queja de no poder operar con el driver de qsqlite). He amadido la libreria qsqlite4.dll al directorio pero sin exito.

    Espero que me puedas ayudar.

    Un saludo,

    Luis

  9. Luis dice:

    Hola de nuevo. Ya he encontrado el problema. La librería qsqlite4.dll debe de estar dentro de un directorio llamado sqldrivers.

  10. Roberto dice:

    Esta bien que me pida tambien las QtCored4.dll y la QtGuid4.dll ???
    Por que estas 2 bibliotecas me suman como 170 Mb al paquete.
    Vos solo usas las versiones sin la d ?
    Saludos

    • dibosa dice:

      Hola Roberto,

      estas librerías con la ‘d’ se corresponden con las de la compilación en modo ‘debug’ por eso ocupan mucho espacio.

      Para distribuir la aplicación deberías compilar en modo ‘release’, para eso añade la siguiente línea al fichero del proyecto (.pro):

      CONFIG += release

      Un saludo,

      José Manuel

      • Roberto dice:

        Gracias por la respuesta, aclaro que estoy usando el QtCreator y solo cambiando en ajustes de construccion a Release anduvo de una.
        Me tenia preocupado eso desde hace tiempo cuando pensabamos como distribuir el programa y no me habia dado cuenta de ese detalle.
        Muchas gracias!

        PD: Estas anotado en algun foro de Qt donde pueda consultarte sobre otros temas?. Saludos

  11. Jose M dice:

    Muchas gracias por la información, me ha parecido fantástica

  12. andres dice:

    El ejemplo de las fechas está bastante ilustrativo, sin embargo tiene errores, para hacerlo correr es necesario hacer modificaciones al código. Pero repito está bien. Gracias

    • dibosa dice:

      Hola Andrés, quizás los errores que comentas se deban a que la versión de las QT en las que se desarrollaron sea un poco antigua. El ejemplo compiló y se ejecutó perfectamente. Te animo a que publiques los modificaciones necesarias y el entorno sobre el que desarrollas,

      Un saludo,

      José Manuel

  13. ruben sanabria dice:

    quisiera aprender qt, me parece una buena alternativa a java..gracias desde paraguay, ruben

  14. ruben sanabria dice:

    exelentes tutoriales referentes a ireport y qt, creo que no existen muchos reporteadores para utilizarlos con qt, me encuentro con la grata sorpresa que si se puede…ahora falta como ejecutarlos desde el qt, o adjuntarlo dentro del mismo, el jasper…

  15. Javier dice:

    Yo vengo de Java y ultimamente estoy probando la programación en c++ con Qt, pero encuentro algo incómodo tener que prescindir del manejo de errores con bloques try/catch. En java es obligatorio (y muy cómodo), en cambio, a pesar de que c++ también admite los bloques try/catch, Qt no los utiliza y esto obliga a utilizar señales o numerosas condiciones if. “Googleando”, encontré alguna información sobre la posibilidad de modificar QCoreApplication::notify() para que los errores en Qt lancen algún tipo de excepción, pero no sé si valdría la pena probar esta modificación. ¿alguna idea?

    • dibosa dice:

      Hola Javier, lo que comentas es muy interesante, no obstante supondría un gran esfuerzo modificar todo el código fuente del programa para adaptarlo al método de captura de errores.
      A mediados del año que viene se van a liberar las QT 5.0, esto va a suponer que tengamos que comenzar la migración a estas nuevas librerías. Ésta podría ser una buena ocasión para introducir una nueva filosofía en la forma de desarrollar la aplicación; por ejemplo, tenemos planeado el empleo de varias ventanas no modales para que la experiencia del usuario sea más satisfactoria.

      Un saludo,

      José Manuel

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: