#Bases de datos

0 Seguidores · 73 Publicaciones

La base de datos InterSystems Caché es un archivo donde se guardan todos los datos, scripts de aplicaciones y usuarios, funciones y configuraciones de seguridad. Normalmente, el nombre del archivo es cache.dat.

Documentation.

Artículo Jose-Tomas Salvador · nov 4, 2025 2m read

Hola a todos,

Primero quiero reconocer a @Theo Stolker y @Rupert Young, porque me ayudaron con la solución.

Cuando usáis EnsLib.SQL.Snapshot como una propiedad en el mensaje de respuesta para devolver datos de Snapshot (por ejemplo, desde una Business Operation a un Business Process), los datos del Snapshot no se limpian con la tarea/servicio de depuración de mensajes.

Class ResponseMessage Extends Ens.Response

{

    Property SnapshotProp As EnsLib.SQL.Snapshot;

}

0
0 15
InterSystems Official Mario Sanchez Macias · jun 12, 2025

InterSystems ha publicado nuevas actualizaciones puntuales para resolver un defecto que afecta a las versiones anteriores más recientes de 2025.1.0, 2024.1.4, 2023.1.6 y 2022.1.7, en las siguientes líneas de productos compatibles:

  • InterSystems IRIS
  • InterSystems IRIS for Health
  • HealthShare Health Connect

Este problema podría provocar errores inesperados de tipo <PROTECT> o anomalías de acceso al utilizar funciones como:

  • Namespaces implícitos
  • Acceso mixto de solo lectura/lectura-escritura a bases de datos
  • Páginas del Portal de Administración para listar rutinas y globals

Síntomas incluidos:

0
0 32
Artículo Alberto Fuentes · jun 11, 2025 3m read

Si estáis migrando de Oracle a InterSystems IRIS, como muchos de mis clientes, podríais encontraros con patrones SQL específicos de Oracle que necesitan ser traducidos.

Tomad este ejemplo:

SELECT (TO_DATE('2023-05-12','YYYY-MM-DD') - LEVEL + 1) AS gap_date
FROM dual
CONNECT BY LEVEL <= (TO_DATE('2023-05-12','YYYY-MM-DD') - TO_DATE('2023-05-02','YYYY-MM-DD') + 1);

En Oracle:

0
0 35
Artículo Daniela Echenique · jun 2, 2025 5m read

Objetivo

Explorar un enfoque de validación estructural adaptativa para mantener estables las pruebas automatizadas frente a cambios en la estructura del JSON generado a partir de datos expuestos desde una base de datos InterSystems IRIS.

Se busca minimizar falsos positivos ante cambios no críticos, siempre que la respuesta siga siendo coherente con la estructura real de la base. En estos casos, la ejecución continúa y se deja registro del evento en un archivo estructurado, que permite trazabilidad y análisis evolutivo.

Además de validar la idea central, este proyecto abre camino a posibles extensiones técnicas, como la validación dinámica contra el esquema real de la base, el seguimiento histórico de cambios estructurales, y el uso de técnicas de machine learning para distinguir entre ajustes legítimos y errores funcionales, comprendiendo no solo el cambio, sino también su intención.

Desafíos actuales

Diseñar una Arquitectura de Automatización de Pruebas sólida es fundamental para garantizar la calidad en entornos donde las APIs son el nexo entre sistemas.

Un desafío que enfrentan los equipos de testing es la fragilidad de las pruebas automatizadas ante cambios menores en la estructura del JSON que devuelven los endpoints.

Estos cambios, aunque no afecten el comportamiento funcional de la API, pueden romper los tests al no coincidir con las estructuras esperadas, lo que genera:

  • Tiempo y esfuerzo en mantenimiento.
  • Interrupciones de pipelines CI/CD.
  • Dificultad para identificar defectos reales.
  • Desconfianza en la suite de regresión.

La fragilidad estructural no es un detalle técnico: es un obstáculo estratégico, el avance es hacia resiliencia estructural, con pruebas que evolucionen con el sistema, no que se quiebren con él.

Enfoque propuesto

Frente a estos desafíos, se propone una estrategia de validación estructural adaptativa, que incorpora una capa intermedia entre la respuesta de la API y la lógica del test, capaz de detectar diferencias estructurales y responder de manera flexible según el tipo de cambio.

Cuando se detecta una discrepancia entre el esquema recibido y el esperado, el sistema debería poder:

  1. Identificar la diferencia estructural (claves nuevas o faltantes).
  2. Validar si el cambio es coherente con la evolución del modelo de datos, utilizando una fuente confiable como la base de datos real.
  3. Clasificar según reglas predefinidas
    • Si es aceptable, continuar la ejecución y registrar un warning.
    • Si es crítico, detener la prueba.
  4. Dejar registro del evento en un archivo .ndjson, lo que habilita posibles evoluciones:
    • Dashboards de trazabilidad.
    • Entrenamiento de modelos para anticipar cambios.
    • Pull requests automáticos para adaptar tests validados.

MVP

Se desarrolló un MVP funcional que implementa este enfoque y permite validar la adaptación estructural ante cambios en la respuesta JSON generada desde InterSystems IRIS.

Componentes principales

  • Base de datos: InterSystems IRIS Community Edition ejecutada en contenedor Docker.
  • Exposición de datos: Clase ObjectScript que expone información como JSON.
  • Cliente de validacion: Python 3.10 con el módulo irisnative.
  • Pruebas: Pytest
  • Lógica de validación:
    • Modo estricto: falla ante cualquier discrepancia.
    • Modo adaptativo: acepta campos adicionales no esperados y emite un warning si no faltan campos críticos.
  • Trazabilidad: eventos estructurales detectados se almacenan en formato .ndjson.
  • IDE: Visual Studio Code (Ubuntu)
  • Repositorio: https://github.com/danielaeche/resilient-api-test.git

Limitación del MVP

La validación estructural no se hace contra el esquema activo de la base en tiempo real, sino contra una clase intermediaria. Esto significa que si la base cambia, se debe actualizar manualmente la clase.

Escalar esta solución requiere usar la Python DB API de InterSystems para consultar directamente la estructura actual de la base de datos, permitiendo una validación verdaderamente dinámica y autónoma, sin necesidad de actualizar manualmente la clase que expone los datos.

Ejecución de prueba

  1. Estructura esperada: Se entrega este JSON desde IRIS:
