SDK de Android: Crea una aplicación de escaneo de libros - visualización de información del libro
Spanish (Español) translation by Juliana Carvajal (you can also view the original English article)
Con la biblioteca ZXing, puedes crear aplicaciones de Android con funcionalidad de escaneo de códigos de barras. En Android SDK: Crea un lector de códigos de barras, implementamos la lectura básica de códigos de barras con la biblioteca en una aplicación simple. En esta serie de tutoriales, nos basaremos en lo que aprendimos para crear una aplicación que escaneará libros y recuperará información relacionada sobre ellos desde la API de Google Libros.
Esta serie sobre la creación de una aplicación de escaneo de libros consta de tres partes:
- API de Google Libros y configuración de ZXing
- Creación de interfaces y búsqueda de libros
- Recuperación y visualización de información del libro
Aquí hay una vista previa de la aplicación en la que estamos trabajando:



Cuando se ejecute la aplicación final, el usuario podrá escanear los códigos de barras de los libros. La aplicación presentará una selección de datos sobre los libros escaneados, incluido dónde están disponibles para su compra. La aplicación también proporcionará un enlace a la página de un libro escaneado en Google Libros.
1. Analiza los resultados de la búsqueda
Paso 1
En la última sección, ejecutamos la consulta de búsqueda de Google Libros utilizando el número ISBN (EAN) escaneado, luego trajimos los datos devueltos a la aplicación como una cadena. Ahora analicemos la cadena devuelta, que es un feed JSON. Haremos esto en el método onPostExecute de la clase interna AsyncTask que creamos. Agrega el esquema del método después del método doInBackground que completamos la última vez.
1 |
protected void onPostExecute(String result) { |
2 |
//parse search results
|
3 |
}
|
Cuando analizas una fuente JSON o cualquier otro dato entrante de una fuente de datos externa, primero debes familiarizarte con la estructura de los datos entrantes. Echa un vistazo a las secciones Búsqueda y Volumen en la documentación de la API de Google Libros para ver la estructura de lo que devolverá la consulta de búsqueda.
Usaremos las bibliotecas JSON de Java para procesar los resultados de la búsqueda. Agrega las siguientes importaciones a tu clase de actividad principal.
1 |
import java.io.BufferedInputStream; |
2 |
import java.net.URL; |
3 |
import java.net.URLConnection; |
4 |
|
5 |
import org.json.JSONArray; |
6 |
import org.json.JSONException; |
7 |
import org.json.JSONObject; |
8 |
|
9 |
import android.net.Uri; |
10 |
import android.graphics.Bitmap; |
11 |
import android.graphics.BitmapFactory; |
Paso 2
Comencemos a trabajar con el resultado de búsqueda JSON. En tu método onPostExecute, agrega los bloques prueba y captura para hacer frente a las excepciones de entrada.
1 |
try{ |
2 |
//parse results
|
3 |
}
|
4 |
catch (Exception e) { |
5 |
//no result
|
6 |
}
|
En el bloque de captura, responde a los casos en los que no ha habido un resultado de búsqueda válido.
1 |
e.printStackTrace(); |
2 |
titleText.setText("NOT FOUND"); |
3 |
authorText.setText(""); |
4 |
descriptionText.setText(""); |
5 |
dateText.setText(""); |
6 |
starLayout.removeAllViews(); |
7 |
ratingCountText.setText(""); |
8 |
thumbView.setImageBitmap(null); |
9 |
previewBtn.setVisibility(View.GONE); |
Simplemente configuramos todos los elementos de la interfaz de usuario para indicar que no se devolvió un resultado de búsqueda válido. Esto puede suceder si un libro escaneado no aparece en Google Libros o si algo sale mal al ingresar los datos.
Paso 3
De vuelta en el bloque de prueba, configura el botón de vista previa para que sea visible, asumiendo que tenemos un resultado válido:
1 |
previewBtn.setVisibility(View.VISIBLE); |
Ahora comienza a recuperar los objetos JSON de la cadena devuelta.
1 |
JSONObject resultObject = new JSONObject(result); |
2 |
JSONArray bookArray = resultObject.getJSONArray("items"); |
Como puedes ver en la estructura descrita en las instrucciones de búsqueda en la documentación de la API de Google Libros, el resultado de la consulta incluirá una matriz denominada "elementos" que contiene los resultados que coincidan con el número ISBN transmitido. Dado que solo esperamos un resultado único para el libro escaneado, obtengamos el primer elemento de la matriz.
1 |
JSONObject bookObject = bookArray.getJSONObject(0); |
Ahora necesitamos el objeto "volumeInfo" que contiene algunos de los datos que queremos mostrar.
1 |
JSONObject volumeObject = bookObject.getJSONObject("volumeInfo"); |
Este objeto nos dará acceso a la mayoría de los elementos de datos de libros que estamos buscando.
Paso 4
En algunos casos, encontrarás que el resultado de la búsqueda de Google Libros devolverá algunos, pero no todos, los elementos de datos, por lo que usaremos bloques de prueba y captura repetidos para manejar los casos en los que faltan valores de datos individuales. Empieza por el título.
1 |
try{ titleText.setText("TITLE: "+volumeObject.getString("title")); } |
2 |
catch(JSONException jse){ |
3 |
titleText.setText(""); |
4 |
jse.printStackTrace(); |
5 |
}
|
Si se puedes recuperar la cadena, la mostraremos en la Vista de texto correspondiente. De lo contrario, lo configuraremos para que muestre una cadena vacía, con el bloque de captura que permite que la aplicación continúe procesando los resultados de búsqueda a pesar de este elemento faltante.
A continuación, procesemos el autor, que se representa como una matriz en caso de que haya más de uno. Construiremos la cadena completa usando un bucle.
1 |
StringBuilder authorBuild = new StringBuilder(""); |
2 |
try{ |
3 |
JSONArray authorArray = volumeObject.getJSONArray("authors"); |
4 |
for(int a=0; a<authorArray.length(); a++){ |
5 |
if(a>0) authorBuild.append(", "); |
6 |
authorBuild.append(authorArray.getString(a)); |
7 |
}
|
8 |
authorText.setText("AUTHOR(S): "+authorBuild.toString()); |
9 |
}
|
10 |
catch(JSONException jse){ |
11 |
authorText.setText(""); |
12 |
jse.printStackTrace(); |
13 |
}
|
Los autores estarán separados por comas si hay más de uno; de lo contrario, aparecerá el nombre del autor único. Ahora analiza la fecha de publicación.
1 |
try{ dateText.setText("PUBLISHED: "+volumeObject.getString("publishedDate")); } |
2 |
catch(JSONException jse){ |
3 |
dateText.setText(""); |
4 |
jse.printStackTrace(); |
5 |
}
|
A continuación, procesaremos la descripción.
1 |
try{ descriptionText.setText("DESCRIPTION: "+volumeObject.getString("description")); } |
2 |
catch(JSONException jse){ |
3 |
descriptionText.setText(""); |
4 |
jse.printStackTrace(); |
5 |
}
|
Ahora tratemos con la calificación de estrellas. Recuerda que creamos un diseño y declaramos una matriz para contener las vistas de imagen en estrella. Regresa a tu método onCreate por un momento y crea una instancia de la matriz después del código existente.
1 |
starViews=new ImageView[5]; |
2 |
for(int s=0; s<starViews.length; s++){ |
3 |
starViews[s]=new ImageView(this); |
4 |
}
|
Puede haber un máximo de cinco estrellas. De vuelta en onPostExecute después del último bloque de captura que agregamos para la descripción, agrega bloques de prueba y captura para las estrellas.
1 |
try{ |
2 |
//set stars
|
3 |
}
|
4 |
catch(JSONException jse){ |
5 |
starLayout.removeAllViews(); |
6 |
jse.printStackTrace(); |
7 |
}
|
Cuando recuperemos la cantidad de estrellas otorgadas al libro, agregaremos las Vistas de imagen dinámicamente al diseño que creamos para la calificación de estrellas. Si no se puede recuperar el número de calificación de estrellas, eliminaremos las vistas agregadas anteriormente en el bloque de captura. En este bloque de prueba, recupera el número de estrellas del JSON devuelto y conviértelo en un número entero.
1 |
double decNumStars = Double.parseDouble(volumeObject.getString("averageRating")); |
2 |
int numStars = (int)decNumStars; |
Establece el número de estrellas que se muestran actualmente como etiqueta del objeto de diseño. Usaremos esto más adelante cuando tratemos de guardar el estado de la aplicación. Elimina las vistas existentes del diseño para escaneos repetidos.
1 |
starLayout.setTag(numStars); |
2 |
starLayout.removeAllViews(); |
Ahora podemos simplemente hacer un bucle para agregar el número relevante de estrellas.
1 |
for(int s=0; s<numStars; s++){ |
2 |
starViews[s].setImageResource(R.drawable.star); |
3 |
starLayout.addView(starViews[s]); |
4 |
}
|
Recuerda que agregamos la imagen de la estrella a los elementos de diseño de la aplicación la última vez. Después del bloque de captura de la sección de estrellas, podemos ocuparnos del recuento de calificaciones.
1 |
try{ ratingCountText.setText(" - "+volumeObject.getString("ratingsCount")+" ratings"); } |
2 |
catch(JSONException jse){ |
3 |
ratingCountText.setText(""); |
4 |
jse.printStackTrace(); |
5 |
}
|
Ahora, verifiquemos si el libro tiene una vista previa en Google Libros. Configura el botón de vista previa para que se habilite o deshabilite en consecuencia.
1 |
try{ |
2 |
boolean isEmbeddable = Boolean.parseBoolean |
3 |
(bookObject.getJSONObject("accessInfo").getString("embeddable")); |
4 |
if(isEmbeddable) previewBtn.setEnabled(true); |
5 |
else previewBtn.setEnabled(false); |
6 |
}
|
7 |
catch(JSONException jse){ |
8 |
previewBtn.setEnabled(false); |
9 |
jse.printStackTrace(); |
10 |
}
|
Nos ocuparemos de lo que sucede al presionar el botón de vista previa más adelante. A continuación, obtén la URL de la página del libro en Google Libros y configúrala como la etiqueta del botón de enlace.
1 |
try{ |
2 |
linkBtn.setTag(volumeObject.getString("infoLink")); |
3 |
linkBtn.setVisibility(View.VISIBLE); |
4 |
}
|
5 |
catch(JSONException jse){ |
6 |
linkBtn.setVisibility(View.GONE); |
7 |
jse.printStackTrace(); |
8 |
}
|
Implementaremos clics en esto más tarde. La única información restante que aún queremos procesar es la imagen en miniatura, que es un poco más compleja. Queremos usar una clase AsyncTask adicional para recuperarla en segundo plano.
2. Recupera la imagen en miniatura
Paso 1
Después de la clase "GetBookInfo", agrega otra para recuperar la miniatura del libro.
1 |
private class GetBookThumb extends AsyncTask<String, Void, String> { |
2 |
//get thumbnail
|
3 |
}
|
Pasaremos la cadena de URL en miniatura de la otra AsyncTask cuando la recuperemos del JSON. Dentro de esta nueva clase AsyncTask, agrega el método doInBackground.
1 |
@Override |
2 |
protected String doInBackground(String... thumbURLs) { |
3 |
//attempt to download image
|
4 |
}
|
Paso 2
Dentro de doInBackground, agregue bloques prueba y captura en caso de excepciones de E/S.
1 |
try{ |
2 |
//try to download
|
3 |
}
|
4 |
catch(Exception e) { |
5 |
e.printStackTrace(); |
6 |
}
|
En el bloque de prueba, intenta establecer una conexión utilizando la URL de miniatura transmitida.
1 |
URL thumbURL = new URL(thumbURLs[0]); |
2 |
URLConnection thumbConn = thumbURL.openConnection(); |
3 |
thumbConn.connect(); |
Ahora obten flujos de entrada y almacenados en búfer.
1 |
InputStream thumbIn = thumbConn.getInputStream(); |
2 |
BufferedInputStream thumbBuff = new BufferedInputStream(thumbIn); |
Queremos traer la imagen a la aplicación como un mapa de bits, así que agrega una nueva variable de instancia en la parte superior de la declaración de la clase de actividad.
1 |
private Bitmap thumbImg; |
De vuelta en doInBackground para la clase "GetBookThumb", después de crear el flujo de entrada almacenado en búfer, lea la imagen en este mapa de bits.
1 |
thumbImg = BitmapFactory.decodeStream(thumbBuff); |
Cierra los arroyos.
1 |
thumbBuff.close(); |
2 |
thumbIn.close(); |
Después del bloque de captura, devuelve una cadena vacía para completar el método.
1 |
return ""; |
Después de doInBackground, agrega el método onPostExecute para mostrar la imagen descargada.
1 |
protected void onPostExecute(String result) { |
2 |
thumbView.setImageBitmap(thumbImg); |
3 |
}
|
3. Ejecuta las clases de recuperación y análisis
Paso 1
Ahora juntemos estos hilos. En tu método onActivityResult, después de la línea en la que creaste la cadena de consulta de búsqueda, crees y ejecutes una instancia de la clase AsyncTask para obtener los resultados de la búsqueda.
1 |
new GetBookInfo().execute(bookSearchString); |
En el método onPostExecute de la clase "GetBookInfo", después del bloque de captura para el botón de enlace, agrega una sección final para obtener la miniatura utilizando los datos JSON y la otra clase AsyncTask.
1 |
try{ |
2 |
JSONObject imageInfo = volumeObject.getJSONObject("imageLinks"); |
3 |
new GetBookThumb().execute(imageInfo.getString("smallThumbnail")); |
4 |
}
|
5 |
catch(JSONException jse){ |
6 |
thumbView.setImageBitmap(null); |
7 |
jse.printStackTrace(); |
8 |
}
|
4. Maneja los clics restantes
Paso 1
Tratemos con los botones de vista previa y enlace. Empieza con el botón de enlace. Ya hemos configurado la clase como oyente de clics para los botones, así que agrega una nueva sección a tu método onClick después del condicional para el botón de escaneo.
1 |
else if(v.getId()==R.id.link_btn){ |
2 |
//get the url tag
|
3 |
String tag = (String)v.getTag(); |
4 |
//launch the url
|
5 |
Intent webIntent = new Intent(Intent.ACTION_VIEW); |
6 |
webIntent.setData(Uri.parse(tag)); |
7 |
startActivity(webIntent); |
8 |
}
|
Este código recupera la URL de la etiqueta del botón, que configuramos cuando procesamos el JSON. Luego, el código lanza el enlace en el navegador.
Para iniciar la vista previa, necesitamos el número ISBN del libro. Vuelve al método onActivityResult por un momento. Agrega una línea antes de la línea en la que creaste la cadena de consulta de búsqueda. Configura el ISBN escaneado como una etiqueta para el botón de vista previa.
1 |
previewBtn.setTag(scanContent); |
Ahora, de nuevo en onClick, agrega un condicional para el botón de vista previa en el que recupera la etiqueta.
1 |
else if(v.getId()==R.id.preview_btn){ |
2 |
String tag = (String)v.getTag(); |
3 |
//launch preview
|
4 |
}
|
Paso 2
Vamos a crear una nueva actividad para la vista previa del libro. Agrega una nueva clase a tu proyecto en el mismo paquete que la actividad principal, nombrándola "EmbeddedBook". Dale a tu clase el siguiente esquema.
1 |
import java.io.BufferedReader; |
2 |
import java.io.IOException; |
3 |
import java.io.InputStream; |
4 |
import java.io.InputStreamReader; |
5 |
|
6 |
import android.app.Activity; |
7 |
import android.os.Bundle; |
8 |
import android.webkit.WebView; |
9 |
|
10 |
public class EmbeddedBook extends Activity { |
11 |
|
12 |
public void onCreate(Bundle savedInstanceState) { |
13 |
super.onCreate(savedInstanceState); |
14 |
}
|
15 |
}
|
Crea un nuevo archivo de diseño en la carpeta "res/layout" de tu aplicación, asígnale el nombre "embedded_book.xml". Ingresa al siguiente diseño.
1 |
<WebView xmlns:android="https://schemas.android.com/apk/res/android" |
2 |
android:id="@+id/embedded_book_view" |
3 |
android:layout_width="fill_parent" |
4 |
android:layout_height="fill_parent" /> |
Presentaremos la vista previa del libro en WebView. De vuelta en la nueva clase, establece esto como vista de contenido en onCreate después del código existente.
1 |
setContentView(R.layout.embedded_book); |
Agrega una variable de instancia en la parte superior de la clase.
1 |
private WebView embedView; |
En onCreate, obtén una referencia al WebView que incluimos en el diseño.
1 |
embedView = (WebView)findViewById(R.id.embedded_book_view); |
Configura JavaScript para que esté habilitado.
1 |
embedView.getSettings().setJavaScriptEnabled(true); |
Cuando lancemos la actividad, pasaremos el ISBN. Recupera estos datos pasados a continuación en onCreate.
1 |
Bundle extras = getIntent().getExtras(); |
Ahora comprueba si tenemos extras.
1 |
if(extras !=null) { |
2 |
//get isbn
|
3 |
}
|
Dentro del condicional, intenta recuperar el ISBN.
1 |
String isbn = extras.getString("isbn"); |
Vamos a incluir el contenido de WebView en un archivo de activos con formato HTML. Agrega bloques de prueba y captura para cargarlo.
1 |
try{ |
2 |
//load asset
|
3 |
}
|
4 |
catch(IOException ioe){ |
5 |
embedView.loadData |
6 |
("<html><head></head><body>Whoops! Something went wrong.</body></html>", |
7 |
"text/html", "utf-8"); |
8 |
ioe.printStackTrace(); |
9 |
}
|
Si el archivo no se puede cargar, mostraremos un mensaje de error. Regresaremos al bloque de prueba pronto. Primero, crea un nuevo archivo en la carpeta "activos" de tu aplicación, asígnale el nombre "embedded_book_page.html". Busca el nuevo archivo en su Explorador de paquetes, haz clic derecho y selecciona "Abrir con" y luego "Editor de texto". Ingresa el siguiente código.
1 |
<html xmlns="http://www.w3.org/1999/xhtml"> |
2 |
<head>
|
3 |
<meta http-equiv="content-type" content="text/html; charset=utf-8"/> |
4 |
<title>Google Books Embedded Viewer API Example</title> |
5 |
<script type="text/javascript" src="https://www.google.com/jsapi"></script> |
6 |
<script type="text/javascript"> |
7 |
google.load("books", "0"); |
8 |
|
9 |
function initialize() { |
10 |
var viewer = new google.books.DefaultViewer(document.getElementById('viewerCanvas')); |
11 |
viewer.load('ISBN:$ISBN$'); |
12 |
}
|
13 |
|
14 |
google.setOnLoadCallback(initialize); |
15 |
</script>
|
16 |
</head>
|
17 |
<body>
|
18 |
<div id="viewerCanvas" style="width: 600px; height: 500px"></div> |
19 |
</body>
|
20 |
</html>
|
Este es el código de marcado estándar que se utiliza para mostrar una vista previa de Google Libros con la API de visor integrado. Ten en cuenta que la página incluye "$ISBN$". Usaremos esto para pasar el número ISBN de la actividad.
Regresa al bloque prueba en tu clase "EmbeddedBook". Intenta cargar la página utilizando el siguiente algoritmo.
1 |
InputStream pageIn = getAssets().open("embedded_book_page.html"); |
2 |
BufferedReader htmlIn = new BufferedReader(new InputStreamReader(pageIn)); |
3 |
StringBuilder htmlBuild = new StringBuilder(""); |
4 |
String lineIn; |
5 |
while ((lineIn = htmlIn.readLine()) != null) { |
6 |
htmlBuild.append(lineIn); |
7 |
}
|
8 |
htmlIn.close(); |
Luego, pasa el ISBN, reemplazando la sección que incluimos en el HTML.
1 |
String embeddedPage = htmlBuild.toString().replace("$ISBN$", isbn); |
Completa la nueva actividad cargando la página.
1 |
embedView.loadData(embeddedPage, "text/html", "utf-8"); |
Paso 3
De vuelta en el método onClick de la clase de actividad principal, en el bloque condicional que agregaste para el botón de vista previa, después de la línea en la que recuperaste la etiqueta, inicia la nueva actividad, pasando el ISBN.
1 |
Intent intent = new Intent(this, EmbeddedBook.class); |
2 |
intent.putExtra("isbn", tag); |
3 |
startActivity(intent); |
Agrega la nueva clase a tu archivo de manifiesto de aplicación, después del elemento de actividad principal.
1 |
<activity android:name="com.example.barcodescanningapp.EmbeddedBook" /> |
Modifica el nombre del paquete para adaptarlo al tuyo si es necesario.
5. Conserva el estado de la aplicación
Paso 1
Puedes ejecutar la aplicación en esta etapa y debería funcionar. Sin embargo, primero ocupémonos del cambio de estado de la aplicación. En tu clase de actividad principal, agrega el siguiente método.
1 |
protected void onSaveInstanceState(Bundle savedBundle) { |
2 |
savedBundle.putString("title", ""+titleText.getText()); |
3 |
savedBundle.putString("author", ""+authorText.getText()); |
4 |
savedBundle.putString("description", ""+descriptionText.getText()); |
5 |
savedBundle.putString("date", ""+dateText.getText()); |
6 |
savedBundle.putString("ratings", ""+ratingCountText.getText()); |
7 |
savedBundle.putParcelable("thumbPic", thumbImg); |
8 |
if(starLayout.getTag()!=null) |
9 |
savedBundle.putInt("stars", Integer.parseInt(starLayout.getTag().toString())); |
10 |
savedBundle.putBoolean("isEmbed", previewBtn.isEnabled()); |
11 |
savedBundle.putInt("isLink", linkBtn.getVisibility()); |
12 |
if(previewBtn.getTag()!=null) |
13 |
savedBundle.putString("isbn", previewBtn.getTag().toString()); |
14 |
}
|
No entraremos en detalles sobre cómo salvar el estado porque no es el punto focal de este tutorial. Revisa el código para asegurarte de que lo comprendes. Simplemente estamos guardando los datos mostrados para que podamos conservarlos cuando cambies el estado de la aplicación. En onCreate después del código existente, recupera esta información.
1 |
if (savedInstanceState != null){ |
2 |
authorText.setText(savedInstanceState.getString("author")); |
3 |
titleText.setText(savedInstanceState.getString("title")); |
4 |
descriptionText.setText(savedInstanceState.getString("description")); |
5 |
dateText.setText(savedInstanceState.getString("date")); |
6 |
ratingCountText.setText(savedInstanceState.getString("ratings")); |
7 |
int numStars = savedInstanceState.getInt("stars");//zero if null |
8 |
for(int s=0; s<numStars; s++){ |
9 |
starViews[s].setImageResource(R.drawable.star); |
10 |
starLayout.addView(starViews[s]); |
11 |
}
|
12 |
starLayout.setTag(numStars); |
13 |
thumbImg = (Bitmap)savedInstanceState.getParcelable("thumbPic"); |
14 |
thumbView.setImageBitmap(thumbImg); |
15 |
previewBtn.setTag(savedInstanceState.getString("isbn")); |
16 |
|
17 |
if(savedInstanceState.getBoolean("isEmbed")) previewBtn.setEnabled(true); |
18 |
else previewBtn.setEnabled(false); |
19 |
if(savedInstanceState.getInt("isLink")==View.VISIBLE) linkBtn.setVisibility(View.VISIBLE); |
20 |
else linkBtn.setVisibility(View.GONE); |
21 |
previewBtn.setVisibility(View.VISIBLE); |
22 |
}
|
Nuevamente, no entraremos en detalles sobre esto. El código simplemente lleva a cabo lo que el código ya hace inmediatamente después de escanear. En este caso, sigue un cambio de estado. Extiende la etiqueta de apertura de tu elemento de actividad principal en el manifiesto del proyecto para manejar los cambios de configuración.
1 |
android:configChanges="orientation|keyboardHidden|screenSize" |
¡Eso completa la aplicación! Deberías poder ejecutarlo en este punto. Cuando se inicie, presiona el botón y escanea un libro. Si el libro aparece en Google Libros, la información relevante debería aparecer dentro de la interfaz. Si faltan ciertos elementos de datos, la aplicación aún debería mostrar los demás. Si hay una vista previa disponible, el botón de vista previa debe estar habilitado y debe iniciar la actividad del libro incrustado.



Conclusión
En esta serie, nos basamos en la base del SDK de Android: Crea un escáner de código de barras para crear una aplicación de escáner de libros junto con la biblioteca de escaneo ZXing y la API de Google Libros. Experimenta con la aplicación tratando de recuperar y mostrar diferentes elementos de datos del feed JSON devuelto o respondiendo a escaneos de diferentes tipos de códigos de barras, en lugar de solo EAN. El uso de recursos externos a través de API es una habilidad clave en cualquier disciplina de desarrollo, ya que te permite hacer el mejor uso de los datos y la funcionalidad existentes para concentrarse en los detalles únicos de tus propias aplicaciones.



