Cómo leer y escribir archivos CSV en Node.js usando Node-CSV

Índice
  1. Introducción
  • Prerrequisitos
  • Paso 1: Configuración del directorio del proyecto
  • Paso 2: Lectura de archivos CSV
  • Paso 3: inserción de datos en la base de datos
  • Paso 4: Escritura de archivos CSV
  • Conclusión
  • La autora seleccionó a la Sociedad de Mujeres Ingenieras para recibir una donación como parte del programa Write for DOnations .

    Introducción

    Un CSV es un formato de archivo de texto sin formato para almacenar datos tabulares. El archivo CSV utiliza un delimitador de coma para separar los valores en las celdas de la tabla, y una nueva línea delinea dónde comienzan y terminan las filas. La mayoría de los programas de hojas de cálculo y bases de datos pueden exportar e importar archivos CSV. Debido a que CSV es un archivo de texto sin formato, cualquier lenguaje de programación puede analizar y escribir en un archivo CSV. Node.js tiene muchos módulos que pueden trabajar con archivos CSV, como node-csv, fast-csvy papaparse.

    En este tutorial, utilizará el node-csvmódulo para leer un archivo CSV mediante secuencias de Node.js, lo que le permite leer grandes conjuntos de datos sin consumir mucha memoria. Modificará el programa para mover los datos analizados del archivo CSV a una base de datos SQLite. También recuperará datos de la base de datos, los analizará con node-csvy utilizará secuencias de Node.js para escribirlos en un archivo CSV en fragmentos.

    Implemente sus aplicaciones Node desde GitHub con la plataforma de aplicaciones DigitalOcean . Deje que DigitalOcean se concentre en escalar su aplicación.

    Prerrequisitos

    Para seguir este tutorial, necesitarás:

    • Node.js instalado en su entorno local o de servidor. Siga Cómo instalar Node.js y crear un entorno de desarrollo local para instalar Node.js.

    • SQLite instalado en su entorno local o de servidor, que puede instalar siguiendo el paso 1 de Cómo instalar y usar SQLite en Ubuntu 20.04 . Es útil saber cómo usar SQLite y se puede aprender en los pasos 2 a 7 de la guía de instalación.

    • Familiaridad con la escritura de un programa Node.js. Consulte Cómo escribir y ejecutar su primer programa en Node.js.

    • Familiaridad con los flujos de Node.js. Consulte Cómo trabajar con archivos mediante flujos en Node.js.

    Paso 1: Configuración del directorio del proyecto

    En esta sección, creará el directorio del proyecto y descargará paquetes para su aplicación. También descargará un conjunto de datos CSV de Stats NZ , que contiene datos de migración internacional en Nueva Zelanda.

    Para comenzar, cree un directorio llamado csv_demoy navegue hasta el directorio:

    1. mkdir csv_demo
    2. cd csv_demo

    A continuación, inicialice el directorio como un proyecto npm usando el npm initcomando:

    1. npm init -y

    La -yopción indica npm initque se debe responder “sí” a todas las solicitudes. Este comando crea un archivo package.jsoncon valores predeterminados que puede cambiar en cualquier momento.

    Con el directorio inicializado como un proyecto npm, ahora puedes instalar las dependencias necesarias: node-csvy node-sqlite3.

    Introduzca el siguiente comando para instalar node-csv:

    1. npm install csv

    El node-csvmódulo es una colección de módulos que le permite analizar y escribir datos en un archivo CSV. El comando instala los cuatro módulos que forman parte del node-csvpaquete: csv-generate, csv-parse, csv-stringifyy stream-transform. Utilizará el csv-parsemódulo para analizar un archivo CSV y el csv-stringifymódulo para escribir datos en un archivo CSV.

    A continuación, instale el node-sqlite3módulo:

    1. npm install sqlite3

    El node-sqlite3módulo permite que su aplicación interactúe con la base de datos SQLite.

    Después de instalar los paquetes en su proyecto, descargue el archivo CSV de migración de Nueva Zelanda con el wgetcomando:

    1. wget https://www.stats.govt.nz/assets/Uploads/International-migration/International-migration-September-2021-Infoshare-tables/Download-data/international-migration-September-2021-estimated-migration-by-age-and-sex-csv.csv

    El archivo CSV que descargaste tiene un nombre largo. Para que sea más fácil trabajar con él, cambia el nombre del archivo por uno más corto usando el mvcomando:

    1. mv international-migration-September-2021-estimated-migration-by-age-and-sex-csv.csv migration_data.csv

    El nuevo nombre de archivo CSV, migration_data.csv, es más corto y más fácil de trabajar.

    Usando nano, o su editor de texto favorito, abra el archivo:

    1. nano migration_data.csv

    Una vez abierto, verás un contenido similar a este:

    demo_csv/datos_migracion.csv

    year_month,month_of_release,passenger_type,direction,sex,age,estimate,standard_error,status2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,344,0,Final2001-01,2020-09,Long-term migrant,Arrivals,Male,0-4 years,341,0,Final...

    La primera línea contiene los nombres de las columnas y todas las líneas subsiguientes contienen los datos correspondientes a cada columna. Una coma separa cada dato. Este carácter se conoce como delimitador porque delimita los campos. No está limitado al uso de comas. Otros delimitadores populares incluyen dos puntos( :), punto y coma( ;) y tabulaciones( td). Debe saber qué delimitador se utiliza en el archivo, ya que la mayoría de los módulos lo requieren para analizar los archivos.

    Después de revisar el archivo e identificar el delimitador, salga del migration_data.csvarchivo usando CTRL+X.

    Ya ha instalado las dependencias necesarias para su proyecto. En la siguiente sección, leerá un archivo CSV.

    Paso 2: Lectura de archivos CSV

    En esta sección, utilizará node-csvel método fsdel módulo createReadStream()para leer los datos del archivo CSV y crear una secuencia legible. Luego, conectará la secuencia a otra secuencia inicializada con el csv-parsemódulo para analizar los fragmentos de datos. Una vez que se hayan analizado los fragmentos de datos, podrá registrarlos en la consola.

    Crea y abre un readCSV.jsarchivo en tu editor preferido:

    1. nano readCSV.js

    En su readCSV.jsarchivo, importe los módulos fsy csv-parseagregando las siguientes líneas:

    demo_csv/readCSV.js

    const fs = require("fs");const { parse } = require("csv-parse");

    En la primera línea, define la fsvariable y le asigna el fsobjeto que el require()método Node.js devuelve cuando importa el módulo.

    En la segunda línea, extrae el parsemétodo del objeto devuelto por el require()método en la parsevariable utilizando la sintaxis de desestructuración .

    Agregue las siguientes líneas para leer el archivo CSV:

    demo_csv/readCSV.js

    ...fs.createReadStream("./migration_data.csv")  .pipe(parse({ delimiter: ",", from_line: 2 }))  .on("data", function (row) {    console.log(row);  })

    El createReadStream()método del fsmódulo acepta un argumento del nombre del archivo que desea leer, que se encuentra migration_data.csvaquí. Luego, crea un flujo legible , que toma un archivo grande y lo divide en fragmentos más pequeños. Un flujo legible le permite solo leer datos de él y no escribir en él.

    Después de crear el flujo legible, pipe()el método de Node reenvía fragmentos de datos del flujo legible a otro flujo. El segundo flujo se crea cuando se invoca el método csv-parsedel módulo dentro del método. El módulo implementa un flujo de transformación (un flujo legible y escribible), que toma un fragmento de datos y lo transforma en otro formato. Por ejemplo, cuando recibe un fragmento como , el método lo transformará en una matriz.parse()pipe()csv-parse2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,344parse()

    El parse()método toma un objeto que acepta propiedades. Luego, el objeto configura y proporciona más información sobre los datos que analizará el método. El objeto toma las siguientes propiedades:

    • delimiterdefine el carácter que separa cada campo de la fila. El valor ,indica al analizador que las comas delimitan los campos.

    • from_linedefine la línea en la que el analizador debe comenzar a analizar las filas. Con el valor 2, el analizador omitirá la línea 1 y comenzará en la línea 2. Debido a que insertará los datos en la base de datos más adelante, esta propiedad lo ayuda a evitar insertar los nombres de las columnas en la primera fila de la base de datos.

    A continuación, adjunta un evento de transmisión mediante el on()método Node.js. Un evento de transmisión permite que el método consuma un fragmento de datos si se emite un determinado evento. El dataevento se activa cuando los datos transformados desde el parse()método están listos para ser consumidos. Para acceder a los datos, pasa una devolución de llamada al on()método, que toma un parámetro llamado row. El rowparámetro es un fragmento de datos transformado en una matriz. Dentro de la devolución de llamada, registra los datos en la consola mediante el console.log()método.

    Antes de ejecutar el archivo, deberá agregar más eventos de transmisión. Estos eventos de transmisión manejan errores y escriben un mensaje de éxito en la consola cuando se han consumido todos los datos del archivo CSV.

    Aún en su readCSV.jsarchivo, agregue el código resaltado:

    demo_csv/readCSV.js

    ...fs.createReadStream("./migration_data.csv")  .pipe(parse({ delimiter: ",", from_line: 2 }))  .on("data", function (row) {    console.log(row);  })  .on("end", function () {    console.log("finished");  })  .on("error", function (error) {    console.log(error.message);  });

    El endevento se emite cuando se han leído todos los datos del archivo CSV. Cuando esto sucede, se invoca la devolución de llamada y se registra un mensaje que indica que ha finalizado.

    Si ocurre un error en cualquier lugar mientras se leen y analizan los datos CSV, errorse emite el evento, que invoca la devolución de llamada y registra el mensaje de error en la consola.

    Su archivo completo ahora debería verse así:

    demo_csv/readCSV.js

    const fs = require("fs");const { parse } = require("csv-parse");fs.createReadStream("./migration_data.csv")  .pipe(parse({ delimiter: ",", from_line: 2 }))  .on("data", function (row) {    console.log(row);  })  .on("end", function () {    console.log("finished");  })  .on("error", function (error) {    console.log(error.message);  });

    Guarde y salga de su readCSV.jsarchivo usando CTRL+X.

    A continuación, ejecute el archivo utilizando el nodecomando:

    1. node readCSV.js

    El resultado será similar a esto (editado para abreviar):

    Output[  '2001-01',  '2020-09',  'Long-term migrant',  'Arrivals',  'Female',  '0-4 years',  '344',  '0',  'Final']...[  '2021-09',  ...  '70',  'Provisional']finished

    Todas las filas del archivo CSV se han transformado en matrices mediante el csv-parseflujo de transformación. Debido a que el registro se realiza cada vez que se recibe un fragmento del flujo, los datos aparecen como si se estuvieran descargando en lugar de mostrarse todos a la vez.

    En este paso, leerá los datos de un archivo CSV y los transformará en matrices. A continuación, insertará los datos de un archivo CSV en la base de datos.

    Paso 3: inserción de datos en la base de datos

    Insertar datos de un archivo CSV en la base de datos usando Node.js le brinda acceso a una amplia biblioteca de módulos que puede usar para procesar, limpiar o mejorar los datos antes de insertarlos en la base de datos.

    En esta sección, establecerá una conexión con la base de datos SQLite mediante el node-sqlite3módulo. Luego, creará una tabla en la base de datos, copiará el readCSV.jsarchivo y lo modificará para insertar todos los datos leídos del archivo CSV en la base de datos.

    Crea y abre un db.jsarchivo en tu editor:

    1. nano db.js

    En su db.jsarchivo, agregue las siguientes líneas para importar los módulos fsy node-sqlite3:

    demo_csv/db.js

    const fs = require("fs");const sqlite3 = require("sqlite3").verbose();const filepath = "./population.db";...

    En la tercera línea, se define la ruta de la base de datos SQLite y se almacena en la variable filepath. El archivo de la base de datos aún no existe, pero será necesario para node-sqlite3establecer una conexión con la base de datos.

    En el mismo archivo, agregue las siguientes líneas para conectar Node.js a una base de datos SQLite:

    demo_csv/db.js

    ...function connectToDatabase() {  if (fs.existsSync(filepath)) {    return new sqlite3.Database(filepath);  } else {    const db = new sqlite3.Database(filepath, (error) = {      if (error) {        return console.error(error.message);      }      console.log("Connected to the database successfully");    });    return db;  }}

    Aquí, se define una función denominada connectToDatabase()para establecer una conexión con la base de datos. Dentro de la función, se invoca el método fsdel módulo en una declaración, que verifica si el archivo de la base de datos existe en el directorio del proyecto. Si la condición se evalúa como , se crea una instancia de la clase SQLite del módulo con la ruta del archivo de la base de datos. Una vez que se establece la conexión, la función devuelve el objeto de conexión y sale.existsSync()ififtrueDatabase()node-sqlite3

    Sin embargo, si la ifinstrucción se evalúa como false(si el archivo de base de datos no existe), la ejecución saltará al elsebloque. En el elsebloque, crea una instancia de la Database()clase con dos argumentos: la ruta del archivo de base de datos y una devolución de llamada.

    El primer argumento es la ruta del archivo de base de datos SQLite, que es ./population.db. El segundo argumento es una devolución de llamada que se invocará automáticamente cuando la conexión con la base de datos se haya establecido correctamente o si se produjo un error. La devolución de llamada toma un errorobjeto como parámetro, que es nullsi la conexión es exitosa. Dentro de la devolución de llamada, la ifdeclaración verifica si el errorobjeto está configurado. Si evalúa como true, la devolución de llamada registra un mensaje de error y regresa. Si evalúa como false, registra un mensaje de éxito que confirma que se ha establecido la conexión.

    Actualmente, los bloques ify elseestablecen el objeto de conexión. Se pasa una devolución de llamada al invocar la Databaseclase en el elsebloque para crear una tabla en la base de datos, pero solo si el archivo de base de datos no existe. Si el archivo de base de datos ya existe, la función ejecutará el ifbloque, se conectará con la base de datos y devolverá el objeto de conexión.

    Para crear una tabla si el archivo de base de datos no existe, agregue el código resaltado:

    demo_csv/db.js

    const fs = require("fs");const sqlite3 = require("sqlite3").verbose();const filepath = "./population.db";function connectToDatabase() {  if (fs.existsSync(filepath)) {    return new sqlite3.Database(filepath);  } else {    const db = new sqlite3.Database(filepath, (error) = {      if (error) {        return console.error(error.message);      }      createTable(db);      console.log("Connected to the database successfully");    });    return db;  }}function createTable(db) {  db.exec(`  CREATE TABLE migration  (    year_month       VARCHAR(10),    month_of_release VARCHAR(10),    passenger_type   VARCHAR(50),    direction        VARCHAR(20),    sex              VARCHAR(10),    age              VARCHAR(50),    estimate         INT  )`);}module.exports = connectToDatabase();

    Ahora connectToDatabase()invoca la createTable()función, que acepta el objeto de conexión almacenado en la dbvariable como argumento.

    Fuera de la connectToDatabase()función, se define la createTable()función, que acepta el objeto de conexión dbcomo parámetro. Se invoca el exec()método en el dbobjeto de conexión que toma una sentencia SQL como argumento. La sentencia SQL crea una tabla con un nombre migrationy 7 columnas. Los nombres de las columnas coinciden con los encabezados del migration_data.csvarchivo.

    Finalmente, invoca la connectToDatabase()función y exporta el objeto de conexión devuelto por la función para que pueda reutilizarse en otros archivos.

    Guarde y salga de su db.jsarchivo.

    Una vez establecida la conexión a la base de datos, ahora copiará y modificará el readCSV.jsarchivo para insertar las filas que el csv-parsemódulo analizó en la base de datos.

    Copie y cambie el nombre del archivo insertData.jscon el siguiente comando:

    1. cp readCSV.js insertData.js

    Abra el insertData.jsarchivo en su editor:

    1. nano insertData.js

    Añade el código resaltado:

    demo_csv/insertData.js

    const fs = require("fs");const { parse } = require("csv-parse");const db = require("./db");fs.createReadStream("./migration_data.csv")  .pipe(parse({ delimiter: ",", from_line: 2 }))  .on("data", function (row) {    db.serialize(function () {      db.run(        `INSERT INTO migration VALUES (?, ?, ? , ?, ?, ?, ?)`,        [row[0], row[1], row[2], row[3], row[4], row[5], row[6]],        function (error) {          if (error) {            return console.log(error.message);          }          console.log(`Inserted a row with the id: ${this.lastID}`);        }      );    });  });

    En la tercera línea, importa el objeto de conexión desde el db.jsarchivo y lo almacena en la variable db.

    Dentro de la datadevolución de llamada de evento adjunta a la fssecuencia del módulo, se invoca el serialize()método en el objeto de conexión. El método garantiza que una instrucción SQL termine de ejecutarse antes de que comience otra, lo que puede ayudar a evitar condiciones de competencia en la base de datos donde el sistema ejecuta operaciones que compiten entre sí simultáneamente.

    El serialize()método acepta una devolución de llamada. En la devolución de llamada, se invoca el runmétodo en el dbobjeto de conexión. El método acepta tres argumentos:

    • El primer argumento es una sentencia SQL que se pasará y se ejecutará en la base de datos SQLite. El run()método solo acepta sentencias SQL que no devuelvan resultados. La INSERT INTO migration VALUES (?, ..., ?sentencia inserta una fila en la tabla migrationy ?son marcadores de posición que luego se sustituyen con los valores en el run()segundo argumento del método.

    • El segundo argumento es una matriz [row[0], ... row[5], row[6]]. En la sección anterior, el parse()método recibe un fragmento de datos del flujo legible y lo transforma en una matriz. Dado que los datos se reciben como una matriz, para obtener cada valor de campo, debe usar índices de matriz para acceder a ellos, como [row[1], ..., row[6]], etc.

    • El tercer argumento es una devolución de llamada que se ejecuta cuando se han insertado los datos o si se ha producido un error. La devolución de llamada comprueba si se ha producido un error y registra el mensaje de error. Si no hay errores, la función registra un mensaje de éxito en la consola mediante el console.log()método, lo que permite saber que se ha insertado una fila junto con el ID.

    Por último, elimine los eventos endy errordel archivo. Debido a la naturaleza asincrónica de los node-sqlite3métodos, los eventos endy errorse ejecutan antes de que se inserten los datos en la base de datos, por lo que ya no son necesarios.

    Guarde y salga de su archivo.

    Ejecute el insertData.jsarchivo usando node:

    1. node insertData.js

    Dependiendo de su sistema, puede tomar algún tiempo, pero nodedebería devolver el siguiente resultado:

    OutputConnected to the database successfullyInserted a row with the id: 1Inserted a row with the id: 2...Inserted a row with the id: 44308Inserted a row with the id: 44309Inserted a row with the id: 44310

    El mensaje, especialmente los identificadores, demuestra que la fila del archivo CSV se ha guardado en la base de datos.

    Ahora puedes leer un archivo CSV e insertar su contenido en la base de datos. A continuación, escribirás un archivo CSV.

    Paso 4: Escritura de archivos CSV

    En esta sección, recuperará datos de la base de datos y los escribirá en un archivo CSV mediante transmisiones.

    Crea y abre writeCSV.jsen tu editor:

    1. nano writeCSV.js

    En su writeCSV.jsarchivo, agregue las siguientes líneas para importar los módulos fsy csv-stringifyy el objeto de conexión de base de datos desde db.js:

    demo_csv/writeCSV.js

    const fs = require("fs");const { stringify } = require("csv-stringify");const db = require("./db");

    El csv-stringifymódulo transforma datos de un objeto o matriz en un formato de texto CSV.

    A continuación, agregue las siguientes líneas para definir una variable que contenga el nombre del archivo CSV en el que desea escribir datos y una secuencia escribible en la que escribirá los datos:

    demo_csv/writeCSV.js

    ...const filename = "saved_from_db.csv";const writableStream = fs.createWriteStream(filename);const columns = [  "year_month",  "month_of_release",  "passenger_type",  "direction",  "sex",  "age",  "estimate",];

    El createWriteStreammétodo toma como argumento el nombre del archivo en el que desea escribir su flujo de datos, que es el saved_from_db.csvnombre del archivo almacenado en la filenamevariable.

    En la cuarta línea, se define una columnsvariable que almacena una matriz que contiene los nombres de los encabezados de los datos CSV. Estos encabezados se escribirán en la primera línea del archivo CSV cuando comience a escribir los datos en el archivo.

    Aún en su writeCSV.jsarchivo, agregue las siguientes líneas para recuperar datos de la base de datos y escribir cada fila en el archivo CSV:

    demo_csv/writeCSV.js

    ...const stringifier = stringify({ header: true, columns: columns });db.each(`select * from migration`, (error, row) = {  if (error) {    return console.log(error.message);  }  stringifier.write(row);});stringifier.pipe(writableStream);console.log("Finished writing data");

    En primer lugar, se invoca el stringifymétodo con un objeto como argumento, lo que crea un flujo de transformación. El flujo de transformación convierte los datos de un objeto en texto CSV. El objeto que se pasa al stringify()método tiene dos propiedades:

    • headeracepta un valor booleano y genera un encabezado si el valor booleano se establece en true.
    • columnstoma una matriz que contiene los nombres de las columnas que se escribirán en la primera línea del archivo CSV si la headeropción está configurada en true.

    A continuación, invoca el each()método desde el dbobjeto de conexión con dos argumentos. El primer argumento es la sentencia SQL select * from migrationque recupera las filas una por una en la base de datos. El segundo argumento es una devolución de llamada que se invoca cada vez que se recupera una fila de la base de datos. La devolución de llamada toma dos parámetros: un errorobjeto y un rowobjeto que contiene datos recuperados de una sola fila en la base de datos. Dentro de la devolución de llamada, verifica si el errorobjeto está configurado en la ifsentencia. Si la condición se evalúa como true, se registra un mensaje de error en la consola mediante el console.log()método. Si no hay ningún error, invoca el write()método on stringifier, que escribe los datos en el stringifierflujo de transformación.

    Cuando el each()método termina de iterar, el pipe()método en el stringifierflujo comienza a enviar datos en fragmentos y a escribirlos en el archivo writableStream. El flujo escribible guardará cada fragmento de datos en el saved_from_db.csvarchivo. Una vez que todos los datos se hayan escrito en el archivo, console.log()se registrará un mensaje de éxito.

    El archivo completo ahora se verá así:

    demo_csv/writeCSV.js

    const fs = require("fs");const { stringify } = require("csv-stringify");const db = require("./db");const filename = "saved_from_db.csv";const writableStream = fs.createWriteStream(filename);const columns = [  "year_month",  "month_of_release",  "passenger_type",  "direction",  "sex",  "age",  "estimate",];const stringifier = stringify({ header: true, columns: columns });db.each(`select * from migration`, (error, row) = {  if (error) {    return console.log(error.message);  }  stringifier.write(row);});stringifier.pipe(writableStream);console.log("Finished writing data");

    Guarde y cierre su archivo, luego ejecute el writeCSV.jsarchivo en la terminal:

    1. node writeCSV.js

    Recibirá el siguiente resultado:

    OutputFinished writing data

    Para confirmar que se han escrito los datos, inspeccione el contenido del archivo utilizando el catcomando:

    1. cat saved_from_db.csv

    catdevolverá todas las filas escritas en el archivo (editado para abreviar):

    Outputyear_month,month_of_release,passenger_type,direction,sex,age,estimate2001-01,2020-09,Long-term migrant,Arrivals,Female,0-4 years,3442001-01,2020-09,Long-term migrant,Arrivals,Male,0-4 years,3412001-01,2020-09,Long-term migrant,Arrivals,Female,10-14 years,...

    Ahora puede recuperar datos de la base de datos y escribir cada fila en un archivo CSV usando secuencias.

    Conclusión

    En este artículo, leyó un archivo CSV e insertó sus datos en una base de datos mediante los módulos node-csvy node-sqlite3. Luego recuperó datos de la base de datos y los escribió en otro archivo CSV.

    Ahora puede leer y escribir archivos CSV. Como próximo paso, puede trabajar con grandes conjuntos de datos CSV utilizando la misma implementación con flujos que hacen un uso eficiente de la memoria, o puede buscar un paquete event-streamque facilite el trabajo con flujos.

    Para explorar más sobre node-csv, visita su documentación Proyecto CSV – Paquete CSV de Node.js . Para obtener más información sobre node-sqlite3, visita su documentación de Github . Para seguir desarrollando tus habilidades en Node.js, consulta la serie Cómo codificar en Node.js.

    SUSCRÍBETE A NUESTRO BOLETÍN 
    No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

    Subir

    Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion