Tutorial: Crear una aplicación de una sola página con Bing Image Search APITutorial: Create a single-page app using the Bing Image Search API

Bing Image Search API permite buscar en la Web imágenes relevantes de alta calidad.The Bing Image Search API enables you to search the web for high-quality, relevant images. Use este tutorial para crear una aplicación web de una sola página que pueda enviar consultas a la API y mostrar los resultados en la página web.Use this tutorial to build a single-page web application that can send search queries to the API, and display the results within the webpage. Este tutorial es similar al tutorial correspondiente para Bing Web Search.This tutorial is similar to the corresponding tutorial for Bing Web Search.

En la aplicación del tutorial se muestra cómo:The tutorial app illustrates how to:

  • Realizar una llamada a Bing Image Search API en JavaScriptPerform a Bing Image Search API call in JavaScript
  • Mejorar los resultados de la búsqueda mediante las opciones de búsquedaImprove search results using search options
  • Mostrar y paginar los resultados de la búsquedaDisplay and page through search results
  • Solicite y controle una clave de suscripción de API y un identificador de cliente de Bing.Request and handle an API subscription key, and Bing client ID.

El código fuente completo de este tutorial está disponible en GitHub.The full source code for this tutorial is available on GitHub.

Requisitos previosPrerequisites

  • La última versión de Node.js.The latest version of Node.js.
  • El marco de Express.js para Node.js.The Express.js framework for Node.js. Las instrucciones de instalación del código fuente están disponibles en el archivo Léame del ejemplo de GitHub.Installation instructions for the source code are available in the GitHub sample readme file.

Debe tener una cuenta de Cognitive Services API con acceso a Bing Search APIs.You must have a Cognitive Services API account with access to the Bing Search APIs. Si no tiene una suscripción de Azure, puede crear una cuenta gratuita.If you don't have an Azure subscription, you can create an account for free. Antes de continuar, necesitará la clave de acceso que se le proporcionó al activar la versión de evaluación gratuita o puede usar una clave de suscripción de pago desde el panel de Azure.Before continuing, You will need the access key provided after activating your free trial, or a paid subscription key from your Azure dashboard.

Administración y almacenamiento de las claves de suscripción de usuariosManage and store user subscription keys

Esta aplicación utiliza el almacenamiento persistente de los exploradores web para almacenar las claves de suscripción de API.This application uses web browsers' persistent storage to store API subscription keys. Si no se almacena ninguna clave, la página web solicitará al usuario la clave y la almacenará para futuros usos.If no key is stored, the webpage will prompt the user for their key and store it for later use. Si la API rechaza la clave más adelante, la aplicación la eliminará del almacenamiento.If the key is later rejected by the API, The app will remove it from storage.

Defina funciones storeValue y retrieveValue que usan el objeto localStorage (si el explorador lo admite) o una cookie.Define storeValue and retrieveValue functions to use either the localStorage object (if the browser supports it) or a cookie.

// Cookie names for data being stored
API_KEY_COOKIE   = "bing-search-api-key";
CLIENT_ID_COOKIE = "bing-search-client-id";
// The Bing Image Search API endpoint
BING_ENDPOINT = "https://api.cognitive.microsoft.com/bing/v7.0/images/search";

try { //Try to use localStorage first
    localStorage.getItem;   

    window.retrieveValue = function (name) {
        return localStorage.getItem(name) || "";
    }
    window.storeValue = function(name, value) {
        localStorage.setItem(name, value);
    }
} catch (e) {
    //If the browser doesn't support localStorage, try a cookie
    window.retrieveValue = function (name) {
        var cookies = document.cookie.split(";");
        for (var i = 0; i < cookies.length; i++) {
            var keyvalue = cookies[i].split("=");
            if (keyvalue[0].trim() === name) return keyvalue[1];
        }
        return "";
    }
    window.storeValue = function (name, value) {
        var expiry = new Date();
        expiry.setFullYear(expiry.getFullYear() + 1);
        document.cookie = name + "=" + value.trim() + "; expires=" + expiry.toUTCString();
    }
}

La función getSubscriptionKey() intenta recuperar una clave almacenada previamente con el uso de retrieveValue. Si no se encuentra ninguna clave, pedirá al usuario que la proporcione y la almacenará con storeValue.If one isn't found, it will prompt the user for their key, and store it using storeValue.


