portada gu铆a async JS

Async JS: Gu铆a completa sobre c贸digo as铆ncrono | Parte #2

脷ltima actualizaci贸n:

Retomamos esta serie sobre JavaScript as铆ncrono. Si acabas de aterrizar aqu铆, te recomiendo que veas la parte #1 antes, ya que voy a utilizar todos los acr贸nimos all铆 definidos. 馃槉

C贸mo usar las callback functions 鈥媎entro de una HTTP Request鈥嬧嬧嬧嬧

Lo habitual cuando hacemos una request es darle forma de funci贸n y guardarla en una variable para as铆 poder llamarla cuando la necesitemos. Adem谩s, eso la hace re-utilizable, lo que es una buena pr谩ctica. Como estamos trabajando con JSONPlaceholder para obtener una lista de to do's (tareas)鈥嬧, vamos a darle el nombre a la variable de getToDos.

const getToDos = () => {
    const request = new XMLHttpRequest();

    request.addEventListener('readystatechange', () => {
        // console.log(request);
        // console.log(request.readyState);
        if (request.readyState === 4 && request.status === 200) {
            console.log(request, request.responseText);
        } else if (request.readyState === 4) {
            console.log('ooops...we could not fetch the data');
        }
    });

    request.open('GET', 'https://jsonplaceholder.typicode.com/todos');
    request.send();
};

Ahora, para hacer nuestra request, solo tendr铆amos que invocar la funci贸n:鈥嬧鈥嬧鈥嬧

getToDos();

y recibir铆amos la response igual que antes.

Por ahora todo lo que estamos haciendo es imprimir siempre el responseText, porque as铆 lo hemos especificado:

console.log(request, request.responseText);

Ser铆a m谩s 煤til si, en lugar de tener ese console.log, le pas谩ramos una callback function, cuyo comportamiento podr铆amos gestionar independientemente y as铆 hacer nuestra request m谩s flexible. Entonces, cada vez que llam谩semos a getToDos(), podr铆amos especificar una callback function distinta, adaptada a nuestras necesidades. 隆Vamos a ello! 馃懇鈥嶐煉

1. Especificamos una callback function como primer argumento del getToDos().鈥嬧

鈥嬧嬧鈥嬧鈥嬧鈥嬧2. Como le estamos pasando un par谩metro al getToDos, eso significa que tenemos que avisarle de que va a recibir un par谩metro. Lo llamamos callbackFunc, por ejemplo.

3. Dentro de nuestra request, en lugar de escribir un console.log, le pasamos nuestra callbackFunc()Vamos a probarlo haciendo un console.log dentro de la callback function de getToDos().

const getToDos = (callbackFunc) => {
    const request = new XMLHttpRequest();

    request.addEventListener('readystatechange', () => {
        // console.log(request);
        // console.log(request.readyState);
        if (request.readyState === 4 && request.status === 200) {
            // console.log(request, request.responseText);
            callbackFunc();
        } else if (request.readyState === 4) {
            // console.log('ooops...we could not fetch the data');
            callbackFunc();
        }
    });

    request.open('GET', 'https://jsonplaceholder.typicode.com/todos');
    request.send();
};

getToDos(() => {
    console.log('callback function fired!');
});

馃 Pero si ahora cometemos alg煤n tipo de error en el API endpoint, la callback function se seguir铆a disparando, porque evidentemente le estamos pasando la misma callback a ambos casos del if / else statement. Lo suyo ser铆a pasarle la informaci贸n de la response en el bloque if  y cualquier error que ocurra en el bloque else:

    request.addEventListener('readystatechange', () => {
        if (request.readyState === 4 && request.status === 200) {
            callbackFunc(// pass the data);
        } else if (request.readyState === 4) {
            callbackFunc(// pass the error);
        }
    });

Para hacer esa verificaci贸n de cu谩ndo estamos ante una obtenci贸n de datos correcta y cu谩ndo ante un error, configuramos nuestra callback function en el getToDos().

4. La callback function va a aceptar dos par谩metros: err data. Es una convenci贸n que primero le pasemos el error y segundo la informaci贸n. 馃

getToDos((err, data) => {
    console.log('callback function fired!');
});

5. Personalizamos cada callbackFunc. El primer if statement contempla el caso de que todo haya salido bien. Por tanto, el par谩metro err ser铆a undefinedEl segundo par谩metro (data), ser铆a el responseText. 

En el block del else statement, la callbackFunc 鈥媡ambi茅n espera un error como primer par谩metro, y este es el bloque que gestiona si algo ha salido mal, as铆 que le pasamos un mensaje tipo "algo sali贸 mal, no se ha podido obtener los datos". Como segundo par谩metro le pasamos undefined, ya que en este bloque no se ha podido recibir el responseText. 

        request.addEventListener('readystatechange', () => {
            if (request.readyState === 4 && request.status === 200) {
                callbackFunc(undefined, request.responseText);
            } else if (request.readyState === 4) {
                callbackFunc('no se han podido obtener los datos', undefined);
            }
        });

6. Hacemos un console.log del err y de la data para ver si vamos por el buen camino.

getToDos((err, data) => {
    console.log('callback function fired!');
    console.log(err, data);
});

Prueba a hacer la request con un API endpoint correcto y con uno incorrecto, y ver谩s la diferencia. 馃槒

7. Sabiendo esto, podemos comprobar si tenemos o no un error, y hacer cosas distintas seg煤n el caso. Vamos simplemente a imprimir por consola el error y los datos.

getToDos((err, data) => {
    console.log('callback function fired!');
    // console.log(err, data);
    if (err) {
        console.log(err);
    } else {
        console.log(data);
    }
});

Y con esta comprobaci贸n, ahora s贸lo se nos imprimir谩 el mensaje de error en caso de que el API endpoint sea incorrecto, o recibiremos los datos en caso contrario. 馃憣

8. Si ahora hacemos una prueba para ver si verdaderamente este c贸digo as铆ncrono no bloquea nuestra secuencia de ejecuci贸n, tendr铆amos un resultado satisfactorio.

console.log(1);
console.log(2);

getToDos((err, data) => {
    console.log('callback function fired!');
    // console.log(err, data);
    if (err) {
        console.log(err);
    } else {
        console.log(data);
    }
});

console.log(3);
console.log(4);

En tu consola ver谩s que los console.log se han impreso primero, ya que la request se ha ejecutado en paralelo y finalmente resuelto, siendo as铆 la 煤ltima parte en imprimirse en la consola. 馃憤

C贸mo trabajar con JSON data

Sabemos que el formato JSON es el formato en el que la mayor铆a de APIs nos devuelven informaci贸n cuando se la solicitamos por medio de una HTTP request. Este formato tiene pinta de una JS array lleno de JS objects, pero si nos fijamos, es en realidad un string gigante. 

JSON = JavaScript Object Notation

Es un string porque esta es la 煤nica manera de transferir datos entre un servidor y un navegador. 

馃懇鈥嶐煆 Vamos a aprender a convertir la informaci贸n de formato JSON a un verdadero objeto de JS. Para esto, existe un built-in object en JS llamado JSON, que, junto a un m茅todo llamado parse()hace justo lo que necesitamosVamos a probarlo en nuestro sandbox.js.

1. Dentro del bloque if de nuestra request, creamos una variable llamada data que almacenar谩 el valor de responseText, convertido a un array de objetos. Para ello usamos el m茅todo parse() sobre JSON y le pasamos el JSON string que queramos convertir en un objeto de JS (el responseText).

2. En la callbackFunc del bloque if le pasamos la variable data que acabamos de crear, en lugar del responseText. 

            if (request.readyState === 4 && request.status === 200) {
                const data = JSON.parse(request.responseText);
                callbackFunc(undefined, data);
            } else if (request.readyState === 4) {
                callbackFunc('no se han podido obtener los datos', undefined);
            }

Si ahora guardas y vas a tu consola, 隆ver谩s que tenemos un JS array lleno de objetos! 馃憦

> (200) [{鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, {鈥, 鈥

M谩s adelante exploraremos c贸mo acceder a esos objetos y a sus key-value pairs. Por el momento, hay otra cosa que podemos hacer: crear nuestro propio archivo JSON. 馃挭

3. En la misma carpeta que nuestro index.html sandbox.js (al mismo nivel) creamos un archivo llamado toDos.json. Sabemos que un archivo JSON es un 煤nico string, pero no por ello necesitamos escribir nuestro c贸digo entre comillas " ", ya que nuestro editor sabe que es un archivo JSON.

馃憖 La diferencia cuando escribimos c贸digo JSON es que todos nuestros key-values tienen que estar entre comillas dobles. Para los n煤meros, no es necesario:

[
    {
        "key": "value",
        "key": "value",
        "key": 33,
        "key": 30,
    }
]

4. Siguiendo estas indicaciones, vamos a a帽adir las primeras l铆neas de c贸digo a nuestro archivo JSON, imaginando que estamos creando una base de datos con listas de tareas de diferentes personas. 馃摑

[
    {
        "play dungeons & dragons": "Will"
    },
    {
        "buy bat": "Steve"
    },
    {
        "call Ms. Wheeler": "Billy"
    }
]

5. Volvemos al sandbox.js e intentamos hacer nuestra request un API endpoint distinto. Concretamente, a nuestro archivo JSON, usando un relative path. 

    request.open('GET', 'toDos.json');

隆Y ya lo tenemos! En nuestra consola podemos ver el c贸digo JSON, convertido en un array con 3 objetos dentro. 馃帀

驴Qu茅 demonios es el callback hell?

 Hasta ahora s贸lo estamos llamando a la funci贸n getToDos una vez, y s贸lo solicitando informaci贸n a un API endpoint. En la vida real, lo normal es que hagamos http requests a m谩s de una API, por tanto, vamos a ver c贸mo tratar esos casos. 

1. Creamos una carpeta al nivel del index.html a la que llamamos toDos. Dentro creamos tres archivos JSON, donde cada uno contiene una lista de tareas de una persona distinta. 

Estructura de archivos:

  • 馃搧toDos
    • 馃摑billy.json
    • 馃摑mike.json
    • 馃摑will.json
  • 馃摑index.html
  • 馃摑sandbox.js 
[
  { "task": "call Ms. Wheeler", "author": "Billy" },
  { "task": "recruit people for the dark side", "author": "Billy" },
  { "task": "clean swimmingpool", "author": "Billy" }
]
[
  { "task": "kill Demogorgon", "author": "Mike" },
  { "task": "find Eleven", "author": "Mike" },
  { "task": "buy a new bike", "author": "Mike" }
]
[
  { "task": "play dungeons & dragons", "auhtor": "Will" },
  { "task": "escape from Demogorgon", "author": "Will" },
  { "task": "go to the arcade", "author": "Will" }
]

Lo que queremos es obtener la informaci贸n de los tres archivos en orden, es decir, primero las tareas de Billy, luego las de Mike y finalmente las de Will.

Este concepto de solicitar la informaci贸n en cierto orden es muy com煤n cuando trabajamos con APIs, porque es normal que queramos obtener cierta informaci贸n de una API primero para luego usarla y hacer otra http request, por ejemplo.

Si buscamos en nuestro c贸digo, podemos localizar que el momento en el que una primera request estar铆a completada ser铆a cuando hacemos el console.log de data: 鈥嬧鈥嬧

getToDos((err, data) => {
    console.log('callback function fired!');
    if (err) {
        console.log(err);
    } else {
        console.log(data);
    }
});

As铆 que ese ser铆a un buen lugar para hacer nuestra segunda http request. 

Ahora, nuestro API endpoint apunta al archivo toDos.json. Nosotros queremos recibir la informaci贸n de los otros tres archivos JSON, pero no podemos escribir el relative path de ninguno de 茅stos, porque as铆 no estar铆amos escribiendo c贸digo din谩mico, sino est谩tico (hardcoded). 

2. As铆 que lo que hacemos es pasarle otro par谩metro a la funci贸n getToDos(), llamado tasksList. Se lo pasamos tambi茅n al preparar la request como segundo argumento, sustituyendo as铆 el API endpoint que ten铆amos hardcoded. 

3. Al llamar a la funci贸n getToDos(), que ahora recibe dos argumentos, le pasamos como primer argumento el relative path de uno de nuestros archivos JSON, del que queramos recibir la informaci贸n primero. Por ejemplo, de mike.json.

4. Quitamos el bloque if / else de momento, y s贸lo hacemos un console.log de data, para comprobar que efectivamente s贸lo nos devuelve la informaci贸n de mike.json.鈥嬧鈥嬧鈥嬧

const getToDos = (tasksList, callbackFunc) => {
    const request = new XMLHttpRequest();

    request.addEventListener('readystatechange', () => {
        if (request.readyState === 4 && request.status === 200) {
            const data = JSON.parse(request.responseText);
            callbackFunc(undefined, data);
        } else if (request.readyState === 4) {
            callbackFunc('no se han podido obtener los datos', undefined);
        }
    });

    request.open('GET', tasksList);
    request.send();
};

getToDos('toDos/mike.json', (err, data) => {
    // console.log('callback function fired!');
    console.log(data);
});

Si ahora vas a tu consola, ver谩s que s贸lo has recibido la informaci贸n sobre Mike. 隆Chachi! 馃

5. Como ya estamos seguros de que en este punto hemos recibido la informaci贸n de Mike, ahora queremos recibir la informaci贸n de Billy. Y eso deber铆amos hacerlo justo despu茅s de recibir la informaci贸n de Mike. 

getToDos('toDos/mike.json', (err, data) => {
  console.log(data);
  // solicitar info Billy aqu铆
});

Siguiendo el mismo patr贸n, una vez estemos seguros de que hemos recibido la informaci贸n de Billy, ya podr铆amos solicitar la informaci贸n de Will.

getToDos('toDos/mike.json', (err, data) => {
    console.log(data);
    getToDos('toDos/billy.json', (err, data) => {
        console.log(data);
        getToDos('toDos/will.json', (err, data) => {
            console.log(data);
        });
    });
});

Aunque esto funciona perfectamente, empieza a convertirse en un c贸digo realmente complejo y terriblemente dif铆cil de mantener a la larga. Por no hablar de que, si a帽adi茅semos 10 callbacks m谩s, tendr铆amos un c贸digo en forma de pir谩mide totalmente insostenible. 馃槺

Esto es lo que se llama el callback hell, aunque a m铆 me gusta llamarlo el tri谩ngulo del infierno. As铆 sucede el callback hell: anidando鈥嬧 callbacks dentro de callbacks. 馃樀

callback hell ejemplo

Qu茅 son las promises y c贸mo usarlas鈥嬧

Para lidiar con el callback hell y librarnos de 茅l, podemos usar una herramienta m谩s moderna de JS: las promesas (promises). Para entender bien el concepto, vamos a hacer un peque帽o ejemplo y luego aplicaremos lo aprendido a nuestra getToDos().

Ejemplo con conceptos b谩sicos

1. Comentamos todo el c贸digo del callback hell o lo borramos directamente, porque no nos va a hacer falta m谩s. 

2. Creamos una variable llamada getSomething, que guardar谩 una funci贸n encargada de hacer una http request. Parecido a lo que hace nuestra funci贸n getToDos(). Cuando utilizamos promises, lo primero que debemos hacer es un return de una new Promise. 

const getSomething = () => {
    return new Promise();
};
鈥婾na promise es algo que va a tardar cierto tiempo en ejecutarse, y cuando lo haga, lo har谩 de dos maneras posibles: si se cumple lo que hab铆amos previsto, (por ejemplo, obtener cierta informaci贸n), la promesa ser谩 resuelta (resolved), en caso contrario, la promesa ser谩 rechazada (rejected).

Aqu铆 un esquemita aclarativo: 

esquema estructura promises

3. La promise acepta como par谩metro una funci贸n. Dentro de esa funci贸n es donde hacemos la http request. Por ahora no vamos a hacer una request, solo vamos a simularla para no complicar el ejemplo. 

const getSomething = () => {
    return new Promise(() => {
        
    });
};

Los conceptos de resolve reject son parecidos a cuando hac铆amos un if / else statement para comprobar si la request hab铆a funcionado correctamente y sin errores. Con la ventaja de que en una promise, estos dos m茅todos (resolve reject) vienen por defecto en el objeto promise, como parte de la JavaScript promise API鈥嬧. 馃憣  

4. Y eso es lo que le pasamos a la funci贸n de nuestra promise como par谩metroSi hemos recibido bien los datos de nuestra request, ejecutaremos el m茅todo resolve() y le pasaremos los datos. Pero como por ahora no tenemos datos porque estamos haciendo una simulaci贸n, le pasamos un string diciendo algo tipo 'datos recibidos'.

5. Sin embargo, si hubiese un error, llamar铆amos al m茅todo reject() y le pasar铆amos el error. Como tampoco tenemos un error definido, le pasamos un string diciendo algo tipo 'error en la recepci贸n de los datos'.

const getSomething = () => {
    return new Promise((resolve, reject) => {
        resolve('datos recibidos');
        reject('error en la recepci贸n de los datos');
    });
};

6. Normalmente har铆amos todo esto dentro de un if / else statement, comprobando si todo ha salido bien, ejecutando el resolve, o ejecutando el reject si algo ha ido mal. Pero por motivos de simplicidad, vamos a hacer una posibilidad cada vez. Probamos primero con el m茅todo resolve(), comentando el reject().鈥嬧

7. Si ahora llamamos a la funci贸n getSomething(), 茅sta devuelve una promise, que va a ejecutar el bloque de resolve o el de reject dependiendo de c贸mo haya ido la request. Aqu铆 es donde vinculamos dos m茅todos a la funci贸n: el then y el catch:

getSomething()
    .then()
    .catch();

8. Al m茅todo then() le pasamos una funci贸n que se ejecutar谩 cuando la promise haya salido bien (resulte en resolve). Lo que significa que recibe por par谩metro la informaci贸n que se haya recibido en el m茅todo resolve. Si ahora hacemos un console.log de esa informaci贸n, veremos que es justamente la informaci贸n que trae el m茅todo resolve (la frase 'datos recibidos').

const getSomething = () => {
    return new Promise((resolve, reject) => {
        resolve('datos recibidos');
        // reject('error en la recepci贸n de los datos');
    });
};

getSomething()
    .then(data => {
        console.log(data);
    })

Y esa es la frase que deber铆amos ver en nuestra consola. 馃

9. Si probamos a comentar el resolve() y a descomentar el reject, se dispar谩 la callback function vinculada a otro m茅todo llamado catchAh铆 es donde tenemos que especificar qu茅 hacer con el error. En este caso, simplemente lo imprimimos por consola. 

const getSomething = () => {
    return new Promise((resolve, reject) => {
        // resolve('datos recibidos');
        reject('error en la recepci贸n de los datos');
    });
};

getSomething()
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.log(error);
    });

馃拋鈥嶁檧锔 El estilo para escribir el c贸digo es algo personal, aunque hay ciertas convenciones. Normalmente, cuando encadenamos m茅todos, debemos ponerlos cada uno en una l铆nea nueva para ser m谩s legibles. 


Promises en la pr谩ctica鈥嬧

Hecho ya el ejemplo con datos simulados, vamos a utilizar promises para hacer una verdadera http request. Lo que vamos a hacer es utilizar una promise dentro de la funci贸n getToDos(). Puedes comentar el ejemplo anterior para que no nos moleste.

1. Hacemos un return de una nueva promesa, que acepta una funci贸n como par谩metro. Dentro de esta funci贸n es donde haremos nuestra http request, as铆 que cortamos el c贸digo y lo pegamos ah铆 dentro. 

2. Nuestra promise acepta los m茅todos resolve reject como par谩metros, as铆 que se los pasamos. 

3. Ya no vamos a necesitar el segundo par谩metro en la funci贸n getToDos (callbackFunc), as铆 que lo eliminamos. Esto es as铆 porque en lugar de llamar a la funci贸n callbackFunc, vamos a llamar al m茅todo resolve o al reject seg煤n la promesa se resuelva con 茅xito o no. 馃檲

Al m茅todo resolve le pasamos la variable datamientras que al reject podemos pasarle un string diciendo que ha habido alg煤n tipo de error, como hac铆amos antes en nuestro ejemplo.

const getToDos = tasksList => {
    return new Promise((resolve, reject) => {
        const request = new XMLHttpRequest();

        request.addEventListener('readystatechange', () => {
            if (request.readyState === 4 && request.status === 200) {
                const data = JSON.parse(request.responseText);
                resolve(data);
            } else if (request.readyState === 4) {
                reject('no se han podido obtener los datos');
            }
        });

        request.open('GET', tasksList);
        request.send();
    });
};

4. Llamamos a la funci贸n getToDos() y le pasamos un 煤nico par谩metro, el que hace referencia a la tasksList. Le pasamos por ejemplo el archivo JSON de Billy.

5. A la funci贸n le adjuntamos el m茅todo then(), que, como sabemos, est谩 vinculado al m茅todo resolve(). Es decir, la funci贸n dentro de then() se ejecutar谩 si la 鈥嬧鈥嬧鈥嬧嬧嬧鈥嬧鈥嬧promise se cumple. Le pedimos simplemente que haga un console.log de la informaci贸n recibida en el resolve(). 

6. Hacemos lo mismo con el m茅todo catch(), lo adjuntamos al getToDos y le pasamos una funci贸n que simplemente imprima por consola el error que hemos especificado en el m茅todo reject() en caso de que algo salga mal. Recuerda que esta funci贸n se disparar谩 si algo sale mal en la promise y JS ejecuta el bloque de c贸digo del m茅todo reject().鈥嬧鈥嬧鈥嬧鈥嬧鈥嬧鈥嬧

getToDos('toDos/billy.json')
    .then(data => {
        console.log('promise resolved!:', data);
    })
    .catch(error => {
        console.log('promise rejected :(', error);
    });

隆Y ya est谩! 馃憦 Ahora en tu consola deber铆as ver los datos de Billy. Si intentas escribir el API endpoint mal, por ejemplo:

getToDos('toDos/billyyyy.json')

JS ejecutar谩 la parte del reject(), ya que la promesa no se ha cumplido al no poder obtener los datos debido a un error por nuestra parte. 馃檰鈥嶁檧锔

THE END!

隆Y con esto terminamos nuestra segunda parte de esta Async saga! Espero que hayas aprendido algo nuevo 馃槉.  Si te queda alguna duda, 隆nos vemos en los comentarios! 

Si quieres seguir aprendiendo, aqu铆 tienes la parte #3.鈥嬧

Y si crees que este post puede serle 煤til a alguien, 隆comp谩rtelo!

Otros art铆culos que pueden interesarte

D铆as del 353 al 386
Objetivos versus realidad Y nuevamente, lleg贸 otro d铆a clave. Lleg贸鈥 pas贸. El pasado 4 de marzo este Reto Computer Geek[...]
Angular: Entendiendo la Directiva ngModel
Angular es un framework que nos permite, entre otras cosas, a帽adir contenido din谩mico a nuestros archivos HTML. Una de las formas[...]
D铆as del 2 al 4
"Si buscas resultados distintos no hagas siempre lo mismo" - Albert Einstein Estos d铆as estoy aprendiendo a hacer loops en[...]

Deja un comentario

Este sitio usa Akismet para reducir el spam. Aprende c贸mo se procesan los datos de tus comentarios.

Como toda web legal que se precie, utilizamos cookies para asegurar que damos la mejor experiencia al usuario en nuestro sitio web. Si contin煤as utilizando este sitio asumiremos que est谩s de acuerdo. m谩s informaci贸n

Los ajustes de cookies de esta web est谩n configurados para "permitir cookies" y as铆 ofrecerte la mejor experiencia de navegaci贸n posible. Si sigues utilizando esta web sin cambiar tus ajustes de cookies o haces clic en "Aceptar" estar谩s dando tu consentimiento a esto.

Cerrar