jueves, 11 de febrero de 2016

Cómo construir una aplicación con App Engine


Contenido:

•    Público objetivo
•    Requisitos
•    Descargar Código de ejemplo
•    Backend App Engine
•    Cliente Android
•    Implementar la aplicación de ejemplo
•    Cliente iOS
•    Recursos adicionales

La aplicación de ejemplo utilizado en este tutorial es el Asistente Mobile Shopping. La aplicación permite a los usuarios obtener información sobre los productos en la tienda que se encuentran, así como encontrar las tiendas cercanas.
La aplicación incluye un código personalizado para conectar el cliente básico de aplicación a la nube backend App Engine, a través de Google Cloud Endpoints. La aplicación puede entonces determinar la ubicación del usuario, localizar las tiendas cercanas, y permitir al usuario obtener ofertas y recomendaciones pertinentes.
La siguiente figura muestra los componentes principales de la aplicación de ejemplo.

 

Público objetivo

Este tutorial está dirigido a cualquier desarrollador interesado en aprender cómo construir los servicios en la nube y los utilizan de aplicaciones móviles que dan a los clientes un entorno más atractivo y mejor experiencia.
Usted necesita saber cómo programar en Java. También, usted debe estar familiarizado con la creación de aplicaciones móviles (Android o iOS, dependiendo de la plataforma de destino). No se requiere un conocimiento básico de la tecnología de App Engine, pero es una ventaja.

Requisitos

Asegúrese de que ha instalado las siguientes herramientas y paquetes para el tutorial:
•    Android Studio versión 1.0 o superior.
•    IDEA Checkstyle (opcional), que ofrece tanto en tiempo real y análisis bajo demanda de los archivos Java con CheckStyle 6.5. O, de la androide de estudio, con la idea de Checkstyle-plugin y Android SDK de 21 instalados.

Descargar Código de ejemplo

Para continuar con el tutorial, descargar el código de la acplicación movíl Asistente de compras.

Backend App Engine

En esta sección se describe cómo configurar la aplicación backend móvil que funciona con Google Cloud Platform (App Engine). La aplicación contiene el repositorio de datos y la lógica de negocio para apoyar las solicitudes de los clientes móviles. En este tutorial se utiliza Android Estudio de configurar y desplegar el back-end que será utilizada tanto por el Android y el IOS clientes.
El uso de Android Studio para configurar el backend (tanto para clientes) tiene las siguientes ventajas:
•    Android Studio le da la integración directa con App Engine.
•    A través de Android Studio, puede generar automáticamente las bibliotecas de cliente Cloud Endpoints necesarios para acceder al servidor.

Crear el proyecto de back-end

Para crear el proyecto de back-end en Android Studio, siga las instrucciones para añadir un módulo de mensajería de la nube . El nombre del paquete puede ser algo como com.google.sample.mobileassistantbackend .

presenta un mapa de los objetos de datos persistentes:
o    CheckIn
o    Offer
o    Place
o    PlaceInfo
o    Recommendation
o    Registration
o    UserAccount
•    com.google.sample.mobileassistantbackend. apis archivos -Contiene de punto final que exponen APIs REST para determinados recursos, tales como el registro, Place, oferta, recomendación, y mensajería (para los mensajes de Google mensajería en la nube entre la aplicación y un dispositivo registrado).
•    com.google.sample.mobileassistantbackend. util - CheckInUtil, EndpointUtil, PlacesHelper.
•    Constants -tiendas todas las claves de la API y los identificadores
•    EndpointUtil se utiliza para comprobar la autenticación de un usuario.
•    OfyService - objectify contenedor de servicio que permite registrar de forma estática clases de persistencia.
Se crea una clase de entidad como describe en la siguiente sección. Se repite el proceso para cada una de las entidades.

Escribe el backend