// Get the stored API subscription key, or prompt if it's not found
function getSubscriptionKey() {
    var key = retrieveValue(API_KEY_COOKIE);
    while (key.length !== 32) {
        key = prompt("Enter Bing Search API subscription key:", "").trim();
    }
    // always set the cookie in order to update the expiration date
    storeValue(API_KEY_COOKIE, key);
    return key;
}

La etiqueta <form> HTML onsubmit llama a la función bingWebSearch para devolver los resultados de la búsqueda.The HTML <form> tag onsubmit calls the bingWebSearch function to return search results. bingWebSearch utiliza getSubscriptionKey para autenticar cada consulta.bingWebSearch uses getSubscriptionKey to authenticate each query. Tal como se muestra en la definición anterior, si no se ha especificado la clave getSubscriptionKey se la pide al usuario.As shown in the previous definition, getSubscriptionKey prompts the user for the key if the key hasn't been entered. A continuación, la clave se almacena para que la siga usando la aplicación.The key is then stored for continuing use by the application.

<form name="bing" onsubmit="this.offset.value = 0; return bingWebSearch(this.query.value,
bingSearchOptions(this), getSubscriptionKey())">

Envío de solicitudes de búsquedaSend search requests

Esta aplicación usa un valor HTML <form> para enviar solicitudes de búsqueda de usuario, mediante el atributo onsubmit para llamar a newBingImageSearch().This application uses an HTML <form> to initially send user search requests, using the onsubmit attribute to call newBingImageSearch().

<form name="bing" onsubmit="return newBingImageSearch(this)">

De forma predeterminada, el controlador onsubmit devuelve false, lo que impide que el formulario se envíe.By default, the onsubmit handler returns false, keeping the form from being submitted.

Selección de opciones de búsquedaSelect search options

[Formulario de Bing Image Search]

Bing Image Search API ofrece varios parámetros de consulta de filtro para limitar y filtrar los resultados de la búsqueda.The Bing Image Search API offers several filter query parameters to narrow and filter search results. El formulario HTML de esta aplicación usa y muestra las siguientes opciones de parámetro:The HTML form in this application uses and displays the following parameter options:

where Un menú desplegable para seleccionar el mercado (ubicación e idioma) que se usa en la búsqueda.A drop-down menu for selecting the market (location and language) used for the search.
query El campo de texto en que se especificarán los términos de búsqueda.The text field in which to enter the search terms.
aspect Botones de radio para elegir las proporciones de las imágenes que se encuentren: cuadradas, horizontales o verticales.Radio buttons for choosing the proportions of the found images: roughly square, wide, or tall.
color
when Menú desplegable para, opcionalmente, limitar la búsqueda al día, la semana o el mes más reciente.Drop-down menu for optionally limiting the search to the most recent day, week, or month.
safe Una casilla que indica si se usa la característica Búsqueda segura de Bing para filtrar los resultados "para adultos".A checkbox indicating whether to use Bing's SafeSearch feature to filter out "adult" results.
count Campo oculto.Hidden field. Número de resultados de búsqueda que se devolverán en cada solicitud.The number of search results to return on each request. Cambie este valor para mostrar más o menos resultados por página.Change to display fewer or more results per page.
offset Campo oculto.Hidden field. El desplazamiento del primer resultado de búsqueda de la solicitud; se usa para la paginación.The offset of the first search result in the request; used for paging. Se restablece a 0 para cada nueva solicitud.It's reset to 0 on a new request.
nextoffset Campo oculto.Hidden field. Al recibir un resultado de búsqueda, este campo se define con el valor de nextOffset en la respuesta.Upon receiving a search result, this field is set to the value of the nextOffset in the response. El uso de este campo evita resultados superpuestos en páginas sucesivas.Using this field avoids overlapping results on successive pages.
stack Campo oculto.Hidden field. Una lista codificada en JSON de los desplazamientos de las páginas anteriores de los resultados de búsqueda para volver a páginas anteriores.A JSON-encoded list of the offsets of preceding pages of search results, for navigating back to previous pages.

La función bingSearchOptions() da formato a estas opciones en una cadena de consulta parcial, que se puede usar en las solicitudes de API de la aplicación.The bingSearchOptions() function formats these options into a partial query string, which can be used in the app's API requests.

