Carga de datos de archivos en la nube

El paquete PL/SQL DBMS_CLOUD proporciona soporte para cargar datos de archivos en la nube a tablas creadas en su base de datos de IA autónoma en una infraestructura de Exadata dedicada.

Puede cargar datos de diferentes formatos del archivo mediante los siguientes procedimientos PL/SQL proporcionados por DBMS_CLOUD:

Antes de cargar los datos de los archivos, asegúrese de que:

El paquete DBMS_CLOUD admite la carga desde archivos en los siguientes servicios en el nube: Oracle Cloud Infrastructure Object Storage, Oracle Cloud Infrastructure Object Storage Classic, Azure Blob Storage y Amazon S3.

Creación de credenciales

Descubra cómo almacenar su credencial de Cloud Object Storage mediante el procedimiento DBMS_CLOUD.CREATE_CREDENTIAL.

Ejecute el procedimiento DBMS_CLOUD.CREATE_CREDENTIAL mediante cualquier herramienta de base de datos como SQL*Plus, SQL Developer o Database Actions (herramienta de la herramienta SQL Developer basada en web). Por ejemplo:

BEGIN
  DBMS_CLOUD.CREATE_CREDENTIAL(
    credential_name => 'DEF_CRED_NAME',
    username => 'adb_user@oracle.com',
    password => 'password'
  );
END;
/

Los valores que proporcione para username y password dependen del servicio de Cloud Object Storage que utilice:

Esta operación almacena las credenciales en la base de datos en un formato cifrado. Puede utilizar cualquier nombre para el nombre de credencial. Tenga en cuenta que este paso solo es necesario una vez, a menos que cambien las credenciales del almacén de objetos. Una vez almacenadas las credenciales, puede utilizar el mismo nombre de credencial para todas las cargas de datos.

Carga de datos de archivos de texto

Descubra cómo cargar datos de archivos de texto en la nube a su base de datos de IA autónoma mediante el procedimiento DBMS_CLOUD.COPY_DATA.

El archivo de origen de este ejemplo, channels.txt, tiene los siguientes datos:

S,Direct Sales,Direct
T,Tele Sales,Direct
C,Catalog,Indirect
I,Internet,Indirect
P,Partners,Others
  1. Almacene la credencial de Cloud Object Storage mediante el procedimiento DBMS_CREDENTIAL.CREATE_CREDENTIAL. Consulte Creación de credenciales para obtener más información.</span>

  2. Cree la tabla que contendrá los datos. Por ejemplo:

     CREATE TABLE CHANNELS
       (channel_id CHAR(1),
         channel_desc VARCHAR2(20),
         channel_class VARCHAR2(20)
       );
     /
    
  3. Cargue datos en la tabla mediante el procedimiento DBMS_CLOUD.COPY_DATA. Por ejemplo:

     BEGIN
      DBMS_CLOUD.COPY_DATA(
        table_name =>'CHANNELS',
        credential_name =>'DEF_CRED_NAME',
        file_uri_list =>'https://swiftobjectstorage.us-phoenix-1.oraclecloud.com/v1/idthydc0kinr/mybucket/channels.txt',
        format => json_object('delimiter' value ',')
      );
     END;
     /
    
    

    Los parámetros son:

    • table_name: es el nombre de la tabla de destino.

    • credential_name: es el nombre de la credencial creada en el paso anterior.

    • file_uri_list: es una lista delimitada por comas de los archivos de origen que desea cargar.

      En este ejemplo, file_uri_list es un URI de Swift de Oracle Cloud Infrastructure que especifica el archivo channels.txt en el bucket mybucket de la región us-phoenix-1. (idthydc0kinr es el espacio de nombres de almacenamiento de objetos en el que reside el bucket). Para obtener información sobre los formatos de URI soportados, consulte Formatos de URI de almacenamiento de objetos en la nube.

    • format: define las opciones que especifique para describir el formato del archivo de origen. Para obtener información sobre las opciones de formato que puede especificar, consulte Parámetro de formato.

Carga de un archivo JSON de documentos delimitados en una recopilación

Descubra cómo cargar un archivo JSON de documentos delimitados a una recopilación en la base de datos de IA autónoma mediante el procedimiento DBMS_CLOUD.COPY_DATA.