Ahora ya está listo para empezar a construir la aplicación mediante la creación de las clases de entidad que representan los datos de back-end de la aplicación. Una clase de entidad proporciona una "interfaz de objeto-relacional" entre la aplicación y el repositorio de datos back-end.
Crear una clase de entidad
Los ejemplos de esta sección se basan en el Checkin entidad. Es necesario crear una clase de entidad para cada una de las entidades en su aplicación. Para crear una clase de entidad:
1.    En el Explorador de paquetes, vaya a la carpeta modelos bajo su proyecto de back-end, y seleccione Archivo> Nuevo> Java Class. Aparecerá el asistente Nueva clase de Java.
2.    En el nombre: cuadro de introducir el nombre de la clase que representará a la entidad, por ejemplo, CheckIn .
3.    Haga clic en Ok.
4.    En el editor, reemplace el código placa de la caldera con el código de la CheckIn.java clase que se puede obtener de la muestra descargado. Observe que el código que se muestra proporciona una clase de "listo para funcionar". Las cosas más importantes que usted debe prestar atención son las propiedades y las anotaciones.
5.    Guarde la clase.

Generar una nube de puntos finales Clase

Para cada clase de entidad, es necesario generar una clase de nube de puntos finales para darle a su aplicación cliente de acceso a los datos de back-end. Para generar una clase de nube de puntos finales:
1.    Crear una clase CheckInEndpoint en el backend/apis paquete.
2.    Abra src/main/webapp/WEB-INF/web.xml y añadir la ruta completa nueva clasecom.google.sample.mobileassistantbackend.apis.CheckInEndpoint en el  sección, de la siguiente manera:

<servlet>
    <servlet-name>SystemServiceServlet</servlet-name>
    <servlet-class>com.google.api.server.spi.SystemServiceServlet</servlet-class>
    <init-param>
        <param-name>services</param-name>
        <param-value>
            com.google.sample.mobileassistantbackend.apis.CheckInEndpoint,
            ...
        </param-value>
    </init-param>
</servlet>
 
3.    Abra la clase de servicio Objectify OfyService.java . En el poco estática, añada la líneaObjectifyService.register(CheckIn.class) para registrar el CheckIn clase como persistente:

public final class OfyService {
    /**
     * Default constructor, never called.
     */
    private OfyService() {
    }

    static {
            ...
            factory().register(CheckIn.class);
            ...
    }
    ...

    /**
     * Returns the Objectify factory service.
     * @return The factory service.
     */
    public static ObjectifyFactory factory() {
        return ObjectifyService.factory();
    }
}

 Cliente Android

En esta sección se ofrece un resumen de la creación de una aplicación cliente de Android que puede comunicarse con su base de App Engine.

Crear el proyecto de cliente Android

La aplicación de cliente móvil proporciona la interfaz que permite que el cliente interactúe con el back-end, por ejemplo, para obtener información de ventas.
Para crear un proyecto de aplicación de cliente móvil:
1.    En Android Studio, seleccione Archivo> Nuevo proyecto.
2.    Se muestra la ventana del asistente Nuevo proyecto.
3.    En el cuadro Nombre de la aplicación, introduzca el nombre de la aplicación, por ejemplo MobileAssistant.
4.    En el cuadro Nombre del paquete, escriba el nombre del paquete de la aplicación, por ejemplocom.google.samplesolutions.mobileassistant.
5.    En el diálogo de los dispositivos de destino Android, acepte los valores por defecto de factor de forma y SDK mínimo, y haga clic en Siguiente.
6.    En el Agregar una actividad de diálogo móvil, aceptar la selección por defecto "actividad en blanco", y haga clic en Siguiente.
7.    En el cuadro de diálogo Personalizar la actividad, realizar las modificaciones que desee a su actividad, y haga clic en Finalizar.

Lugares de visualización de datos

En Android Studio, vaya a la android proyecto y abrir MainActivity.java . Incluye un AsyncTask llamadaListOfPlacesAsyncRetriever que recupera la lista de lugares cercanos (por ejemplo, tiendas) de la correspondiente y actualiza laListView (cliente de aplicaciones de interfaz de usuario) y la etiqueta. ListOfPlacesAsyncRetriever se llama a partir de MainActivity onCreate() método.
La variable placesList contiene la información obtenida desde el servidor.
La aplicación cliente crea una instancia de la clase ListOfPlacesAsyncRetriever en el momento de la creación y llama a su método de ejecución (en el onCreate() método).
La aplicación cliente realiza llamadas a la API asíncronas cuando un cliente registra en un lugar. En el código de ejemplo, se recupera la información de lugares desde el servidor.

Modificar activity_main.xml

Los resultados de back-end que se recuperan en MainActivity se asignan a la interfaz de usuario de aplicación a través de archivos de recursos XML de la aplicación:
1.    En la aplicación de ejemplo en Android Studio, expanda la carpeta res y luego la carpeta de presentación.
2.    Abra la activity_main.xml archivo. Este extracto asigna los datos de lugares de back-end recuperados en onCreate de la interfaz de usuario de la aplicación cliente:
<ListView
    android:id="@+id/PlacesList""
.../>

Esto define una ListView elemento de interfaz de usuario que muestra la lista de las tiendas.
El último paso es conectar el placesList variable en OnCreate a la ListView en activity_main.xml :

placesList = (ListView) findViewById(R.id.PlacesList);
Esto permite el acceso al widget para que pueda establecer nuevos contenidos (como la lista de las tiendas). Aviso, puede abrir laactivity_main.xml archivo XML usando el editor de Android (visual) y se puede asignar el valor @+id/PlacesList a la propiedad Id.
Aquí están los archivos de recursos XML que definen la interfaz de usuario de la aplicación:
•    activity_main.xml . Este archivo está en el res/layout carpeta y define el diseño de interfaz de usuario para los botones, y la lista de lugares.
•    place_item.xml . Este archivo está en el res/layout carpeta y contiene información sobre el formato de visualización y la posición de los lugares.
•    strings.xml . Este archivo está en el res/values carpeta y contiene cadenas predefinidas para mostrar en varias ocasiones.
Para conocer más la creación de una interfaz de usuario de una aplicación para Android, consulte el Android interfaz de usuario de guía para desarrolladores.

Pantalla coloca vista detallada

El placesListClickListener controlador de eventos se invoca cuando el cliente hace clic en un elemento de la lista de lugares cercanos. El controlador comprueba de forma asíncrona al cliente en el lugar seleccionado y navega a la actividad que va a presentar los detalles acerca de este lugar:
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
   /**
     * Event handler invoked when user clicks on an item in the list of nearby
     * places. It asynchronously checks the user into the selected place and
     * navigates to the activity that will present details about this place.
     */
    private OnItemClickListener placesListClickListener
            = new OnItemClickListener() {
        @Override
        public void onItemClick(final AdapterView<?> arg0, final View arg1,
                final int arg2, final long arg3) {
            PlaceInfo selectedPlace = places.get((int) arg3);

            new CheckInTask().execute(selectedPlace);

            PlaceDetailsActivity.setCurrentPlace(selectedPlace);
            Intent i = new Intent(MainActivity.this,
                    PlaceDetailsActivity.class);
            startActivity(i);
        }
    };
En el método onCreate(), se estableció el gestor de la siguiente manera:
placesList = (ListView) findViewById(R.id.PlacesList);
placesList.setOnItemClickListener(placesListClickListener);
Tenga en cuenta que la placesListClickListener controlador de eventos se inicia una nueva actividad para mostrar la interfaz de usuario de sitios. Todas las actividades deben ser definidos en AndroidManifest.xml :

<activity android:name=
"com.google.samplesolutions.mobileassistant.PlaceDetailsActivity" >
</activity>
El PlaceDetailsActivity.java clase lleva a cabo tareas como la recuperación de ofertas y recomendaciones de forma asíncrona.La clase de forma asíncrona recupera la lista de ofertas y recomendaciones a través de los criterios de valoración adecuados y actualizar el correspondiente ListView y una etiqueta para cada uno:
import com.google.sample.mobileassistantbackend
        .shoppingAssistant.ShoppingAssistant;
import com.google.sample.mobileassistantbackend
        .shoppingAssistant.model.Offer;
import com.google.sample.mobileassistantbackend
        .shoppingAssistant.model.OfferCollection;
import android.os.AsyncTask;
import android.widget.ListView;
import android.widget.TextView;

/**
  * The class automatically generated by the Google App Engine backend
  * project, used to access the API easily from the Android application.
  */
  private ShoppingAssistant shoppingAssistantAPI;

private static PlaceInfo currentPlace;
private ListView offersList;
private ListView recommendationsList;
private TextView placesNameLabel;
private TextView offersListLabel;
private TextView recommendationsListLabel;
...

/**
  * Initializes the activity content, binds relevant widgets and starts
  * asynchronously retrieving offers and recommendations.
  */