// Build query options from the HTML form
function bingSearchOptions(form) {

    var options = [];
    options.push("mkt=" + form.where.value);
    options.push("SafeSearch=" + (form.safe.checked ? "strict" : "off"));
    if (form.when.value.length) options.push("freshness=" + form.when.value);
    var aspect = "all";
    for (var i = 0; i < form.aspect.length; i++) {
        if (form.aspect[i].checked) {
            aspect = form.aspect[i].value;
            break;
        }
    }
    options.push("aspect=" + aspect);
    if (form.color.value) options.push("color=" + form.color.value);
    options.push("count=" + form.count.value);
    options.push("offset=" + form.offset.value);
    return options.join("&");
}

Realizar la solicitudPerforming the request

Con el uso de la consulta de búsqueda, la cadena de opciones y la clave de API, la función BingImageSearch() utiliza un objeto XMLHttpRequest para enviar la solicitud al punto de conexión de Bing Image Search.Using the search query, options string, and API key, the BingImageSearch() function uses an XMLHttpRequest object to make the request to the Bing Image Search endpoint.

// perform a search given query, options string, and API key
function bingImageSearch(query, options, key) {

    // scroll to top of window
    window.scrollTo(0, 0);
    if (!query.trim().length) return false;     // empty query, do nothing

    showDiv("noresults", "Working. Please wait.");
    hideDivs("results", "related", "_json", "_http", "paging1", "paging2", "error");

    var request = new XMLHttpRequest();
    var queryurl = BING_ENDPOINT + "?q=" + encodeURIComponent(query) + "&" + options;

    // open the request
    try {
        request.open("GET", queryurl);
    }
    catch (e) {
        renderErrorMessage("Bad request (invalid URL)\n" + queryurl);
        return false;
    }

    // add request headers
    request.setRequestHeader("Ocp-Apim-Subscription-Key", key);
    request.setRequestHeader("Accept", "application/json");
    var clientid = retrieveValue(CLIENT_ID_COOKIE);
    if (clientid) request.setRequestHeader("X-MSEdge-ClientID", clientid);

    // event handler for successful response
    request.addEventListener("load", handleBingResponse);

    // event handler for erorrs
    request.addEventListener("error", function() {
        renderErrorMessage("Error completing request");
    });

    // event handler for aborted request
    request.addEventListener("abort", function() {
        renderErrorMessage("Request aborted");
    });

    // send the request
    request.send();
    return false;
}

Tras la correcta finalización de la solicitud HTTP, JavaScript llama a controlador de eventos handleBingResponse() de "carga" para controlar una solicitud HTTP GET correcta.Upon successful completion of the HTTP request, JavaScript calls the "load" event handler handleBingResponse() to handle a successful HTTP GET request.

// handle Bing search request results
function handleBingResponse() {
    hideDivs("noresults");

    var json = this.responseText.trim();
    var jsobj = {};

    // try to parse JSON results
    try {
        if (json.length) jsobj = JSON.parse(json);
    } catch(e) {
        renderErrorMessage("Invalid JSON response");
    }

    // show raw JSON and HTTP request
    showDiv("json", preFormat(JSON.stringify(jsobj, null, 2)));
    showDiv("http", preFormat("GET " + this.responseURL + "\n\nStatus: " + this.status + " " +
        this.statusText + "\n" + this.getAllResponseHeaders()));

    // if HTTP response is 200 OK, try to render search results
    if (this.status === 200) {
        var clientid = this.getResponseHeader("X-MSEdge-ClientID");
        if (clientid) retrieveValue(CLIENT_ID_COOKIE, clientid);
        if (json.length) {
            if (jsobj._type === "Images") {
                if (jsobj.nextOffset) document.forms.bing.nextoffset.value = jsobj.nextOffset;
                renderSearchResults(jsobj);
            } else {
                renderErrorMessage("No search results in JSON response");
            }
        } else {
            renderErrorMessage("Empty response (are you sending too many requests too quickly?)");
        }
    }

    // Any other HTTP response is an error
    else {
        // 401 is unauthorized; force re-prompt for API key for next request
        if (this.status === 401) invalidateSubscriptionKey();

        // some error responses don't have a top-level errors object, so gin one up
        var errors = jsobj.errors || [jsobj];
        var errmsg = [];

        // display HTTP status code
        errmsg.push("HTTP Status " + this.status + " " + this.statusText + "\n");

        // add all fields from all error responses
        for (var i = 0; i < errors.length; i++) {
            if (i) errmsg.push("\n");
            for (var k in errors[i]) errmsg.push(k + ": " + errors[i][k]);
        }

        // also display Bing Trace ID if it isn't blocked by CORS
        var traceid = this.getResponseHeader("BingAPIs-TraceId");
        if (traceid) errmsg.push("\nTrace ID " + traceid);

        // and display the error message
        renderErrorMessage(errmsg.join("\n"));
    }
}