En este ejemplo, se cargan los valores JSON de un archivo delimitado por línea y el archivo JSON myCollection.json. Cada valor, cada línea, se carga en una recopilación de la base de datos de IA autónoma como un único documento.

Este es un ejemplo de dicho archivo. Tiene tres líneas, con un objeto por línea. Cada uno de esos objetos se carga como un documento JSON independiente.

{ "name" : "apple", "count": 20 }
{ "name" : "orange", "count": 42 }
{ "name" : "pear", "count": 10 }

Procedimiento

  1. Almacene la credencial de Cloud Object Storage mediante el procedimiento DBMS_CLOUD.CREATE_CREDENTIAL. Consulte Creación de credenciales para obtener más información.

  2. Cargue datos en una recopilación mediante el procedimiento DBMS_CLOUD.COPY_DATA. Por ejemplo:

     BEGIN 
       DBMS_CLOUD.COPY_COLLECTION(
         collection_name =>'fruit',
         credential_name =>'DEF_CRED_NAME',
         file_uri_list =>'https://objectstorage.us-ashburn-1.oraclecloud.com/n/namespace-string/b/fruit_bucket/o/myCollection.json',
         format => json_object('recorddelimiter' value '''\n''')
       );
     END;
     /
    

    Los parámetros son:

    • collection_name: es el nombre de la recopilación de destino.

    • credential_name: es el nombre de la credencial creada en el paso anterior.

    • file_uri_list: es una lista delimitada por comas de los archivos de origen que desea cargar.

      En este ejemplo, file_uri_list es un URI de Swift de Oracle Cloud Infrastructure que especifica el archivo myCollection.json en el bucket mybucket en la región us-phoenix-1. Para obtener información sobre los formatos de URI soportados, consulte Formatos de URI de almacenamiento de objetos en la nube.

    • format: define las opciones que especifique para describir el formato del archivo de origen. Las opciones de formato characterset, compression, ignoreblanklines, jsonpath, maxdocsize, recorddelimiter, rejectlimit, unpackarray están soportadas para cargar datos JSON. Cualquier otro formato especificado producirá un error. Para obtener información sobre las opciones de formato que puede especificar, consulte Parámetro de formato.

Carga de una matriz de documentos JSON en una recopilación

Descubra cómo cargar una matriz de documentos JSON en una recopilación de su base de datos de IA autónoma mediante el procedimiento DBMS_CLOUD.COPY_COLLECTION.

En este ejemplo se utiliza el archivo JSON fruit_array.json. A continuación, se muestra el contenido del archivo fruit_array.json:

[{"name" : "apple", "count": 20 },
 {"name" : "orange", "count": 42 },
 {"name" : "pear", "count": 10 }]

Procedimiento

  1. Almacene la credencial de Cloud Object Storage mediante el procedimiento DBMS_CLOUD.CREATE_CREDENTIAL. Consulte Creación de credenciales para obtener más información.

  2. Cargue datos en una recopilación mediante el procedimiento DBMS_CLOUD.COPY_DATA. Por ejemplo:

     BEGIN 
       DBMS_CLOUD.COPY_COLLECTION(    
         collection_name => 'fruits',    
         credential_name => 'DEF_CRED_NAME',    
         file_uri_list => 'https://objectstorage.us-ashburn-1.oraclecloud.com/n/namespace-string/b/json/o/fruit_array.json',
         format => '{"recorddelimiter" : "0x''01''", "unpackarrays" : "TRUE", "maxdocsize" : "10240000"}'
       );
     END;
     /
    

    En este ejemplo, se carga un único valor JSON que ocupa todo el archivo. Por lo tanto, no es necesario especificar un delimitador de registro. Para indicar que no hay ningún delimitador de registro, puede utilizar un carácter que no se produzca en el archivo de entrada. Por ejemplo, puede utilizar el valor "0x''01''" porque este carácter no se produce directamente en el texto JSON.

    Cuando el parámetro unpackarrays para el valor de formato se define en TRUE, la matriz de documentos se carga como documentos individuales en lugar de como una matriz completa. Sin embargo, el desempaquetado de elementos de matriz está limitado a un único nivel. Si hay matrices anidadas en los documentos, esas matrices no se desempaquetan.

    Los parámetros son:

    • collection_name: es el nombre de la recopilación de destino.

    • credential_name: es el nombre de la credencial creada en el paso anterior.

    • file_uri_list: es una lista delimitada por comas de los archivos de origen que desea cargar.

      En este ejemplo, file_uri_list es un URI de Swift de Oracle Cloud Infrastructure que especifica el archivo myCollection.json en el bucket mybucket en la región us-phoenix-1. Para obtener información sobre los formatos de URI soportados, consulte Formatos de URI de almacenamiento de objetos en la nube.

    • format: define las opciones que especifique para describir el formato del archivo de origen. Las opciones de formato characterset, compression, ignoreblanklines, jsonpath, maxdocsize, recorddelimiter, rejectlimit, unpackarray están soportadas para cargar datos JSON. Cualquier otro formato especificado producirá un error. Para obtener información sobre las opciones de formato que puede especificar, consulte Parámetro de formato.

    La carga de fruit_array.json con DBMS_CLOUD.COPY_COLLECTION mediante la opción de formato unpackarrays hace que el procedimiento reconozca la matriz de valores en el origen. Por lo tanto, en lugar de cargar los datos como un único documento, como se realizarían por defecto, los datos se cargan en la recopilación fruits con cada valor de la matriz como un único documento.

Copia de datos JSON en una tabla existente

Utilice DBMS_CLOUD.COPY_DATA para cargar datos JSON en la nube en una tabla.

El archivo de origen de este ejemplo es un archivo de datos JSON.

Procedimiento

  1. Almacene las credenciales del almacén de objetos mediante el procedimiento DBMS_CLOUD.CREATE_CREDENTIAL. Por ejemplo:

     SET DEFINE OFF
     BEGIN
       DBMS_CLOUD.CREATE_CREDENTIAL(
         credential_name => 'DEF_CRED_NAME',
         username => 'adb_user@example.com',
         password => 'password'
       );
     END;
     /
    

    Esta operación almacena las credenciales en la base de datos en un formato cifrado. Puede utilizar cualquier nombre para el nombre de credencial. Tenga en cuenta que este paso solo es necesario una vez, a menos que cambien las credenciales del almacén de objetos. Una vez almacenadas las credenciales, puede utilizar el mismo nombre de credencial para todas las cargas de datos.

    Para obtener información detallada sobre los parámetros, consulte Procedimiento CREATE_CREDENTIAL.

  2. Cargue los datos JSON en una tabla existente mediante el procedimiento DBMS_CLOUD.COPY_DATA.

    Por ejemplo:

     CREATE TABLE WEATHER2
         (WEATHER_STATION_ID VARCHAR2(20),
          WEATHER_STATION_NAME VARCHAR2(50));
     /
    
     BEGIN
       DBMS_CLOUD.COPY_DATA(
           table_name      => 'WEATHER2',
           credential_name => 'DEF_CRED_NAME',
           file_uri_list   => 'https://objectstorage.us-phoenix-1.oraclecloud.com/n/namespace-string/b/bucketname/o/jsonfiles*',
           format          =>  JSON_OBJECT('type' value 'json', 'columnpath' value '["$.WEATHER_STATION_ID",
               "$.WEATHER_STATION_NAME"]')
         );
     END;
     /
    

    Los parámetros son:

    • table_name: es el nombre de la tabla de destino.

    • credential_name: es el nombre de la credencial creada en el paso anterior.

    • file_uri_list: es una lista delimitada por comas de los archivos de origen que desea cargar. Puede utilizar comodines en los nombres de archivo en los URI. Se puede utilizar el carácter "*" como comodín para varios caracteres; el carácter "?" se puede utilizar como comodín para un solo carácter.

    • format: para DBMS_CLOUD.COPY_DATA con datos JSON, type es json. Especifique otros valores de formato para definir las opciones para describir el formato del archivo de origen JSON. Consulte Opciones de formato de paquete DBMS_CLOUD para obtener más información.

    In this example, namespace-string is the Oracle Cloud Infrastructure object storage namespace and bucketname is the bucket name. Consulte Descripción de los espacios de nombres de Object Storage para obtener más información.

    Para obtener información detallada sobre los parámetros, consulte Procedimiento COPY_DATA.

Supervisión y solución de problemas de carga de datos

Todas las Operaciones de Carga de Datos realizadas mediante el paquete PL/SQL DBMS_CLOUD se registran en las tablas dba_load_operations y user_load_operations:

Consulte estas tablas para ver información sobre las cargas de datos en curso y completadas. Por ejemplo, al utilizar una sentencia SELECT con un predicado de cláusula WHERE en la columna TYPE, se muestran las operaciones de carga con el tipo COPY:

SELECT table_name, owner_name, type, status, start_time, update_time, logfile_table, badfile_table
  FROM user_load_operations WHERE type = 'COPY';
TABLE_NAME OWNER_NAME  TYPE   STATUS     START_TIME                            UPDATE_TIME                          LOGFILE_TABLE   BADFILE_TABLE
---------- ----------- ------- ---------- ---------------------- --------------------- --------------- ------------- ------------- -------------
CHANNELS   SH          COPY   COMPLETED  04-MAR-21 07.38.30.522711000 AM GMT    04-MAR-21 07.38.30.522711000 AM GMT  COPY$1_LOG     COPY$1_BAD

La columna LOGFILE_TABLE muestra el nombre de la tabla que puede consultar para ver el log de una operación de carga. Por ejemplo, la siguiente consulta muestra el log de la operación de carga:

select * from COPY$21_LOG;

La columna BADFILE_TABLE muestra el nombre de la tabla que puede consultar para ver las filas que han obtenido errores durante la carga. Por ejemplo, la siguiente consulta muestra los registros rechazados para la operación de carga:

select * from COPY$21_BAD;

En función de los errores mostrados en el log y las filas que se muestran en la tabla BADFILE_TABLE especificada, puede corregir el error especificando las opciones del formato correcto en DBMS_CLOUD.COPY_DATA.

Nota: Las tablas LOGFILE_TABLE y BADFILE_TABLE se almacenan por dos días para cada operación del proceso de carga y, posteriormente, se eliminan automáticamente.

dbmscloud-para-objetos-y-archivos.md#GUID-CEC0CA63-B77F-4D64-B70F-1E8476AE3ED6

Consulte Procedimiento DELETE_ALL_OPERATIONS para ver información sobre el borrado de la tabla user_load_operations.

Objetos JSON textuales que representan valores escalares ampliados

Los datos JSON binarios nativos (formato OSSON) amplían el lenguaje JSON agregando tipos escalares, como la fecha, que corresponden a tipos SQL y no forman parte del estándar JSON. Oracle Database también soporta el uso de objetos JSON textuales que representan valores escalares JSON, incluidos dichos valores no estándar.

Cuando crea datos JSON binarios nativos a partir de datos JSON textuales que contienen dichos objetos ampliados, opcionalmente se pueden sustituir con los valores escalares JSON (binarios nativos).

Un ejemplo de un objeto ampliado es {"$numberDecimal":31}. Representa un valor escalar JSON del tipo no estándar número decimal, y cuando se interpreta como tal, se sustituye por un número decimal en formato binario nativo.

Por ejemplo, al utilizar el constructor de tipo de dato JSON, JSON, si utiliza la palabra clave EXTENDED, los objetos extendidos reconocidos en la entrada textual se sustituyen por los valores escalares correspondientes en el resultado binario JSON nativo. Si no incluye la palabra clave EXTENDED, no se produce dicha sustitución; los objetos JSON extendidos textuales simplemente se convierten tal cual en objetos JSON en el formato binario nativo.

En la dirección opuesta, al utilizar la función SQL/JSON json_serialize para serializar datos JSON binarios como datos JSON textuales (VARCHAR2, CLOB o BLOB), puede utilizar la palabra clave EXTENDED para sustituir los valores escalares JSON (binarios nativos) por los objetos JSON ampliados textuales correspondientes.

Nota: Si la base de datos que utiliza es una instancia de Oracle Autonomous AI Database, puede utilizar el procedimiento PL/SQL DBMS_CLOUD.copy_collection para crear una recopilación de documentos JSON a partir de un archivo de datos JSON como el que producen las bases de datos NoSQL comunes, incluida Oracle NoSQL Database.

Si utiliza ejson como valor del parámetro type del procedimiento, los objetos JSON extendidos reconocidos en el archivo de entrada se sustituyen por los valores escalares correspondientes en la recopilación JSON binaria nativa resultante. En la otra dirección, puede utilizar la función json_serialize con la palabra clave EXTENDED para sustituir valores escalares por objetos JSON ampliados en los datos JSON textuales resultantes.

