Índice
- Lecturas adicionales sobre SmashingMag:
- Funciones asíncronas
- ¿Qué es la Koa?
- Koa 2 contra Koa 1
- Koa versus expreso
- Estado de Koa 2
- Aplicación de demostración
Una de las próximas características de JavaScript que me gusta especialmente es la compatibilidad con funciones asincrónicas . En este artículo, me gustaría mostrarles un ejemplo muy práctico de cómo crear una aplicación del lado del servidor usando Koa 2 , una nueva versión del marco web, que depende en gran medida de esta característica. Primero, recapitularé qué son las funciones asíncronas y cómo funcionan. Luego, resaltaré las diferencias entre Koa 1 y Koa 2. Después de eso, describiré mi aplicación de demostración para Koa 2, cubriendo todos los aspectos del desarrollo, incluidas las pruebas (usando Mocha, Chai y Supertest) y la implementación (usando PM2). .
Una de las próximas características de JavaScript que me gusta especialmente es la compatibilidad con funciones asincrónicas . En este artículo, me gustaría mostrarles un ejemplo muy práctico de cómo crear una aplicación del lado del servidor usando Koa 2 , una nueva versión del marco web, que depende en gran medida de esta característica.
Primero, recapitularé qué son las funciones asíncronas y cómo funcionan. Luego, resaltaré las diferencias entre Koa 1 y Koa 2. Después de eso, describiré mi aplicación de demostración para Koa 2, cubriendo todos los aspectos del desarrollo, incluidas las pruebas (usando Mocha, Chai y Supertest) y la implementación (usando PM2). .
Lecturas adicionales sobre SmashingMag:
- Comprender la función.prototype.bind de JavaScript
- Representación del lado del servidor con React, Node y Express
- Por qué debería considerar React Native para su aplicación móvil
Funciones asíncronas
El viejo problema de las aplicaciones JavaScript complejas es cómo lidiar con las devoluciones de llamadas y cómo estructurar el código para evitar el llamado "infierno de las devoluciones de llamadas". A lo largo del tiempo se han desarrollado varias soluciones, algunas de las cuales todavía se basan en devoluciones de llamada y otras se basan en características más recientes de JavaScript: promesas y generadores . Comparemos devoluciones de llamada, promesas y generadores usando el ejemplo de una función que recupera dos archivos JSON en secuencia.
// Fetch two files with callbacksfunction doWork(cb) { fetch(‘data1.json’, (err1, result1) = { if (err1) { return cb(err1); } fetch(‘data2.json’, (err2, result2) = { if (err2) { return cb(err2); } cb(null, { result1, result2, }); }) });}
Las funciones anidadas de devolución de llamadas en línea son el principal indicador del infierno de las devoluciones de llamadas. Podrías reestructurar el código y dividir las funciones en módulos, pero de todos modos tendrías que depender de las devoluciones de llamada.
// Fetch two files with promisesfunction doWork(cb) { fetch(‘data1.json’) .then(result1 = fetch(‘data2.json’) .then(result2 = cb(null, { result1, result2, }))) .catch(cb);}
La versión basada en promesas se ve un poco mejor, pero las llamadas aún están anidadas y necesitamos reorganizar el código para que parezca secuencial.
// Fetch two files with generatorsfunction* doWork() { var result1 = yield fetch(‘data1.json’); var result2 = yield fetch(‘data2.json’); return { result1, result2 };}
Los generadores permiten la solución más corta, y esto parece un código sincrónico normal, mientras que los fragmentos de devolución de llamada y promesa son claramente asincrónicos y altamente anidados. Sin embargo, la solución del generador requiere que cambiemos el tipo de función a la función del generador agregando *
después de la function
palabra clave, y requiere una forma especial de invocar doWork
. Esto parece poco intuitivo y la async/await
sintaxis soluciona este inconveniente proporcionando una mejor abstracción. Mire el mismo ejemplo usando funciones asíncronas:
async function doWork() { // Fetch two files with async/await var result1 = await fetch(‘data1.json’); var result2 = await fetch(‘data2.json’); return { result1, result2 };}
Esta sintaxis se puede interpretar de la siguiente manera: Las funciones marcadas con la async
palabra clave nos permiten utilizar la await
palabra clave en ellas, lo que pausa la ejecución de la función para esperar a que finalicen las operaciones asincrónicas. La operación asincrónica puede representarse mediante un generador, una promesa u otra función asincrónica. Además, puede utilizar try
/ catch
para gestionar cualquier error o rechazo que se produzca durante las operaciones asíncronas que realice await
. El mismo mecanismo de manejo de errores es posible con el flujo de control basado en generador.
¿Qué es la Koa?
Koa se posiciona como un framework web de próxima generación y fue diseñado por las personas que crearon Express (y en particular, por TJ ). Koa es muy liviano y modular y permite escribir código sin utilizar devoluciones de llamada. La aplicación Koa es una serie de funciones de middleware que se ejecutan en secuencia procesando las solicitudes entrantes y proporcionando una respuesta. Cada función de middleware tiene acceso al objeto de contexto que envuelve los objetos de solicitud y respuesta del nodo nativo y proporciona una API mejorada para trabajar con ellos. La aplicación básica de Koa se ve así:
// This is Koa 1var koa = require(‘koa’);var app = koa();
app.use(function *(){ this.body = ‘Hello World’;});
app.listen(3000);
Esto no es mucho excepto esto en el núcleo de Koa. La funcionalidad avanzada la proporcionan módulos de terceros.
Koa 2 contra Koa 1
Koa 1 es famoso por su temprana adopción de generadores y por admitir el control basado en generadores listo para usar. Así es como se ve un fragmento de código típico para Koa 1 que utiliza la cascada de middleware y un manejo mejorado de errores:
// Adapted from https://github.com/koajs/koa// In Koa 1.0, middleware is a generator function.app.use(function *(next) { try { yield next; } catch (err) { // the request context is codethis/code this.body = { message: err.message } this.status = err.status || 500 }})
app.use(function *(next) { const user = yield User.getById(this.session.id); this.body = user;})
Koa 2 elimina el soporte integrado para generadores y utiliza funciones asíncronas en su lugar. La firma de las funciones de middleware cambiará para admitir async
funciones de flecha. Así es como se ve el mismo código escrito para Koa 2:
// Taken from https://github.com/koajs/koa// Uses async arrow functionsapp.use(async (ctx, next) = { try { await next(); // next is now a function, await instead of yield } catch (err) { ctx.body = { message: err.message }; ctx.status = err.status || 500; }});
app.use(async ctx = { // await instead of yield const user = await User.getById(ctx.session.id); // ctx instead of this ctx.body = user;});
Aún es posible utilizar funciones normales, promesas y funciones de generador, como se describe en la documentación de Koa 2 .
Koa versus expreso
Koa es un marco más simple y ágil que Express, construido sobre el módulo HTTP de Node.js. Express proporciona funciones integradas para enrutar, crear plantillas y enviar archivos y otras funciones, mientras que Koa proporciona lo mínimo, así como un flujo de control basado en generador (Koa 1) o basado en asíncrono/espera (Koa 2). La comunidad proporciona enrutamiento, plantillas y otras características como módulos separados y normalmente hay varias alternativas para elegir. La sección Koa de GitHub tiene un documento con información adicional sobre las diferencias entre Koa y Express.
Estado de Koa 2
Koa 2 se lanzará una vez que la función async/await esté disponible en Node.js de forma nativa. Afortunadamente, async/await y Koa 2.0 se pueden probar ahora mismo usando Babel. Llegaremos a esto pronto. Primero, repasemos el alcance de la aplicación que creé para demostrar Koa 2 y las funciones asíncronas.
Aplicación de demostración
El objetivo aquí es crear una aplicación simple que rastree las visitas a la página de un sitio web estático, algo así como Google Analytics pero mucho más simple. La aplicación tendrá dos puntos finales:
- uno para almacenar la información sobre un evento (por ejemplo, una vista de página);
- y uno para obtener el número total de eventos;
- Además, los puntos finales deben estar protegidos con una clave API.
Redis se utilizará para almacenar los datos de los eventos. La funcionalidad se probará mediante pruebas unitarias y API. El código fuente completo de la aplicación está disponible en Github .
Dependencias de la aplicación
Comencemos con la lista de módulos necesarios para la aplicación, junto con explicaciones de por qué son necesarios. Primero, aquí están las dependencias necesarias en tiempo de ejecución:
npm install --save # babel-polyfill provides the runtime that async functions need babel-polyfill # The Koa framework itself koa@next # Because Koa is minimalist, we need a parser # to parse JSON in the body of requests koa-bodyparser@next # and the router koa-router@next # The redis module to store the app's data redis # The Koa cors module to allow cross-origin requests kcors@next
Tenga en cuenta que la versión de los módulos Koa que estamos usando es next
. Esto significa que esta versión será compatible con Koa 2 y muchos módulos ya lo proporcionan. Estos son los módulos necesarios para el desarrollo y las pruebas:
npm install --save-dev # To write assertions in our tests chai # A popular testing framework mocha # API-level tests supertest # Babel CLI to build our app babel-cli # A set of Babel plugins to support ES6 features babel-preset-es2015 # and to support stage-3 features babel-preset-stage-3 # Overrides Node.js's require and compiles modules at runtime babel-register # To watch JavaScript files in development and restart the server # if there are changes in the files nodemon
¿Cómo organizar la aplicación?
Después de probar varias formas de organizar los archivos de la aplicación, se me ocurrió la siguiente estructura simple que es adecuada para aplicaciones y equipos pequeños:
index.js
El principal punto de entrada de la aplicación.ecosystem.json
El ecosistema PM2, que describe cómo iniciar la aplicación.src
El código fuente de la aplicación, que contiene archivos JavaScript que Babel compilará.config
Los archivos de configuración de la aplicación.-
build
La compilación de la aplicación, que contiene el código compilado delsrc
directoriosrc
, contiene lo siguiente:api.js
El módulo que define la API de la aplicación.app.js
El módulo que crea una instancia y configura la instancia de la aplicación Koa.config.js
El módulo que proporciona la configuración de la aplicación a otros módulos. Si se necesitan archivos o módulos adicionales a medida que la aplicación crece, los colocaríamos en una subcarpeta de lasrc
carpeta, por ejemplo,src/models
para modelos de aplicaciones,src/routes
para definiciones de API más modulares,src/gateways
para módulos que interactuar con servicios externos, etc.
Scripts de NPM como ejecutor de tareas
Después de usar Gulp y Grunt como ejecutores de tareas, llegué a la conclusión de que los scripts npm son mejores que las herramientas independientes cuando se trabaja en proyectos del lado del servidor. Una de las ventajas de los scripts npm es que le permiten invocar módulos instalados localmente como si estuvieran instalados globalmente. Utilizo los siguientes scripts, que deben definirse en
package.json
:"scripts": { "start": "node index.js", "watch": "nodemon --exec npm run start", "build": "babel src -d build", "test": "npm run build; mocha --require 'babel-polyfill' --compilers js:babel-register"}
El
start
script simplemente se ejecutaindex.js
. Elwatch
script se ejecutastart
utilizando la herramienta nodemon, que reinicia automáticamente el servidor cada vez que cambia algo en la aplicación. Tenga en cuenta que nodemon se instala como una dependencia de desarrollo local y no es necesario instalarlo globalmente. Elbuild
script compila los archivos en lasrc
carpeta usando Babel y envía los resultados a labuild
carpeta. Eltest
script ejecutabuild
primero el script y luego ejecuta las pruebas usandomocha
. Mocha requiere dos módulos:babel-polyfill
para proporcionar dependencias de tiempo de ejecución del código compilado ybabel-register
para compilar los archivos de prueba antes de ejecutarlos. Además, se deben agregar ajustes preestablecidos para Babelpackage.json
, de modo que no sea necesario proporcionarlos en la línea de comando: Significado de emojis{ "babel": { "presets": [ "es2015", "stage-3" ] }}
Este ajuste preestablecido habilita todas las funciones de ECMAScript 2015, así como las funciones que se encuentran actualmente en la etapa 3 . Con esto instalado y configurado, podemos comenzar a desarrollar la aplicación.
El código de la aplicación
Primero, veamos
index.js
:const port = process.env.PORT || 4000;const env = process.env.NODE_ENV || 'development';const src = env === 'production' ? './build/app' : './src/app';require('babel-polyfill');if (env === 'development') { // for development use babel/register for faster runtime compilation require('babel-register');}const app = require(src).default;app.listen(port);
El módulo lee dos variables ambientales:
PORT
yNODE_ENV
.NODE_ENV
debería ser cualquieradevelopment
oproduction
. En modo de desarrollo,babel-register
se utilizará para compilar módulos en tiempo de ejecución.babel-register
almacena en caché los resultados de la compilación y, por lo tanto, reduce el tiempo de inicio del servidor, para que pueda iterar más rápido durante el desarrollo. Debido a que este módulo no se recomienda para uso en producción, la versión precompilada delbuild
directorio se usará en modo de producción.index.js
es el único archivo del proyecto que no será compilado por Babel y que debe usar la sintaxis del módulo nativo (es decir, CommonJS). Por lo tanto, la instancia de la aplicación se encuentra en ladefault
propiedad delapp
módulo importado, que es un módulo ECMAScript 6 que exporta la instancia de la aplicación como exportación predeterminada:export default app;
Es importante tener esto en cuenta si combina módulos ECMAScript 6 y CommonJS. Ahora al
app.js
archivo en sí. Babel siempre compila este y los otros archivos que se analizan a continuación en entornos de desarrollo y producción, y la nueva sintaxis (incluidas las funciones asíncronas) se puede usar libremente en ellos:import Koa from 'koa';import api from './api';import config from './config';import bodyParser from 'koa-bodyparser';import cors from 'kcors';const app = new Koa() .use(cors()) .use(async (ctx, next) = { ctx.state.collections = config.collections; ctx.state.authorizationHeader = `Key ${config.key}`; await next(); }) .use(bodyParser()) .use(api.routes()) .use(api.allowedMethods());export default app;
Aquí, utilizamos
import
la sintaxis de ECMAScript 2015 para importar los módulos necesarios. Luego, creamos una nueva instancia de la aplicación Koa y le adjuntamos varias funciones de middleware utilizando eluse
método. Lo último que hacemos es exportar la aplicación para que pueda ser utilizada porindex.js
. La segunda función de middleware en la cadena es una función asíncrona y una función de flecha al mismo tiempo:app.use(async (ctx, next) = { // Set up the request context ctx..state.collections = config.collections; ctx..state.authorizationHeader = `Key ${config.key}`; await next(); // The execution will reach here only when // the next function returns and finishes all async tasks // console.log('Request is done');})
En Koa 2, el
next
parámetro es una función asíncrona que activa la siguiente función de middleware en la lista. Al igual que en Koa 1, puedes controlar si la función de middleware actual debe hacer su trabajo antes o después de las demás al realizar la llamadanext
al principio de la función actual o al final. Además, puede detectar todos los errores en las funciones de middleware posteriores envolviendo laawait next();
declaración en unatry/catch
declaración siempre que tenga sentido hacerlo.Definiendo la API
El
api.js
archivo es donde reside la lógica central de nuestra aplicación. Debido a que Koa no proporciona enrutamiento listo para usar, la aplicación debe usar elkoa-router
módulo:import KoaRouter from 'koa-router';const api = KoaRouter();
Aquí,
koa-router
se proporcionan funciones para definir funciones de middleware para métodos y rutas HTTP específicos, por ejemplo, la ruta que almacena eventos en la base de datos:// Declare a post method and what it does// :collection is a parameterapi.post('/:collection', // First, validate auth key validateKey, // Then, validate that the provided collection exists validateCollection, // Handle adding the new item to the collection async (ctx, next) = { // Use ES6 destructuring to extract the collection param const { collection } = ctx.params; // Wait until the persistence layer saves the item const count = await ctx .state .collections[collection] .add(ctx.request.body); // Reply with 201 Created when the item is saved ctx.status = 201; });
Cada método puede tener varios controladores, que se ejecutan secuencialmente y tienen exactamente la misma firma que las funciones de middleware definidas en el nivel superior de
app.js
. Por ejemplo,validateKey
yvalidateCollection
son simplemente funciones asíncronas que validan la solicitud entrante y devuelven 404 o 401 si la colección de eventos proporcionada no existe o si la clave API no es válida:const validateCollection = async (ctx, next) = { const { collection } = ctx.params; if (!(collection in ctx.state.collections)) { return ctx.throw(404); } await next();}const validateKey = async (ctx, next) = { const { authorization } = ctx.request.headers; if (authorization !== ctx.state.authorizationHeader) { return ctx.throw(401); } await next();}
Tenga en cuenta que las funciones de middleware de flecha no pueden hacer referencia al contexto de la solicitud actual utilizando
this
(es decir,this
siempre no está definida en los ejemplos hasta ahora). Por lo tanto, los objetos de solicitud y respuesta, así como los ayudantes de Koa, están disponibles en el objeto de contexto (ctx
). Koa 1 no tenía un objeto de contexto separado ythis
se refería al contexto de solicitud actual. Después de definir otros métodos API , finalmente exportamos la API para conectarla a la aplicación Koa enapp.js
:export default api;
Capa de persistencia
En
api.js
, accedemos a lacollections
matriz en el contextoctx
, que inicializamos enapp.js
. Estos objetos de colección son responsables de almacenar y recuperar datos almacenados en Redis. LaCollection
clase es la siguiente:// Use promise-based Redis clientconst redis = require('promise-redis')();const db = redis.createClient();class Collection { // Full source: // https://github.com/OrKoN/koa2-example-app/blob/master/src/collection.js async count() { // We can `await` for promises // The await syntax allows us to work with // async calls as if they were synchronous var count = await db .zcount(this.name, '-inf', '+inf'); return Number(count); } async add(event) { await db .zadd(this.name, 1, JSON.stringify(event)); await this._incrGroups(event); } async _incrGroups(event) { // ES6 for:of syntax allows for easier iteration // groupBy is an array that holds possible attributes of the event for (let attr of this.groupBy) { // We can use await inside loops, // thus calling async operations sequentially in the loop await db.hincrby(`${this.name}_by_${attr}`, event[attr], 1); } }}export default Collection;
La sintaxis async/await es realmente útil aquí porque nos permite coordinar varias operaciones asíncronas fácilmente, por ejemplo, en un bucle. Pero hay una cosa importante a tener en cuenta. Veamos el
_incrGroups
método:async _incrGroups(event) { // ES6 for:of syntax allows iterating easier for (let attr of this.groupBy) { // We can use await inside loops, // thus calling async operations sequentially in the loop await db.hincrby(`${this.name}_by_${attr}`, event[attr], 1); }}
Aquí, las claves se incrementan secuencialmente, lo que significa que la siguiente clave se incrementará una vez que el incremento anterior haya tenido éxito. ¡Pero este tipo de trabajo se puede realizar en paralelo! Con async/await, la tarea puede no ser fácil de realizar, pero las promesas pueden ayudar:
// Start all increments in parallel// because there is no await inside the map callback hereconst promises = this.groupBy.map(attr = db.hincrby(`${this.name}_by_${attr}`, event[attr], 1));// Wait for all of them to finishawait Promise.all(promises);
La intercambiabilidad de promesas y funciones asíncronas es muy útil.
Pruebas
Las pruebas de la aplicación se encuentran en la
test
carpeta . VeamosapiSpec.js
, que representa la especificación de prueba para la API de la aplicación:import { expect } from 'chai';import request from 'supertest';import app from '../build/app';import config from '../build/config';
Importamos
expect
desdechai
ysupertest
. Usamos la versión precompilada de la aplicación y la configuración para asegurarnos de que estamos probando exactamente el mismo código que se ejecutará en producción. A continuación, escribimos las pruebas para la API, aprovechando la sintaxis async/await para lograr la ejecución secuencial de los pasos de prueba:describe('API', () = { const inst = app.listen(4000); describe('POST /:collection', () = { it('should add an event', async () = { const page = 'https://google.com'; const encoded = encodeURIComponent(page); const res = await request(inst) .post(code/pageviews/code) .set({ Authorization: 'Key ' + config.key }) .send({ time: 'now', referrer: 'a', agent: 'b', source: 'c', medium: 'd', campaign: 'e', page: page }) .expect(201); // here res is available // you can use res.headers, res.body etc // no callback for codeexpect/code or codeend/code functions is required. expect(res.body).to.be.empty; }); });});
Tenga en cuenta que las funciones pasadas a
it
funciones están marcadas conasync
. Esto significa queawait
se puede utilizar para ejecutar tareas asincrónicas, incluidas lassupertest
solicitudes, que devuelventhen
objetos capaces y que, por tanto, son compatibles con loawait
esperado.Implementación con PM2
Una vez que la aplicación esté lista y hayan pasado las pruebas, preparemos todo para ejecutar la aplicación en producción. Para esto, declaremos
ecosystem.json
, que contendrá la configuración de producción de la aplicación:{ "apps" : [ { "name" : "koa2-example-app", // The entry point to the compiled version of the app "script" : "index.js", // In production, we don't want to watch for changes in files "watch" : false, // And we want to merge logs from all instances "merge_logs" : true, // We want to timestamp log messages "log_date_format": "YYYY-MM-DD HH:mm Z", "env": { // And include environment variables for the app "NODE_ENV": "production", "PORT": 4000 }, // Start two processes of the app, and balance the load between them "instances": 2, // Start app as a cluster "exec_mode" : "cluster_mode", // Watch failures and auto-restart processes "autorestart": true } ]}
Si su aplicación requiere servidores adicionales (por ejemplo, una tarea cron), puede agregarlos a
ecosystem.json
y se iniciarán junto con el servidor principal. Para iniciar la aplicación en el servidor de producción, puede ejecutar esto:pm2 start ecosystem.json
Y para conservar la configuración, ejecutarías esto:
pm2 save
PM2 proporciona algunas funciones de monitoreo (pruebe
pm2 list
,pm2 info
,pm2 monit
). Por ejemplo, PM2 muestra cuánta memoria está utilizando su aplicación. Esta aplicación básica de Koa consume 44 MB por proceso de Node.js.Conclusión
Babel nos permite crear aplicaciones utilizando la sintaxis ECMAScript que no está disponible de forma nativa pero que incluye async/await, lo que hace que escribir código asincrónico sea más agradable. El código que utiliza la sintaxis async/await es más fácil de leer y mantener. Koa 2 y Babel te permiten comenzar a usar funciones asíncronas ahora mismo. Sin embargo, Babel conlleva una sobrecarga adicional y requiere una configuración adicional y un paso de compilación adicional. Por lo tanto, se recomienda esperar hasta que async/await esté disponible de forma nativa en Node.js. Koa 2 debería lanzarse oficialmente una vez que esto suceda. Entonces, Koa 2 será una buena alternativa a Express porque es más modular y sencillo y permite configurarlo como quieras. La sección de implementación de este tutorial puede ser demasiado simple y no escalable. Deja abierta la cuestión de cómo y cuándo construir e implementar el código; puede hacerlo manualmente (
rsync
,scp
) o configurar un servidor de integración continua para esto. Además, la arquitectura interna de la aplicación es demasiado simple pero adecuada para una demostración. Las aplicaciones más grandes y ambiciosas pueden requerir otras entidades, como puertas de enlace, mapeadores, repositorios, etc., pero todas ellas pueden aprovechar las funciones asíncronas. Espero que hayas disfrutado de este tutorial. ¡Gracias por leer!Enlaces
- Koa , GitHub Una lista de módulos Koa
- “ Funciones asíncronas, borrador de etapa 3 ”, Comité Técnico Internacional 39 de Ecma, GitHub La especificación asíncrona
- koa2-example-app El código fuente completo de nuestra aplicación de demostración
- “ Middleware ”, Koa, soporte de GitHub Middleware para Koa 2
- “ Koa 2.0.0 ”, GitHub El problema de GitHub para seguir el progreso de Koa 2
- “ Implementación de Async/Await en V8 ”, GitHub El problema de GitHub para rastrear el progreso de la implementación de Async/Await en Node (al)
Explora más en
- Codificación
- Marcos
- javascript
- API
Tal vez te puede interesar:
- Creación de su propia biblioteca de validación de React: la experiencia del desarrollador (Parte 3)
- Introducción a Quasar Framework: creación de aplicaciones multiplataforma
- Creación de un componente web retro que se puede arrastrar con iluminación
- Creación y acoplamiento de una aplicación Node.js con arquitectura sin estado con la ayuda de Kinsta
Creación de una aplicación del lado del servidor con funciones asíncronas y Koa 2
Lecturas adicionales sobre SmashingMag:Funciones asíncronas¿Qué es la Koa?Koa 2 contra Koa 1Koa versus expresoEstado de Koa 2Aplicación de demostraciónCre
programar
es
https://aprendeprogramando.es/static/images/programar-creacion-de-una-aplicacion-del-lado-del-servidor-con-funciones-asincronas-y-koa-2-900-0.jpg
2025-01-14
Si crees que alguno de los contenidos (texto, imagenes o multimedia) en esta página infringe tus derechos relativos a propiedad intelectual, marcas registradas o cualquier otro de tus derechos, por favor ponte en contacto con nosotros en el mail [email protected] y retiraremos este contenido inmediatamente