[
  { "id": 1, "name": "Virginia", "age": 38 },
  { "id": 2, "name": "Daniela", "age": 17 }
]
  • Test estricto (test_api_strict.py) : ✅ Resultado: la prueba pasa.
  1. Cambio estructural simulado : Se agrega un campo nuevo (coverage):
[
  { "id": 1, "name": "Virginia", "age": 38, "coverage": "premium" },
  { "id": 2, "name": "Daniela", "age": 17, "coverage": "basic" }
]
  • Test estricto: 🛑 Resultado: la prueba falla por estructura inesperada.
  • Test adaptativo (test_api_adaptive.py) : ⚠️ Resultado: la prueba pasa con warning[WARNING] Nuevos campos detectados: {'coverage'}

Conclusión

Este artículo propone un enfoque frente a un problema: cómo evitar que pequeños cambios estructurales rompan pruebas que no deberían fallar.

El MVP demuestra que es posible incorporar tolerancia estructural sin perder capacidad de detección. La solución es escalable y propone una evolución hacia modelos donde la calidad de la prueba no dependa exclusivamente del script, sino que sea respaldada por una capa de validación contextual que no solo detecta un cambio, sino que evalúa su impacto antes de decidir.

También plantea una dirección de valor: mecanismos resilientes que puedan integrarse de forma nativa en herramientas como las que InterSystems desarrolla, como parte de una estrategia de calidad continua para entornos donde el cambio es constante.

El futuro de la automatización no está en tener más scripts que se ejecuten una y otra vez, sino en construir pruebas capaces de entender por qué fallan los sistemas, incluso cuando parecen funcionar.

Automatizar con resiliencia es evitar que el esfuerzo se consuma en lo repetible, y entregarle a quienes diseñan la calidad el espacio para identificar riesgos emergentes, desafiar lo previsto y diseñar validaciones con sentido estratégico.

0
0 66
Artículo Ricardo Paiva · abr 22, 2025 4m read

Migrar desde Oracle, MSSQL u otros sistemas de bases de datos puramente relacionales a un sistema multimodelo como InterSystems IRIS es una decisión estratégica que requiere una planificación y ejecución cuidadosas. Aunque esta transición ofrece beneficios significativos, como un mejor rendimiento, escalabilidad y soporte para arquitecturas modernas, también conlleva desafíos. En este artículo destacaré algunas de las consideraciones relacionadas con la codificación para asegurar una migración exitosa. Dejaré fuera del alcance de este artículo todo lo relacionado con la migración real de estructuras y datos.

Primero, cuando estáis considerando migrar a un sistema de base de datos diferente, necesitáis comprender vuestra lógica de negocio, ya sea del lado de la aplicación (servidor de aplicaciones) o del servidor de bases de datos. Básicamente, ¿dónde tenéis vuestras sentencias SQL que potencialmente tendréis que reescribir?

0
0 54
InterSystems Official Mario Sanchez Macias · abr 7, 2025

Resumen de alertas

ID de la Alerta Productos y versiones afectadas Requisitos explícitos
DP-439207 InterSystems IRIS® data platform 2024.3 (AIX) Instalaciones AIX Uso del procesamiento JSON y conjuntos de caracteres Unicode no-Latin-1 
DP-439280 InterSystems IRIS 2024.3 (containers with IntegratedML) Contenedores integradosML usando TensorFlow

Detalle de las alertas

DP-439207 - Problema de análisis JSON Unicode en AIX

0
0 45
Artículo Luis Angel Pérez Ramos · feb 20, 2025 3m read

Es posible que hayáis notado que, para configurar un mirror en InterSystems IRIS for Health™ y HealthShare® Health Connect, hay un requisito especial. En este artículo, quiero guiaros paso a paso por el proceso.

Esto supone que ya habéis configurado el segundo miembro de conmutación por error y habéis confirmado un estado exitoso de dicho miembro en el monitor del mirror:

Paso 1: Activad el usuario HS_Services (en el servidor de respaldo y en el principal).

2
1 91
Artículo Ricardo Paiva · ene 29, 2025 4m read

No estoy seguro de que haya muchos que se conecten a MS SQL para ejecutar consultas, procedimientos almacenados, etc., pero nuestro sistema de salud tiene muchas bases de datos diferentes basadas en MS SQL que usamos en el entorno de interoperabilidad por diversas razones.

Con el impulso de movernos de on-premises a la nube, nos encontramos con algunas dificultades con nuestras conexiones SQL Gateway y cómo configurarlas para usar Microsoft Entra para la autenticación de Active Directory.

0
0 77
Artículo Luis Angel Pérez Ramos · oct 29, 2024 1m read

Preguntas frecuentes de InterSystems 

El número máximo de namespaces que se pueden crear en una instancia es de 2047. Sin embargo, para utilizar un gran número de namespaces, necesitaréis configurar la memoria adecuadamente.

El número máximo de bases de datos (incluidas las bases de datos remotas) que se pueden crear en una instancia es de 15,998. Dependiendo del tipo de licencia, puede haber restricciones sobre la cantidad que se puede crear. Para más detalles, consultad el siguiente documento.

Configuración de la Base de Datos [IRIS]
Configuración de la Base de Datos

0
0 70
Artículo Andrii Mishchenko · oct 16, 2024 5m read

En este artículo, nos adentraremos en el funcionamiento de una aplicación publicada en OpenExchange llamada db-management-tool que sirve como herramienta de gestión de bases de datos, explorando la arquitectura y las tecnologías que la sustentan. Comprenderemos cómo funciona la aplicación para daros una visión de su diseño, cómo gestiona bases de datos, tablas y cómo la API interactúa con los datos.

0
0 62
Artículo Jose-Tomas Salvador · oct 10, 2024 3m read

Rúbrica de preguntas frecuentes de InterSystems

Podéis comprobar el espacio libre en disco en cualquier momento usando la clase de utilidad del sistema: SYS.Database y la consulta: FreeSpace.

Aquí tenéis cómo probarlo en el terminal de IRIS (id al espacio de nombres %SYS y luego ejecutadlo):

zn"%SYS"set stmt=##class(%SQL.Statement).%New()
set st=stmt.%PrepareClassQuery("SYS.Database","FreeSpace")
set rset=stmt.%Execute()
do rset.%Display()

El resultado de salida es el siguiente:

0
0 69
Artículo Luis Angel Pérez Ramos · oct 7, 2024 3m read