Importante

Las solicitudes HTTP correctas pueden contener información de búsqueda errónea.Successful HTTP requests may contain failed search information. Si se produce un error durante la operación de búsqueda, Bing Image Search API devuelve un código de estado HTTP distinto de 200 e información de error en la respuesta JSON.If an error occurs during the search operation, the Bing Image Search API will return a non-200 HTTP status code and error information in the JSON response. Además, si la solicitud tenía limitación de frecuencia, la API devuelve una respuesta vacía.Additionally, if the request was rate-limited, the API will return an empty response.

Visualización de los resultados de la búsquedaDisplay the search results

Los resultados de la búsqueda se muestran mediante la función renderSearchResults(), que toma el valor JSON devuelto por el servicio Bing Image Search y llama a una función de representador apropiada en cualquier imagen devuelta y en las búsquedas relacionadas.Search results are displayed by the renderSearchResults() function, which takes the JSON returned by the Bing Image Search service and calls an appropriate renderer function on any returned images and related searches.

function renderSearchResults(results) {

    // add Prev / Next links with result count
    var pagingLinks = renderPagingLinks(results);
    showDiv("paging1", pagingLinks);
    showDiv("paging2", pagingLinks);

    showDiv("results", renderImageResults(results.value));
    if (results.relatedSearches)
        showDiv("sidebar", renderRelatedItems(results.relatedSearches));
}

Los resultados de la búsqueda de imágenes están incluidos en el objeto value de alto nivel dentro de la respuesta JSON.Image search results are contained in the top-level value object within the JSON response. Se pasan a renderImageResults(), que realizan la iteración por los resultados y convierte cada elemento a HTML.These are passed to renderImageResults(), which iterates through the results and converts each item into HTML.

function renderImageResults(items) {
    var len = items.length;
    var html = [];
    if (!len) {
        showDiv("noresults", "No results.");
        hideDivs("paging1", "paging2");
        return "";
    }
    for (var i = 0; i < len; i++) {
        html.push(searchItemRenderers.images(items[i], i, len));
    }
    return html.join("\n\n");
}

Bing Image Search API puede devolver los cuatro tipos de sugerencias de búsqueda para ayudar a guiar las experiencias de búsqueda de los usuarios, cada una en su propio objeto de nivel superior:The Bing Image Search API can return four types of search suggestions to help guide users' search experiences, each in its own top-level object:

SugerenciaSuggestion DESCRIPCIÓNDescription
pivotSuggestions Consultas que reemplazan una palabra dinámica de la búsqueda original por otra.Queries that replace a pivot word in original search with a different one. Por ejemplo, si busca "flores rojas", una palabra dinámica podría ser "rojas", y una sugerencia dinámica podría ser "flores amarillas".For example, if you search for "red flowers," a pivot word might be "red," and a pivot suggestion might be "yellow flowers."
queryExpansions Consultas que limitan la búsqueda original al agregar más términos.Queries that narrow the original search by adding more terms. Por ejemplo, si busca "Microsoft Surface", una expansión de consulta podría ser "Microsoft Surface Pro".For example, if you search for "Microsoft Surface," a query expansion might be "Microsoft Surface Pro."
relatedSearches Consultas que también han realizado otros usuarios que escribieron la búsqueda original.Queries that have also been entered by other users who entered the original search. Por ejemplo, su buscó "Monte Rainier", una búsqueda relacionada podría ser "MonteFor example, if you search for "Mount Rainier," a related search might be "Mt. Santa Elena".Saint Helens."
similarTerms Consultas que son similares en significado a la búsqueda original.Queries that are similar in meaning to the original search. Por ejemplo, si busca "gatos", un término similar podría ser "monos".For example, if you search for "kittens," a similar term might be "cute."