@Override
protected final void onCreate(final Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    shoppingAssistantAPI = CloudEndpointBuilderHelper.getEndpoints();

  ...
    offersList = (ListView) findViewById(R.id.OffersList);
    recommendationsList = (ListView) findViewById(R.id.RecommendationsList);

    placesNameLabel = (TextView) findViewById(R.id.PlacesNameLabel);
    offersListLabel = (TextView) findViewById(R.id.OffersListLabel);
    recommendationsListLabel = (TextView) findViewById(
            R.id.RecommendationListLabel);

     placesNameLabel.setText(currentPlace.getName());

    retrieveOffers();
    retrieveRecommendations();
}

...
/**
  * Retrieves the list of offers through appropriate CloudEndpoint.
  * @param params the place for which to retrieve offers.
  * @return collection of retrieved offers.
  */
    @Override
    protected OfferCollection doInBackground(final PlaceInfo... params) {
        PlaceInfo place = params[0];

        if (place == null) {
            return null;
        }

        OfferCollection result;

        try {
            result = shoppingAssistantAPI.offers().listOffers().execute();
        } catch (IOException e) {
            ...
            }
            return result;
        }
    }
código similar se aplica a la colección recomendaciones.

Implementar la aplicación de ejemplo

En esta sección se describe cómo implementar y ejecutar la aplicación de ejemplo. He aquí un resumen de los pasos:
1.    Descarga la aplicación de la muestra, y añadir los valores y las credenciales necesarias.
2.    Construir y desplegar el backend.
3.    Poblar el almacén de datos con datos.
4.    Reconstruir el índice de almacén de datos. Busque en entidades del almacén de datos en la nube de la plataforma de la consola de Google para asegurarse de que el almacén de datos contiene los datos esperados.
5.    Ejecutar la aplicación en un dispositivo conectado o en el emulador.
Estos pasos se describen con más detalle en las siguientes secciones.

Descargar e instalar la aplicación de ejemplo

Descargar el código de la MobileAssistantAndroidAppEngine carpeta en móvil Asistente de compras.
Antes de poder ejecutar la aplicación de ejemplo, es necesario agregar las identificaciones y credenciales necesarias para su versión de la aplicación:
1.    Crear el nuevo proyecto.
2.    Obtener una GCM_API_KEY como se describe en la siguiente tabla, y seguir la documentación de la nube de puntos finales para obtener el ID de cliente ANDROID_CLIENT_ID , IOS_CLIENT_ID , y WEB_CLIENT_ID . Para el ID de cliente de Android es el nombre del paquete com.google.sample.mobileassistant , y para el ID de cliente iOS el paquete escom.google.sample.MobileAssistantIOS .
3.    Abrir MobileAssistantAndroidAppEngine/backend/src/main/webapp/WEB-INF/web.xml appengine- e introduzca suAPPLICATION_ID dentro del elemento de aplicación XML y la GCM_API_KEY dentro del gcm.api.key elemento XML.
4.    Open MobileAssistantAndroidAppEngine/backend/src/main/java/com/google/sample/mobileassistantbackend/Constants.java y insertar las claves de la API y los ID necesarios.
5.    Abrir MobileAssistantAndroidAppEngine/android /config.gradle e insertan SENDER_ID , WEB_CLIENT_ID , y la dirección URL de su aplicación desplegada, que es https://YOUR-APPLICATION-ID.appspot.com/_ah/api/ , y establecer SIGN_IN_REQUIRED a "true".

La siguiente tabla resume los cambios que necesita hacer para los archivos de la aplicación de ejemplo.

Construir y desplegar el backend
 
En Android Studio seleccione Generar> Implementar el módulo de App Engine y siga las instrucciones. Para más detalles, véaseCorrer, probar e implementar el programa de fondo .

Poblar el almacén de datos con los datos
 