Estos son los dos casos de uso principales para objetos ampliados:

Con fines de intercambio, puede ingerir datos JSON de un archivo producido por bases de datos NoSQL comunes, incluida Oracle NoSQL Database, que convierten objetos ampliados en escalares JSON binarios nativos. En la otra dirección, puede exportar datos JSON binarios nativos como datos de texto, sustituyendo los valores JSON escalares específicos de Oracle por los objetos JSON ampliados de texto correspondientes.

Consejo: como ejemplo de inspección, considere un objeto como {"dob" : "2000-01-02T00:00:00"} como resultado de la serialización de datos JSON nativos. ¿Es "2000-01-02T00:00:00" el resultado de la serialización de un valor binario nativo de tipo fecha, o el valor binario nativo es solo una cadena? El uso de json_serialize con la palabra clave EXTENDED le permite saberlo.

La asignación de campos de objeto ampliados a tipos JSON escalares es, en general, de varios a uno: más de un tipo de objeto JSON ampliado se puede asignar a un valor escalar determinado. Por ejemplo, los objetos JSON ampliados {"$numberDecimal":"31"} y {"$numberLong:"31"} se traducen como el valor 31 del número de tipo escalar de lenguaje JSON, y el método de elemento type() devuelve "number" para cada uno de esos escalares JSON.

El método de elemento type() informa el tipo escalar de lenguaje JSON de su valor de destino (como una cadena JSON). Algunos valores escalares se distinguen internamente, incluso cuando tienen el mismo tipo escalar. Esto generalmente permite que la función json_serialize (con la palabra clave EXTENDED) reconstruya el objeto JSON ampliado original. Estos valores escalares se distinguen internamente mediante diferentes tipos de SQL para implantarlos o etiquetándolos con el tipo de objeto JSON ampliado del que se derivaron.

Cuando json_serialize reconstruye el objeto JSON ampliado original, el resultado no siempre es textualmente idéntico al original, pero siempre es semánticamente equivalente. Por ejemplo, {"$numberDecimal":"31"} y {"$numberDecimal":31} son semánticamente equivalentes, aunque los valores de campo difieran en el tipo (cadena y número). Se traducen al mismo valor interno y cada uno se etiqueta como derivado de un objeto ampliado $numberDecimal (la misma etiqueta). Pero cuando se serializa, el resultado para ambos es {"$numberDecimal":31}. Oracle siempre utiliza el tipo más directamente relevante para el valor de campo, que en este caso es el valor de lenguaje JSON 31, del número de tipo escalar.

En la siguiente tabla se presentan las correspondencias entre los distintos tipos utilizados. Se asigna a través de (1) tipos de objetos extendidos utilizados como entrada, (2) tipos informados por el método de elemento type(), (3) tipos SQL utilizados internamente, (4) tipos estándar de lenguaje JSON utilizados como salida por la función json_serialize y (5) tipos de salida de objetos extendidos por json_serialize cuando se especifica la palabra clave EXTENDED.

Tipo de objeto ampliado (entrada) Tipo escalar JSON de Oracle (informado por type()) Tipo escalar SQL Tipo escalar JSON estándar (salida) Tipo de objeto ampliado (salida)
$numberDouble con el valor un número JSON, una cadena que representa el número o una de estas cadenas: "Infinity", "-Infinity", "Inf", "-Inf", "Nan"(consulte la nota al pie 1) doble BINARY_DOUBLE number $numberDouble con un valor de número JSON o una de estas cadenas: "Inf", "-Inf", "Nan"(consulte la nota al pie 2)
$numberFloat con el mismo valor que para $numberDouble float BINARY_FLOAT number $numberFloat con el mismo valor que para $numberDouble
$numberDecimal con el mismo valor que para $numberDouble number NUMBER number $numberDecimal con el mismo valor que para $numberDouble
$numberInt con un valor entero de 32 bits firmado o una cadena que representa el número number NUMBER number $numberInt con el mismo valor que para $numberDouble
$numberLong con un valor de número JSON o una cadena que represente el número number NUMBER number $numberLong con el mismo valor que para $numberDouble

$binary con el valor uno de estos:

  • una cadena de caracteres base-64
  • Un objeto con los campos base64 y subType, cuyos valores son una cadena de caracteres base-64 y el número 0 (binario arbitrario) o 4 (UUID), respectivamente

Cuando el valor es una cadena de 64 caracteres base, el objeto extendido también puede tener el campo $subtype con el valor 0 o 4, expresado como un entero de un byte (0-255) o una cadena hexadecimal de 2 caracteres que representa dicho entero.

binario BLOB o RAW

Cadena

La conversión es equivalente al uso de la función SQL rawtohex.

Una de las siguientes:
  • $binary con un valor de cadena de 64 caracteres base
  • $rawid con un valor de cadena de 32 caracteres hexadecimales, si la entrada tenía un valor subType de 4 (UUID)
$oid con un valor de cadena de 24 caracteres hexadecimales binario RAW(12)

Cadena

La conversión es equivalente al uso de la función SQL rawtohex.

$rawid con un valor de cadena de 24 caracteres hexadecimales
$rawhex con un valor de cadena con un número par de caracteres hexadecimales binario RAW

Cadena

La conversión es equivalente al uso de la función SQL rawtohex.

$binary con el valor una cadena de caracteres base-64, con relleno derecho con caracteres =
$rawid con un valor de cadena de 24 o 32 caracteres hexadecimales binario RAW

Cadena

La conversión es equivalente al uso de la función SQL rawtohex.

$rawid
$oracleDate con un valor de cadena de fecha ISO 8601 Fecha DATE Cadena $oracleDate con un valor de cadena de fecha ISO 8601
$oracleTimestamp con un valor de cadena de registro de hora ISO 8601 registro de hora TIMESTAMP Cadena $oracleTimestamp con un valor de cadena de registro de hora ISO 8601
$oracleTimestampTZ con un valor de cadena de registro de hora ISO 8601 con un desplazamiento de zona horaria numérico o con Z registro de hora con zona horaria TIMESTAMP WITH TIME ZONE Cadena $oracleTimestampTZ con un valor de cadena de registro de hora ISO 8601 con un desplazamiento de zona horaria numérico o con Z

$date con el valor uno de los siguientes:

  • Recuento de milisegundos enteros desde el 1 de enero de 1990
  • Una cadena de registro de hora ISO 8601
  • Un objeto con el campo numberLong con un valor de número entero de milisegundos desde el 1 de enero de 1990
registro de hora con zona horaria TIMESTAMP WITH TIME ZONE Cadena $oracleTimestampTZ con un valor de cadena de registro de hora ISO 8601 con un desplazamiento de zona horaria numérico o con Z
$intervalDaySecond con un valor de cadena de intervalo ISO 8601 como se ha especificado para la función SQL to_dsinterval intervalo de días INTERVAL DAY TO SECOND Cadena $intervalDaySecond con un valor de cadena de intervalo ISO 8601 como se ha especificado para la función SQL to_dsinterval
$intervalYearMonth con un valor de cadena de intervalo ISO 8601 como se ha especificado para la función SQL to_yminterval añomesIntervalo INTERVAL YEAR TO MONTH Cadena $intervalYearMonth con un valor de cadena de intervalo ISO 8601 como se ha especificado para la función SQL to_yminterval

Dos campos:

  • Campo $vector con el valor una matriz cuyos elementos son números o las cadenas "Nan", "Inf" y "-Inf" (que representan valores no-a-number e infinitos).

  • Campo $vectorElementType con el valor de cadena "float32" o "float64". Estos corresponden a los números IEEE de 32 bits y IEEE de 64 bits, respectivamente.

vector VECTOR matriz de números

Dos campos:

  • Campo $vector con valor una matriz cuyos elementos son números o las cadenas "Nan", "Inf" y "-Inf" (que representan valores no-a-number e infinitos).

  • Campo $vectorElementType con el valor de cadena "float32" o "float64".

Nota al pie 1 Los valores de cadena se interpretan de forma no sensible a mayúsculas/minúsculas. Por ejemplo, "NAN" "nan" y "nAn" se aceptan y equivalen, y de manera similar "INF", "inFinity" y "iNf". Los números infinitamente grandes ("Infinity" o "Inf") y pequeños ("-Infinity" o "-Inf") se aceptan con la palabra completa o la abreviatura.

Nota al pie 2 En la salida, solo se utilizan estos valores de cadena. No hay variantes Infinity de palabra completa ni mayúsculas/minúsculas.

Contenido relacionado