Esta aplicación solo representa las sugerencias de relatedItems y coloca los vínculos resultantes en la barra lateral de la página.This application only renders the relatedItems suggestions, and places the resulting links in the page's sidebar.

Representación de los resultados de la búsquedaRendering search results

En esta aplicación, el objeto searchItemRenderers contiene funciones de representador que generan HTML para cada tipo de resultado de la búsqueda.In this application, the searchItemRenderers object contains renderer functions that generate HTML for each kind of search result.

searchItemRenderers = {
    images: function(item, index, count) { ... },
    relatedSearches: function(item) { ... }
}

Estas funciones de representador aceptan los parámetros siguientes:These renderer functions accept the following parameters:

ParámetroParameter DESCRIPCIÓNDescription
item El objeto de JavaScript que contiene las propiedades del elemento, como su dirección URL y la descripción.The JavaScript object containing the item's properties, such as its URL and its description.
index El índice del elemento de resultado dentro de su colección.The index of the result item within its collection.
count El número de elementos de la colección del elemento de resultado de la búsqueda.The number of items in the search result item's collection.

Los parámetros index y count se usan para enumerar los resultados, generar HTML para las colecciones y organizar el contenido.The index and count parameters are used to number results, generate HTML for collections, and organize the content. Concretamente:Specifically, it:

  • Calcula el tamaño de miniatura de imagen (el ancho varía, con un mínimo de 120 píxeles, mientras que la altura se fija en 90 píxeles).Calculates the image thumbnail size (width varies, with a minimum of 120 pixels, while height is fixed at 90 pixels).
  • Crea la etiqueta HTML <img> para mostrar la miniatura de imagen.Builds the HTML <img> tag to display the image thumbnail.
  • Compila etiquetas <a> HTML vinculadas con la imagen y la página que la contiene.Builds the HTML <a> tags that link to the image and the page that contains it.
  • Compila la descripción que muestra información sobre la imagen y el sitio en que se encuentra.Builds the description that displays information about the image and the site it's on.
    images: function (item, index, count) {
        var height = 120;
        var width = Math.max(Math.round(height * item.thumbnail.width / item.thumbnail.height), 120);
        var html = [];
        if (index === 0) html.push("<p class='images'>");
        var title = escape(item.name) + "\n" + getHost(item.hostPageDisplayUrl);
        html.push("<p class='images' style='max-width: " + width + "px'>");
        html.push("<img src='"+ item.thumbnailUrl + "&h=" + height + "&w=" + width +
            "' height=" + height + " width=" + width + "'>");
        html.push("<br>");
        html.push("<nobr><a href='" + item.contentUrl + "'>Image</a> - ");
        html.push("<a href='" + item.hostPageUrl + "'>Page</a></nobr><br>");
        html.push(title.replace("\n", " (").replace(/([a-z0-9])\.([a-z0-9])/g, "$1.<wbr>$2") + ")</p>");
        return html.join("");
    }, // relatedSearches renderer omitted

height y width de la imagen en miniatura se usan tanto en la etiqueta <img> como en los campos h y w de la dirección URL de la miniatura.The thumbnail image's height and width are used in both the <img> tag and the h and w fields in the thumbnail's URL. Esto permite que Bing devuelva una miniatura de ese tamaño exactamente.This enables Bing to return a thumbnail of exactly that size.

Identificador de cliente persistentePersisting client ID

Las respuestas de Bing Search API pueden incluir un encabezado X-MSEdge-ClientID que debe devolverse a la API con las solicitudes sucesivas.Responses from the Bing search APIs may include a X-MSEdge-ClientID header that should be sent back to the API with successive requests. Si se usan varias instancias de Bing Search API, se debe usar el mismo identificador de cliente con todas ellas, si es posible.If multiple Bing Search APIs are being used, the same client ID should be used with all of them, if possible.

Especificar el encabezado X-MSEdge-ClientID permite a las API de Bing asociar todas las búsquedas de un usuario, algo que resulta útil.Providing the X-MSEdge-ClientID header allows the Bing APIs to associate all of a user's searches, which is useful in

En primer lugar, permite al motor de búsqueda de Bing aplicar un contexto pasado a las búsquedas, con el fin de encontrar los resultados que más satisfagan al usuario.First, it allows the Bing search engine to apply past context to searches to find results that better satisfy the user. Si un usuario ha buscado previamente términos relacionados con la navegación, por ejemplo, una búsqueda posterior de "nudos" podría devolver información acerca de los nudos que se usan en la navegación de forma preferente.If a user has previously searched for terms related to sailing, for example, a later search for "knots" might preferentially return information about knots used in sailing.

En segundo lugar, Bing puede seleccionar aleatoriamente usuarios para disfrutar de nuevas características antes de que estén disponibles públicamente.Second, Bing may randomly select users to experience new features before they are made widely available. Proporcionar el mismo identificador de cliente con cada solicitud garantiza que los usuarios elegidos para ver una característica la vean siempre.Providing the same client ID with each request ensures that users that have been chosen to see a feature always see it. Sin el identificador de cliente, el usuario puede ver una característica aparecer y desaparecer, de forma aparentemente aleatoria, en los resultados de búsqueda.Without the client ID, the user might see a feature appear and disappear, seemingly at random, in their search results.

Las directivas de seguridad del explorador (CORS) pueden impedir que el encabezado X-MSEdge-ClientID esté disponible para JavaScript.Browser security policies (CORS) may prevent the X-MSEdge-ClientID header from being available to JavaScript. Esta limitación tiene lugar cuando la respuesta a la búsqueda tiene un origen distinto al de la página que la solicitó.This limitation occurs when the search response has a different origin from the page that requested it. En un entorno de producción, debería abordar esta directiva mediante el hospedaje de un script de lado servidor que realice la llamada API en el mismo dominio que la página web.In a production environment, you should address this policy by hosting a server-side script that does the API call on the same domain as the Web page. Como el script tiene el mismo origen que la página web, el encabezado X-MSEdge-ClientID está disponible para JavaScript.Since the script has the same origin as the Web page, the X-MSEdge-ClientID header is then available to JavaScript.

Nota

En una aplicación web de producción, debe realizar la solicitud del lado servidor de todos modos.In a production Web application, you should perform the request server-side anyway. En caso contrario, es preciso incluir la clave de Bing Search API en la página web, donde está disponible para cualquiera que vea el origen.Otherwise, your Bing Search API key must be included in the Web page, where it is available to anyone who views source. Se le facturará todo el uso bajo su clave de suscripción a API, incluso las solicitudes que realicen partes no autorizadas, por lo que es importante no exponer su clave.You are billed for all usage under your API subscription key, even requests made by unauthorized parties, so it is important not to expose your key.

Para fines de desarrollo, puede realizar la solicitud de Bing Web Search API a través de un proxy CORS.For development purposes, you can make the Bing Web Search API request through a CORS proxy. La respuesta de un proxy de este tipo tiene un encabezado Access-Control-Expose-Headers que permite los encabezados de respuesta y hace que estén disponibles para JavaScript.The response from such a proxy has an Access-Control-Expose-Headers header that allows response headers and makes them available to JavaScript.

Es fácil instalar un proxy CORS para permitir que nuestra aplicación de tutorial acceda al encabezado de identificador de cliente.It's easy to install a CORS proxy to allow our tutorial app to access the client ID header. En primer lugar, si aún no lo tiene, instale Node.js.First, if you don't already have it, install Node.js. Escriba el comando siguiente en una ventana de comandos:Then issue the following command in a command window:

npm install -g cors-proxy-server

A continuación, cambie el punto de conexión de Bing Web Search del archivo HTML a:Next, change the Bing Web Search endpoint in the HTML file to:

http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Por último, inicie el proxy CORS con el siguiente comando:Finally, start the CORS proxy with the following command:

cors-proxy-server

Deje abierta la ventana de comandos mientras usa la aplicación del tutorial, ya que si la cierra, se detendrá el proxy.Leave the command window open while you use the tutorial app; closing the window stops the proxy. En la sección de encabezados HTTP expandibles situada bajo los resultados de la búsqueda, puede ver el encabezado X-MSEdge-ClientID (entre otras cosas) y comprobar que es el mismo en todas las solicitudes.In the expandable HTTP Headers section below the search results, you can now see the X-MSEdge-ClientID header (among others) and verify that it is the same for each request.

Pasos siguientesNext steps

Otras referenciasSee also