La aplicación de ejemplo incluye los archivos CSV que se utilizan para rellenar el almacén de datos con tiendas, ofertas y recomendaciones.
Para insertar datos en el backend:
1.    Busque los archivos CSV en su aplicación de muestra descargado. Son under ~/MobileShoppingAsst-app/MobileShoppingAssistant-sample-master/MobileAssistantAndroidAppEngine/backend/MobileAssistant-Data .
2.    Editar los archivos si desea añadir sus propios datos de ejemplo para ellos.
3.    Abre un terminal y el tipo gcloud auth login para autenticar con la cuenta de que ha designado como cuenta de administrador de la aplicación.
4.    cd al directorio que contiene los archivos CSV, y ejecute los siguientes comandos para cargar los datos CSV en el back-end:
  appcfg.py upload_data --config_file bulkloader.yaml --url = http: //  / remote_api --filename places.csv --kind = Lugar -e
 appcfg.py upload_data --config_file bulkloader.yaml --url = http: //  / remote_api --filename recommendations.csv --kind = Recomendación -e
 appcfg.py upload_data --config_file bulkloader.yaml --url = http: //  / remote_api --filename offers.csv --kind = Presente -e
Tenga en cuenta lo siguiente:
•    Se le pedirá que ingrese. No se salte este paso.
•    La URL de la muestra se basa en su proyecto. Por ejemplo: http://rustic-teacup-92055.appspot.com/remote_api
•    Tenga en cuenta que cada comando se aplica a un tipo diferente de datos. Asegúrese de especificar el tipo correcto; esta información es utilizada por la base de datos.

Reconstruir el índice

Para hacer que sus datos actualizados recientemente a disposición de la aplicación:
1.    Asegúrese de que haya iniciado sesión con su cuenta de aplicación de administración.
2.    Ir a https://  /admin/buildsearchindex . Por ejemplo, https://rustic-teacup-92055.appspot.com/admin/buildsearchindex .

Compruebe el almacén de datos en la consola de la plataforma de la nube
 
Para asegurarse de que los datos subido correctamente:
1.    Abiertas las entidades del almacén de datos en la consola de la plataforma Cloud.
2.    Desde el tipo del menú desplegable, compruebe Place, Recomendación, y se ofrecerá a asegurarse de que sus datos han subido correctamente. 

Prueba a nivel local
 
Si se desea, se puede comprobar la configuración de back-end a nivel local, de la siguiente manera:
1.    En el config.gradle archivo en Android Studio, establezca los siguientes valores:
2.     buildConfigField "Cadena", "root_url",
3.            "\" Http: //10.0.2.2: 8080 / _ah / api / \ ""
4.   
5.     buildConfigField "booleano", "SIGN_IN_REQUIRED", "falso"
Nota: No se olvide de cambiar estos valores hacia atrás antes de desplegar la aplicación.
6.    Siga los pasos de poblar el almacén de datos con los datos , pero en lugar de especificar un URL basado en el ID de proyecto, utilice la URL localhost. Por ejemplo, el comando agrega los datos de Lugar:
7.      appcfg.py upload_data --config_file bulkloader.yaml --url = http: // localhost: 8080 / remote_api --filename places.csv --kind = Lugar -e
8.    Reconstruir su índice de almacén de datos, como se describe en reconstruir el índice , pero el uso de su URL localhost:
9.     http: // localhost: 8080 / admin / buildsearchindex
10.    Iniciar el backend.
11.    Ejecutar su aplicación utilizando el emulador (un dispositivo conectado no funcionará para las pruebas a nivel local).
12.    Ir a http: // localhost: 8080 / _ah / admin y vaya a almacén de datos Visor para comprobar que el almacén de datos local contiene los datos esperados. Debe haber iniciado sesión con su contraseña de administrador.

Ejecutar la aplicación

 
Para ejecutar la aplicación de ejemplo con el back-end desplegado en la nube:
1.    Construir el back-end, a continuación, seleccione Crear> módulo de App Engine Implementar y seguir las instrucciones. Usted tendrá que acceder desde Android Studio en la cuenta de administrador.
2.    Ejecutar la aplicación.

Cliente iOS
 
El cliente móvil Asistente de compras IOS muestra cómo crear un cliente de iOS que imita la funcionalidad del cliente de Android mediante el aprovechamiento de la misma backend de Google App Engine con la ayuda de Google API Client Library para Objective-C.

La configuración del proyecto, instalación y configuración
 
Este código fuente de la muestra y el proyecto está diseñado para trabajar con Xcode 6.2. La aplicación se probó en iOS 8.2.

Requisitos previos
 
Siga las instrucciones de Implementar la aplicación de ejemplo y desplegar el backend para Google App Engine. Tenga en cuenta que el ID de paquete para este cliente iOS es com.google.sample.MobileAssistantIOS .
Hacer que los datos de la muestra seguro de que ha importado correctamente al programa de fondo desplegado siguientesMobileAssistantAndroidAppEngine / backend / MobileAssistant-Data / README.md .

Configurar proyecto móvil iOS Asistente Cliente Xcode

MobileAssistantIOS proyecto abierto en Xcode

 
Abrir una nueva ventana del Finder y vaya al directorio que haya extraído el Cliente Móvil Asistente iOS. Haga doble clic en elMobileAssistantIOS.xcodeproj archivo. Se abrirá el proyecto en Xcode automáticamente.
Configurar Mobile Asistente de programas de fondo para reconocer la aplicación cliente de iOS
1.    El Asistente de programas de fondo móvil se configura para utilizar un determinado IOS_CLIENT_ID en el paso 2 de los requisitos previos.
2.    Buscar el secreto de cliente, que corresponde a la IOS_CLIENT_ID en el back-end, a partir de la nube de la plataforma de la consola , actualice las siguientes constantes en el ShopsTableViewController.m archivo en el proyecto MobileAssistantIOS:
o    kKeyClientID (en la línea 35)
o    kKeyClientSecret (en la línea 36)
3.    En el / API / GTLServiceShoppingAssistant.m MobileAssistantIOS archivo, reemplazar la cadena "{{{YOUR APP ID}}}" con el ID de la aplicación de que se ha desplegado el Asistente de programas de fondo móvil.
4.    Siga los pasos en medio de criterios en un cliente de iOS . Vea el paso 1, si usted no está utilizando el SDK de iOS Google+.
Construir y ejecutar el proyecto MobileAssistantIOS
1.    Siga los pasos para establecer la ubicación simulador para San Francisco, CA .
2.    En la esquina superior izquierda de la barra de herramientas en Xcode, seleccione MobileAssistantIos.
iPhone 8.2 Simulador. Haga clic en el botón Ejecutar para ejecutar la aplicación.
3.    Cambiar al iOS Simulator aplicación de Xcode. Ahora puede interactuar con el cliente de aplicación MobileAssistantIOS.
•    Desde esta aplicación es sensible a la ubicación, para trabajar con los datos existentes en el móvil Asistente de programas de fondo, establecer la ubicación {Latitude: 37.2222, Longtitude: -122.1111} a través del menú Debug > Location > Custom Location…
•    Si se le solicita, haga clic en "OK" para permitir MobileAssitantIOS aplicación acceda a su ubicación actual.
•    La aplicación puede solicitar la información de su cuenta de Google. Iniciar sesión y el consentimiento para permitir la aplicación para view your email address y know who you are on Google .
•    En la primera pantalla, haga clic en cualquiera de sus tiendas. En la siguiente pantalla, la aplicación mostrará diferentes recomendaciones y ofertas basadas en diferentes ubicación de la tienda.

Echar un vistazo más de cerca a la aplicación cliente MobileAssistantIOS
 
En ShopsTableViewController.m archivo, establecer puntos de interrupción a los métodos siguientes:
•    fetchAllShops
•    fetchOffersForPlaceID
•    fetchRecommendationsForPlaceID
Estos métodos son responsables de hacer las peticiones al móvil Asistente de programas de fondo a través de la biblioteca de clientes API de Google para Objective-C. 
Referencia opcional
Haga clic aquí para obtener más información sobre la generación de la biblioteca de cliente de iOS para Google Cloud punto final.
Recursos adicionales
Para obtener más información sobre la tecnología detrás de la guía de aprendizaje, echa un vistazo a los siguientes recursos:
Herramientas en la nube para Android Studio
Las herramientas en la nube para Android Estudio documentación describe cómo crear y desplegar aplicaciones de Android conectada a la nube a través de Android Studio.
App Engine
•    Google Cloud Endpoints le permiten definir la lógica de negocio en App Engine y acceder a ella a través de las API REST o RPC en múltiples plataformas, incluyendo Android, IOS y JavaScript. Para obtener más información, consulte Descripción general de Google Cloud Endpoints .
•    Empezando. Google App Engine le permite ejecutar aplicaciones web en la infraestructura de Google. Para obtener información de principiante, consulte ¿Qué es Google App Engine ?



0 comentarios:

Publicar un comentario