sql-embedding cover

InterSystems IRIS 2024 ha introducido recientemente los tipos de vectores. Esta adición os permite trabajar con búsquedas por vectores, habilitando búsquedas eficientes de similitud, agrupación y una amplia gama de otras aplicaciones. En este artículo, profundizaremos en las complejidades de los tipos de vectores, exploraremos sus aplicaciones y os proporcionaremos ejemplos prácticos para guiar vuestra implementación.

En su esencia, un tipo de vector es una colección estructurada de valores numéricos dispuestos en un orden predefinido. Estos valores sirven para representar diferentes atributos, características o rasgos de un objeto.

SQL-Embedding: Una Herramienta Versátil

Para simplificar la creación y el uso de embeddings para búsquedas por vectores dentro de las consultas SQL, hemos introducido la herramienta SQL-Embedding. Esta función os permite aprovechar una amplia gama de modelos de embedding directamente dentro de vuestras bases de datos SQL, adaptados a vuestras necesidades específicas.

Ejemplo Práctico: Búsqueda de Similitud

Consideremos un escenario donde queréis determinar la similitud entre dos textos utilizando el modelo fastembed y SQL-Embedding. La siguiente consulta SQL muestra cómo se puede lograr esto:

SELECT
 VECTOR_DOT_PRODUCT(
 embFastEmbedModel1,
 dc.embedding('my text', 'fastembed/BAAI/bge-small-en-v1.5')
 ) AS "Similarity between 'my text' and itself",
 VECTOR_DOT_PRODUCT(
 embFastEmbedModel1,
 dc.embedding('lorem ipsum', 'fastembed/BAAI/bge-small-en-v1.5')
 ) AS "Similarity between 'my text' and 'lorem ipsum'"
FROM testvector;

Almacenamiento en caché

Uno de los beneficios significativos de usar SQL-Embedding en InterSystems IRIS es su capacidad para almacenar en caché las solicitudes de embedding repetidas. Este mecanismo de caché mejora considerablemente el rendimiento al reducir la sobrecarga computacional asociada con la generación de embeddings para entradas idénticas o similares.

Cómo Funciona la Caché

Cuando ejecutáis una consulta SQL-Embedding, InterSystems IRIS comprueba si el embedding para la entrada dada ya ha sido almacenado en caché. Si existe, el embedding en caché se recupera y se utiliza directamente, eliminando la necesidad de volver a generarlo. Esto es particularmente ventajoso en escenarios donde los mismos embeddings se solicitan con frecuencia, como en sistemas de recomendación o aplicaciones de búsqueda.

Beneficios de la caché

  • Reducción de la Latencia: Al evitar cálculos redundantes de embeddings, la caché puede reducir significativamente los tiempos de respuesta de las consultas.
  • Mejora de la Escalabilidad: La caché puede gestionar cargas de trabajo incrementadas de manera más eficiente, ya que reduce la presión sobre los modelos de embedding subyacentes.
  • Optimización del Uso de Recursos: La caché ayuda a conservar recursos computacionales al evitar cálculos innecesarios.

En conclusión, la introducción de los tipos de vectores en InterSystems IRIS presenta una herramienta robusta para trabajar con representaciones numéricas de objetos. Al aprovechar las búsquedas de similitud, SQL-Embedding y diversas aplicaciones, los desarrolladores pueden desbloquear nuevas posibilidades y mejorar sus soluciones basadas en datos.

Si encontrasteis nuestra aplicación interesante y os ha aportado algún conocimiento, votad por sql-embeddings y ayudadnos en este viaje.

0
0 89
Anuncio Sergio Farago · sep 16, 2024

Hola Comunidad,

El equipo de certificación de InterSystems Learning Services se complace en anunciar el lanzamiento de nuestro nuevo examen InterSystems IRIS SQL Specialist. Ahora está disponible para su compra y programación en el catálogo de exámenes de InterSystems. Los candidatos potenciales pueden revisar los temas del examen y las preguntas de práctica para orientarse sobre los enfoques y el contenido de las preguntas del examen. Los candidatos que aprueben con éxito el examen recibirán una insignia de certificación digital que puede compartirse en redes sociales como LinkedIn.

0
0 65
Pregunta Luis Angel Pérez Ramos · ago 12, 2024

¡Hola estimados miembros de la comunidad!

En esta ocasión no vengo con ningún artículo sino con un problema que me he encontrado al definir una conexión a una tabla ubicada en una base de datos externa en MySQL.

Estoy haciendo algunas pruebas con la funcionalidad de las Foreign Tables incluidas en IRIS que permite incluir tablas externas en modo consulta como si fueran propias de IRIS y he visto que funciona perfectamente con una tabla ubicada en PostgreSQL, el problema ha sido al intentar hacer lo mismo con una tabla de MySQL, he seguido los siguiente pasos como indica la documentación:

1
0 66
Artículo Luis Angel Pérez Ramos · jul 25, 2024 7m read

Visualización gráfica de tablas

Aquí documentaremos cómo podéis obtener los resultados de vuestra Data Collection para que se muestren gráficamente. La salida de vuestro proyecto se verá así:

image

Tened en cuenta que estoy trabajando en una máquina local. Si vosotros estáis haciendo esto en un servidor, aseguraos de usar la dirección IP correcta.

Primero, importaremos las tres clases que vamos a necesitar (tened en cuenta que las editaremos más tarde):

Podéis tomar el xml e importarlo en vuestro sistema.

La especificación en realidad creará la clase de despacho y la plantilla de implementación. Si estáis interesados en aprender más sobre este proceso, echad un vistazo al excelente artículo de mi colega, Eduard Lebedyuk.

Configurar las APIs

Tened en cuenta que en esta demostración utilizaremos Autorización Básica. También asumimos que ya hay datos en las tablas Sample_DBExpansion_Data.DBAnalysisInfo y Sample_DBExpansion_Data.GlobalAnalysisInfo. Si no los hay, volved a la Colección de Datos y obtened algunos datos.

  1. Primero, vamos a crear un endpoint que nos dará acceso a nuestros datos: image

Rellenad los mismos nombres a menos que planeéis personalizar el código para la aplicación React por vuestra cuenta.

  1. Haced clic en guardar y probemos nuestras APIs. Abrid Postman y enviad la siguiente solicitud (aseguraos de usar la autorización adecuada): image

Nuestra salida debería verse algo así:

{
    "data": [
        {
            "Name": "c:\\intersystems\\irishealth\\mgr\\training\\",
            "Date": "2023-04-30 15:23:58",
            "DBUsedSize": 2010,
            "DBAllocSize": 2060
        },
        {
            "Name": "c:\\intersystems\\irishealth\\mgr\\training\\",
            "Date": "2023-05-01 09:01:42",
            "DBUsedSize": 2010,
            "DBAllocSize": 2060
        },
        {
            "Name": "c:\\intersystems\\irishealth\\mgr\\training\\",
            "Date": "2023-05-03 13:57:40",
            "DBUsedSize": 150,
            "DBAllocSize": 2060
        }
    ]
}

A continuación, enviemos una solicitud GET a http://localhost:52776/Sample/dbAnalysis/globals/all. Comprobad que vuestra respuesta os da una lista de globals cuya información se vea así: (date cuenta de que el nombre se establecerá por defecto al nombre de la clase si el global tiene uno)

        {
            "Name": "someName.cls",
            "UsedMB": 4.2,
            "AllocatedMB": 5.7
        }

Ahora probemos con un global específico, digamos "Errors". Enviad una solicitud GET a http://localhost:52776/Sample/dbAnalysis/global/Errors. Comprobad que vuestra salida es similar a esta:

        {
            "Name": "ERRORS",
            "UsedMB": 0.4,
            "Date": "2023-04-30 15:23:58",
            "AllocatedMB": 0.45
        },
        {
            "Name": "ERRORS",
            "UsedMB": 0.43,
            "Date": "2023-05-01 09:01:42",
            "AllocatedMB": 0.49
        },
        {
            "Name": "ERRORS",
            "UsedMB": 0.1,
            "Date": "2023-05-03 13:57:40",
            "AllocatedMB": 0.13
        }

Y finalmente, enviemos una solicitud GET a http://localhost:52776/Sample/dbAnalysis/globals/table/1000. Esto nos dará el crecimiento de globals, cuya salida canalizaremos en la sección 'Datos Tabulados' de la aplicación React. Tened en cuenta que los 1000 días solo se refieren a cuántos días hacia atrás deberíamos ir. Esto depende completamente de vosotros. Sentíos libres de personalizar esto en el archivo src/components/TableInputBar.js. Buscad la línea <Table timeBack={1000} numGlobals={searchInput}/>. Poned aquí la cantidad de días que deseéis ver hacia atrás en la aplicación React.

Deberíais obtener una respuesta que sea una lista de objetos como esta:

       {
            "Name": "nameOfGlobal",
            "ClassName": "AriTesting.DemoTableElad.cls",
            "OldMB": 0.14,
            "OldDate": "2023-04-30 15:23:58",
            "NewMB": 0.14,
            "NewDate": "2023-05-03 13:57:40",
            "Change": "0"
        }

Dado que todas vuestras solicitudes estaban en orden, ahora podemos crear nuestra aplicación web. Recordad que si no estáis recibiendo las respuestas que esperabais, deberíais retroceder y verificar qué está mal antes de avanzar y crear la aplicación que depende de ellas.

Pasos para crear la aplicación web

  1. Lo primero que haréis es crear una aplicación React genérica. Recordad que necesitaréis tener Node (por lo menos la versión 14) instalado en la máquina local de desarrollo, pero no será necesario en el servidor. Si no lo tenéis instalado, hacedlo aquí. Si no estáis seguros de si lo tenéis instalado, podéis ejecutar este comando desde vuestro terminal:
node --version
  1. Ahora instalaremos una aplicación React genérica y cambiaremos las partes que necesitemos. Esto es tan simple como ejecutar:
npx create-react-app data-collection-graphs
  1. Si es la primera vez que hacéis esto, puede tomar unos minutos. Una vez que esté hecho, tendremos una carpeta que se verá de la siguiente manera: image

  2. Vuestra aplicación React genérica (que personalizaremos) ahora está funcionando. Comprobadlo:

npm start

Deberíais ser redirigidos automáticamente a una pestaña que os muestra lo siguiente (si no, id a http://localhost:3000/): image

  1. Ahora personalicemos según nuestras necesidades. Detened vuestra aplicación desde el terminal con ^C. Descargad la carpeta src desde este repositorio y reemplazad la que se encuentra en vuestro directorio, la cual fue creada automáticamente por nuestros comandos anteriores. Desde el directorio data-collection-graphs, instalad chart.js y react-chartjs-2 de la siguiente manera:
npm install --save chart.js
npm install --save react-chartjs-2

En la carpeta src/components se encuentra el código JavaScript que llama a los puntos finales de la API para obtener datos para el gráfico. Si vuestro servidor no está ejecutándose en localhost:80, entonces debéis cambiar la variable baseUrl (y la autorización básica codificada en base64, si ese es el método de autorización que habéis elegido) en los archivos BarChart.js, DBChart.js, SingleGlobalHistoryChart.js, y TableData.js.

  1. Utilizad npm start para cargar vuestra página, y ahora deberíais obtener la página con vuestros análisis de base de datos.

Nota: Es posible que veáis una página en blanco y, al abrir las herramientas de desarrollo web, veáis un error como este: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:52775/Sample/dbAnalysis/globals/all. (Reason: CORS preflight response did not succeed). Status code: 404.

Si este es el caso, añadid el siguiente método de clase en vuestro archivo generado Sample.DBExpansion.Util.REST.disp.cls:

ClassMethod OnHandleCorsRequest(pUrl As %String) As %Status
{
     //s ^CORS("OnHandleCorsRequest")="Handled"
     #; Get the origin
     Set tOrigin=$Get(%request.CgiEnvs("HTTP_ORIGIN"))
     #; Allow requested origin
     Do ..SetResponseHeaderIfEmpty("Access-Control-Allow-Origin",tOrigin)
     #; Set allow credentials to be true
     Do ..SetResponseHeaderIfEmpty("Access-Control-Allow-Credentials","true")
     #; Allow requested headers
     Set tHeaders=$Get(%request.CgiEnvs("HTTP_ACCESS_CONTROL_REQUEST_HEADERS"))
     Do ..SetResponseHeaderIfEmpty("Access-Control-Allow-Headers",tHeaders)
     #; Allow requested method
     Set tMethod=$Get(%request.CgiEnvs("HTTP_ACCESS_CONTROL_REQUEST_METHOD"))
     Do ..SetResponseHeaderIfEmpty("Access-Control-Allow-Method",tMethod)
     Return $$$OK
}

Como no estamos utilizando autenticación delegada aquí, la solicitud será realizada por el usuario CSPSystem. Esto significa que debemos otorgar al usuario CSPSystem los roles apropiados para las consultas que estamos realizando. Podéis leer más al respecto aquí (o simplemente dar al usuario CSPSystem el rol necesario para leer datos de vuestro espacio de nombres/base de datos).

Con Cross-Origin Resource Sharing (CORS) configurado, después de refrescar la página, deberíais ver cómo los gráficos comienzan a poblarse y verse como lo que vemos en la parte superior de esta página.

Sentíos libres de explorar el código y hacer mejoras o personalizaciones que se adapten mejor a vuestra organización.

Si tenéis alguna sugerencia sobre cómo puedo mejorar esto desde nuestro lado, por favor hacedmelo saber también.

Continuad con el repositorio de análisis de datos aquí.

0
0 112
Artículo Alberto Fuentes · jun 21, 2024 10m read

Spoilers: Realizar verificaciones diarias de integridad (IntegrityChecks) no sólo es una práctica recomendada, sino que también proporciona una instantánea de los tamaños y densidad de los globales.
Actualización 2024-04-16:  A partir de IRIS 2024.1, muchas de las utilidades que comentaremos a continuación ofrecen ahora un modo para estimar el tamaño con un error <2% en promedio, con mejoras significativas en el rendimiento y los requisitos de E/S. Aunque sigo recomendando realizar verificaciones regulares de integridad, hay situaciones donde se necesitan respuestas más urgentes.

0
0 198
Artículo Luis Angel Pérez Ramos · jun 18, 2024 6m read

Recogida de datos

Esta es una guía de instrucciones paso a paso para crear una tarea que recopile datos sobre la base de datos InterSystems y los globales que contiene (como se ve en la Open Exchange App asociada - encontrad todo el código asociado allí).

Descargo de responsabilidad: Este software es meramente para fines de PRUEBA/DEMO. Este código no está soportado por InterSystems como parte de ningún producto. InterSystems lo suministra como herramienta de demostración/prueba para un producto y versión específicos. El usuario o cliente es totalmente responsable del mantenimiento y prueba de este software después de la entrega, e InterSystems no tendrá ninguna responsabilidad por errores o mal uso de este código.

  1. En primer lugar, importad el archivo «DataCollection.xml» a través del portal de gestión y aseguraos de que no hay errores. Si los hubiera, podría tratarse de una cuestión de versiones. Poneos en contacto con Ari Glikman en ari.glikman@intersystems.com para que os ayude a obtener una versión adecuada para vosotros. Además, aseguraos de que importáis los datos en el espacio de nombres cuyos datos internos queréis recopilar para su posterior inspección.

  2. Una vez finalizada la importación, deberíais ver el paquete Sample/Muestra con varios subpaquetes también

image

Si un paquete de Sample/Muestra ya está presente en vuestro servidor, entonces todavía debéis ver los nuevos subpaquetes junto con cualquier otra carpeta que estaban previamente allí.

  1. Ahora es el momento de ejecutar las pruebas unitarias para asegurarse de que todo funciona correctamente.

a. Cread una carpeta llamada Unit Tests que pueda ser leída por vuestro Terminal InterSystems, por ejemplo, como yo tengo una instalación local, haré una carpeta en mi disco C.

FolderStructure

b. En esta carpeta exportaremos ahora la clase Sample.DBExpansion.Test.CaptureTest como un fichero xml.

image

c. En el terminal estableced el global ^UnitTestRoot = “<< carpeta en la que está Unit Tests>>”.. Según el ejemplo anterior, sería (tened en cuenta que debe estar en el mismo espacio de nombres donde importasteis el paquete) C:\ (a tener en cuenta que no es “C:\Unit Tests” !)

set ^UnitTestRoot = "C:\"

d. Por último, ejecutamos las pruebas unitarias. Hacedlo ejecutando la siguiente línea de código desde el terminal:

do ##class(Sample.DBExpansion.Test.TestManager).RunTest("Unit Tests", "/noload/nodelete")

Esencialmente le estamos diciendo al programa que ejecute todas las pruebas que se encuentran en la carpeta C:\Unit Tests. Por el momento sólo tenemos un archivo allí, el creado en 3.b.

La salida debería ser la siguiente

UnitTestOutput

Si las pruebas unitarias no pasan, entonces el programa no está listo para ejecutarse. No continuéis con los siguientes pasos hasta que obtengáis una salida que diga que todas las pruebas han pasado.

  1. ¡Felicidades! Ahora es el momento de construir la tarea. Para ello

a. Abrid el portal de gestión e id a Operación del Sistema > Gestor de Tareas > Nueva Tarea

*Tened en cuenta que vuestro usuario debe tener acceso al espacio de nombres %SYS, de lo contrario la tarea se ejecutará pero no recopilará ningún dato.

NewTask

Ahora se os darán varios campos para rellenar en cuanto a la tarea que queréis crear. Deberéis elegir el espacio de nombres en el que importasteis el paquete y dar un nombre a la tarea. Deberéis dar una descripción para futuras referencias. Lo ideal es dejar la casilla de verificación rápida sin marcar, esto significa que la tarea se ejecutará más lentamente pero recogerá datos más completos. Si tarda demasiado en ejecutarse (depende de lo grande que sea la base de datos y sus globales) entonces quizás sea mejor marcar aquí y optar por una tarea más rápida. HowManyGlobals indica cuántos datos globales deben recopilarse: -1 indica todos los datos globales y es la opción recomendada. Seleccionad Siguiente, elegid con qué frecuencia debe ejecutarse la tarea y pulsad Finalizar.

Recording 2023-05-10 at 7 10 32 PM

b. Aparecerá el Calendario de Tareas, donde podréis ver cuándo están programadas todas las tareas, incluida la recién creada. Si además deseáis ejecutarla ahora, seleccionad Ejecutar en la parte derecha.

Seleccionad el Historial de Tareas para asegurar que se ha creado correctamente. Después de ejecutar la tarea deberíais ver que también se ha ejecutado correctamente. En caso contrario, aparecerá un error.

Esta tarea creará dos tablas:

Sample_DBExpansion_Data.DBAnalysisInfo.

Esta tabla va a almacenar datos sobre la propia base de datos. Nos referimos a esto como «metadatos». La información que almacena se puede ver en la siguiente imagen. La Bandera Rápida indicará la selección elegida en 4.a.

DBTable

Sample_DBExpansion_Data.GlobalAnalysisInfo

Esto contendrá la información relativa a los globales en la base de datos. Observad que si hay un nombre de clase asociado al global, lo veremos aquí junto con su tamaño. Por último, observad que el campo MetaDataID corresponde al campo ID de la tabla Sample_DBExpansion_Data.DBAnalysisInfo. Esto quiere decir que en el momento en que se capturó la información de la base de datos, se capturó su correspondiente información global y comparten este número identificativo (son los globales de la base de datos en ese momento). Es una forma de ver cómo evolucionan en el tiempo los globales de una base de datos y la propia base de datos.

GLOBALTABLE

  1. A continuación, la interfaz de usuario es un poco más bonita.

Recording_2023-05-23_at_2_03_45_PM_AdobeExpress

Muestra información sobre el global y la base de datos que aparecen en la tabla de una manera más digerible. Hay 3 gráficos: uno que muestra la historia de los datos, el segundo que muestra los tamaños históricos de un global elegido, ya sea a través del desplegable o de una búsqueda, y por último hay una visión general de todos los tamaños globales. En la parte inferior hay una tabla donde se introduce cuántos globales se quieren mostrar y los presenta ordenados por tamaño. La columna %Change está resaltada en amarillo para un cambio mínimo de tamaño, en verde para una disminución de tamaño y en rojo para un aumento significativo de tamaño.

Encontraréis instrucciones paso a paso sobre cómo configurarlo aquí.

Si no os interesan los gráficos, continuad con el análisis de datos aquí.

Docker

Prerrequisitos

Aseguraos de tener git y Docker desktop instalados.

Instalación

Clone/git pull el repo en cualquier directorio local

$ git clone https://github.com/rcemper/PR_DataCollection.git
$ docker compose up -d && docker compose logs -f

Inicio del contenedor
cread el directorio apropiado «/home/irisowner/dev/Unit Tests»
estableced ^UnitTestRoot = «/home/irisowner/dev/»

Para abrir el Terminal IRIS:

$ docker-compose exec iris iris session iris
USER>

o utilizando WebTerminal.
http://localhost:42773/terminal/

Para acceder al portal de gestión del sistema IRIS
http://localhost:42773/csp/sys/UtilHome.csp

Para acceder a UnitTestPortal http://localhost:42773/csp/sys/%25UnitTest.Portal.Indices.cls?$NAMESPACE=USUARIO

0
0 199
Anuncio Sergio Farago · mayo 27, 2024

¡Hola desarrolladores!

Os invitamos a un nuevo webinar en español: "Base de datos de Vectores y RAG: Aplicaciones IA generativa sobre tus datos", el jueves 6 de junio, a las 4:00 PM (CEST).

 

El webinar está dirigido a desarrolladores, integradores y arquitectos de aplicaciones.

Durante el webinar, aprenderemos qué son y para qué sirven las bases de datos vectoriales y cómo podemos utilizarlas para construir aplicaciones de IA generativa sobre nuestros propios datos utilizando planteamientos RAG (Retrieval Augmented Generation).

¡Os esperamos!

0
0 206
Artículo Daniel Aguilar · mayo 19, 2024 4m read

   

📜 Santa Tecla, versículo 8: "Extiende tu ratón sobre la pantalla, y el mar de datos abrirá un sendero delante de ti!!"

Hola comunidad, lo primero perdón si a alguien le ha ofendido la blasfemia 😔 

¿Alguna vez has pensado que sería interesante tener separado el código fuente de los datos de la base de datos?. Quizás te gustaría poder hacer copias de seguridad de tu código sin tener que copiar gigas de información de los datos de tus clientes.

A continuación os explico los pasos para separar en 2 bases de datos distintas vuestro mar formado por el código fuente y los datos de un namespace.

0
1 199
Artículo Luis Angel Pérez Ramos · abr 2, 2024 8m read

En este artículo, cubriremos los siguientes temas:

  • ¿Qué es Kubernetes?
  • Componentes principales de Kubernetes (K8)


¿Qué es Kubernetes?

Kubernetes es un marco de orquestación de contenedores de código abierto desarrollado por Google. En esencia, controla la velocidad de los contenedores y ayuda a gestionar aplicaciones formadas de varios contenedores. Además, le permite operarlos en diferentes entornos, por ejemplo, máquinas físicas, máquinas virtuales, entornos de nube o incluso entornos de implementación híbridos.


¿Qué problemas soluciona?

0
0 131
Artículo Daniel Aguilar · feb 4, 2024 7m read

        

Hola, comunidad!

He estado trasteando últimamente con Flutter y he desarrollado algunas aplicaciones que usan Firebase como base de datos. Cuando descubría que existía una librería de Firebase para Python, tuve el impulso de que debía crear algo con InterSystems IRIS que pudiese hacer que se comunicase con Firebase Database RealTime y realizar operaciones CRUD. Justo entonces me encontré con esta idea de Evgeny en el portal de ideas:

https://ideas.intersystems.com/ideas/DP-I-146 

Y me puse manos a la obra!
 

0
0 251
Pregunta Kurro Lopez · ene 31, 2024

Hola comunidad.

Hemos desarrollado una nueva versión de una producción, todo el código es nuevo y ha cambiado BP. Esta aplicación carga información para algunas marcas y la almacena en la base de datos.

El cliente quiere implementar los cambios solo para algunas marcas porque quiere verificar las marcas pequeñas antes de implementarlos para todas las marcas.

Mi propuesta es crear un nuevo namespace, con el nuevo código, y deshabilitar todas las marcas excepto la marca que quiere probar.

Me pregunto cuál es la mejor manera de clonar el namespace.

6
0 201
Artículo Luis Angel Pérez Ramos · dic 29, 2023 6m read

Parece que fue ayer cuando hicimos un pequeño proyecto en Java para probar el rendimiento de IRIS, PostgreSQL y MySQL (podéis revisar el artículo que escribimos allá por Junio al final de este artículo). Si recordáis IRIS se mostró superior a PostgreSQL y claramente superior a MySQL en las inserciones, no habiendo gran diferencia en las consultas.

Poco después @Dmitry Maslennikov me dijo "¿Por qué no pruebas desde un proyecto en Python?" pues bien, aquí está la versión en Python de las pruebas que realizamos mediante las conexiones JDBC previamente.

2
0 230
Artículo Luis Angel Pérez Ramos · sep 5, 2023 12m read

Continuamos con la serie de artículos relativos a la aplicación de QuinielaML. En este artículo vamos a tratar cómo preparamos los datos en crudo que hemos capturado mediante la funcionalidad de Embedded Python.

¡Bienvenidos a todos!

Introducción

0
0 173
Artículo Ricardo Paiva · ago 31, 2023 1m read

Preguntas frecuentes de InterSystems

Podéis ver el espacio libre disponible para la base de datos usando la opción "Free space view" (Ver espacio libre) en el Management Portal: System Operation > Databases.

Y se puede obtener mediante programación a través la consulta FreeSpace de la clase del sistema SYS.Database.

0
0 147
Artículo Ricardo Paiva · jul 25, 2023 9m read

Antes de empezar a hablar de bases de datos y de los distintos modelos de datos que existen, primero explicaré qué es una base de datos y cómo se utiliza.

Una base de datos es una colección organizada de datos, almacenados y accesibles de forma electrónica. Se utiliza para almacenar y recuperar datos estructurados, semiestructurados o sin procesar, que normalmente están relacionados con un tema o una actividad.

En el corazón de toda base de datos hay al menos un modelo utilizado para describir sus datos. Y según el modelo que utilice, una base de datos puede tener características ligeramente diferentes y almacenar distintos tipos de datos.

Para escribir, recuperar, modificar, ordenar, transformar o imprimir la información de la base de datos, se utiliza un software llamado Sistema de Gestión de Bases de Datos (DBMS, por sus siglas en inglés).

El tamaño, la capacidad y el rendimiento de las bases de datos y sus respectivos DBMS ha aumentado de forma significativa. Esto ha sido posible gracias a los avances tecnológicos en varios ámbitos, como los procesadores, la memoria y almacenamiento de los ordenadores y las redes informáticas. En general, el desarrollo de la tecnología de bases de datos puede dividirse en cuatro generaciones basadas en los modelos o la estructura de los datos: navegacional, relacional, de objetos y post-relacionales.

A diferencia de las tres primeras generaciones, que se caracterizan por un modelo de datos específico, la cuarta generación incluye muchas bases de datos diferentes basadas en distintos modelos, como columnas, gráficos, documentos, componentes, multidimensiones, clave-valor, almacenamiento en memoria, etc. Todas estas bases de datos están unidas por un único nombre NoSQL (NoSQL o ahora es más preciso decir No sólo SQL).

Además, ahora aparece una nueva clase, que se llama NewSQL. Se trata de bases de datos relacionales modernas que tienen como objetivo ofrecer el mismo rendimiento escalable que los sistemas NoSQL para cargas de trabajo de procesamiento de transacciones en línea (lectura-escritura), a la vez que utilizan SQL y mantienen ACID.

Por casualidad, entre estas bases de datos de cuarta generación se encuentran las que admiten los modelos de datos múltiples mencionados anteriormente. Se llaman bases de datos multimodelo. Un buen ejemplo de este tipo de base de datos es InterSystems IRIS. Por ello, la utilizaré para dar ejemplos de distintos tipos de modelos, según vayan presentándose.

<iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" src="https://www.youtube.com/embed/EAOfw09xTrM" title="YouTube video player" width="560" height="315" frameborder="0"></iframe>

La primera generación de bases de datos utilizaba modelos jerárquicos o modelos en red. El núcleo de los primeros es una estructura en forma de árbol en la que cada registro tiene un solo propietario. Podéis ver cómo funciona utilizando el ejemplo de InterSystems IRIS, porque su modelo principal es jerárquico y todos los datos se almacenan en globals (que son árboles B). Podéis leer más sobre los globals aquí.

Podemos crear este árbol en IRIS:

Set^a("+7926X", "city") = "Moscow"Set^a("+7926X", "city", "street") = "Req Square"Set^a("+7926X", "age") = 25Set^a("+7916Y", "city") = "London"Set^a("+7916Y", "city", "street") = "Baker Street"Set^a("+7916Y", "age") = 36

Y verlo en la base de datos:

Después, Edgar F. Codd propuso su álgebra relacional y su teoría del almacenamiento de datos, basada en principios relacionales, en 1969. Y tras ello se crearon las bases de datos relacionales. El uso de relaciones (tablas), atributos (columnas), tuplas (filas) y, lo que es más importante, transacciones y requisitos ACID, hizo que estas bases de datos fueran muy populares y lo siguen siendo ahora.

Por ejemplo, tenemos el siguiente esquema:

Podemos crear y completar tablas:

Y si escribimos la consulta:

select p.ID, p.Name, a.Country, A.City
  from My.Person p left join My.Address a
    on p.Address = a.ID

recibiremos la respuesta:

A pesar de las significativas ventajas de las bases de datos relacionales, con la difusión de los lenguajes de objetos se hizo necesario almacenar datos orientados a objetos en bases de datos. Por eso, en los años 90 aparecieron las primeras bases de datos orientadas a objetos y objeto-relacionales. Estas últimas se crearon a partir de bases de datos relacionales y añadiéndole complementos para simular el trabajo con objetos. Las primeras se desarrollaron desde cero a partir de las recomendaciones del consorcio OMG (Object Management Group) y después del ODMG (Object Data Management Group).

Las ideas clave de estas bases de datos orientadas a objetos son las siguientes.

El almacén de datos único es accesible usando:

  • object definition language - schema definition, permite definir clases, sus atributos, relaciones y métodos
  • object-query language - declarative, es un lenguaje similar a SQL, que permite obtener objetos de la base de datos
  • object manipulation language - permite modificar y guardar datos en la base de datos, admite transacciones y la invocación de métodos.

Este modelo permite obtener datos de bases de datos mediante lenguajes orientados a objetos.

Si tomamos la misma estructura que en el ejemplo anterior, pero en la forma orientada a objetos, tendremos las siguientes clases:

Class My.Person Extends%Persistent
{
Property Name As%Name;Property Address As My.Address;
}
Class My.Address Extends%Persistent
{
Property Country;Property City;
}

Y podemos crear los objetos utilizando el lenguaje orientado a objetos:

 set address = ##class(My.Address).%New()
 set address.Country = "France"
 set address.City = "Marseille"
 do address.%Save()
 set person = ##class(My.Person).%New()
 set person.Address = address
 set person.Name = "Quouinaut, Martin"
 do person.%Save()

Desafortunadamente, las bases de datos de objetos no lograron competir con las bases de datos relacionales a pesar de su posición dominante, y como resultado, surgieron muchos ORM.

En cualquier caso, con la expansión de Internet en la década de los 2000 y la aparición de nuevos requisitos para el almacenamiento de datos, empezaron a surgir otros modelos de datos y DBMS. Dos de estos modelos que se utilizan en IRIS son los de documentos y columnas.

Las bases de datos orientadas a documentos se utilizan para gestionar datos semiestructurados. Se trata de datos que no siguen una estructura fija y llevan la estructura dentro. Cada unidad de información en una base de datos de este tipo es un par simple: una clave y un documento específico. Este documento normalmente tiene un formato JSON y contiene la información. Como la base de datos no requiere un esquema particular, también es posible integrar distintos tipos de documentos en el mismo almacén.

Si tomamos el ejemplo anterior, podemos tener documentos como estos:

{
   "Name":"Quouinaut, Martin",
   "Address":{
      "Country":"France",
      "City":"Paris"
   }
}
{
   "Name":"Merlingue, Luke",
   "Address":{
      "Country":"France",
      "City":"Nancy"
   },
   "Age":26
}

Estos dos documentos con un distinto número de campos se almacenan en la base de datos de IRIS sin ningún problema.

Y el último ejemplo de un modelo que estará disponible en la versión 2022.2 es el modelo de columnas. En este caso, el DBMS almacena las tablas de datos por columnas y no por filas.

La orientación por columnas permite un acceso más eficiente a los datos para consultar un subconjunto de columnas (eliminando la necesidad de leer columnas que no son relevantes), y más opciones para comprimir datos. Asimismo, comprimir por columnas es más eficaz cuando los datos de la columna son similares. No obstante, generalmente son menos eficaces para insertar nuevos datos.

Podéis crear esta tabla:

Create Table My.Address (
  city varchar(50),
  zip varchar(5),
  country varchar(15)
) WITH STORAGETYPE = COLUMNAR

En este caso, la clase es la siguiente:

 
Spoiler
 
Class My.Address Extends%Persistent [ ClassType = persistent, DdlAllowed, Final, Owner = {UnknownUser}, ProcedureBlock, SqlRowIdPrivate, SqlTableName = Address ]
{
Property city As%Library.String(COLLATION = "EXACT", MAXLEN = 50) [ SqlColumnNumber = 2 ];Property zip As%Library.String(COLLATION = "EXACT", MAXLEN = 5) [ SqlColumnNumber = 3 ];Property country As%Library.String(COLLATION = "EXACT", MAXLEN = 15) [ SqlColumnNumber = 4 ];Parameter STORAGEDEFAULT = "columnar";Parameter USEEXTENTSET = 1;/// Bitmap Extent Index auto-generated by DDL CREATE TABLE statement.  Do not edit the SqlName of this index.
Index DDLBEIndex [ Extent, SqlName = "%%DDLBEIndex", Type = bitmap ];
Storage Default
{
<Data name="_CDM_city">
<Attribute>city</Attribute>
<ColumnarGlobal>^q3AW.DZLd.1.V1</ColumnarGlobal>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_country">
<Attribute>country</Attribute>
<ColumnarGlobal>^q3AW.DZLd.1.V2</ColumnarGlobal>
<Structure>vector</Structure>
</Data>
<Data name="_CDM_zip">
<Attribute>zip</Attribute>
<ColumnarGlobal>^q3AW.DZLd.1.V3</ColumnarGlobal>
<Structure>vector</Structure>
</Data>
<DataLocation>^q3AW.DZLd.1</DataLocation>
<ExtentLocation>^q3AW.DZLd</ExtentLocation>
<ExtentSize>3</ExtentSize>
<IdFunction>sequence</IdFunction>
<IdLocation>^q3AW.DZLd.1</IdLocation>
<Index name="DDLBEIndex">
<Location>^q3AW.DZLd.2</Location>
</Index>
<Index name="IDKEY">
<Location>^q3AW.DZLd.1</Location>
</Index>
<IndexLocation>^q3AW.DZLd.I</IndexLocation>
<Property name="%%ID">
<AverageFieldSize>3</AverageFieldSize>
<Selectivity>1</Selectivity>
</Property>
<Property name="city">
<AverageFieldSize>7</AverageFieldSize>
<Selectivity>33.3333%</Selectivity>
</Property>
<Property name="country">
<AverageFieldSize>7</AverageFieldSize>
<Selectivity>33.3333%</Selectivity>
</Property>
<Property name="zip">
<AverageFieldSize>7</AverageFieldSize>
<Selectivity>33.3333%</Selectivity>
</Property>
<SQLMap name="%%DDLBEIndex">
<BlockCount>-4</BlockCount>
</SQLMap>
<SQLMap name="IDKEY">
<BlockCount>-4</BlockCount>
</SQLMap>
<SQLMap name="_CDM_city">
<BlockCount>-4</BlockCount>
</SQLMap>
<SQLMap name="_CDM_country">
<BlockCount>-4</BlockCount>
</SQLMap>
<SQLMap name="_CDM_zip">
<BlockCount>-4</BlockCount>
</SQLMap>
<StreamLocation>^q3AW.DZLd.S</StreamLocation>
<Type>%Storage.Persistent</Type>
}
}
 

Después insertamos los datos:

insert into My.Address values ('London', '47000', 'UK')
insert into My.Address values ('Paris', '54000', 'France')
insert into My.Address values ('Kyiv', '03000', 'Ukraine')

En los globals podemos ver:

Si abrimos el global con los nombres de las ciudades, podremos ver:

Y si escribimos una consulta:

select City
  from My.Address

recibimos los datos:

En este caso, el DBMS se limita únicamente a leer un global para obtener el resultado completo. Además, ahorra tiempo y recursos en la lectura.

Así pues, hemos hablado de 5 modelos de datos diferentes compatibles con la base de datos InterSystems IRIS: los modelos jerárquicos, relacionales, de objetos, de documentos y de columnas.

Espero que este artículo os resulte útil para saber qué modelos están disponibles. Si tenéis alguna pregunta, podéis escribirla en los comentarios.

2
0 427