Pular para o conteúdo principal

Guia de integração — Relatórios dinâmicos

Microserviço: XDPeople.Soba.Report.WebAPI
Autenticação: Bearer Token (JWT) ou Chave de API via header X-API-KEY

Requisito de Autenticação

Para seguir este guia com sucesso utilizando autenticação JWT, deve autenticar-se utilizando o seguinte endpoint:

POST /gateway/auth/combined-login

Saiba mais...


Introdução

Este guia descreve como integrar com a API de relatórios dinâmicos: relatórios criados pelo utilizador, compostos por uma consulta SQL SELECT, lista de colunas (fields) e parâmetros (parameters). A API expõe operações para validar a SQL sem persistir, criar ou atualizar o relatório (configuração guardada), obter configuração e dados em tempo de execução e executar SQL ad hoc (com as mesmas regras de segurança).

Em contraste com os Relatórios Padrão (catálogo fixo de relatórios pré-configurados existentes no XD GC), os relatórios dinâmicos permitem criar e alterar a própria consulta SQL (dentro das restrições de segurança descritas abaixo), mantendo metadados de colunas e parâmetros alinhados com o resultado da validação.

Este documento está organizado para leitura por etapas e para consulta por ferramentas: primeiro os conceitos da SQL e marcadores, depois os fluxos (criar, atualizar, executar), o comportamento dos parâmetros na interface, o formato jsonParameters, a referência de endpoints e a estrutura dos DTOs (incluindo os valores canónicos de fieldType / parameterType), exemplos e erros. Ver Mapa do documento.


Quando utilizar

  • Criar ou alterar relatórios personalizados através da API ou de uma aplicação parceira.
  • Validar uma consulta antes de gravar (descoberta automática de colunas e marcadores de posição).
  • Obter a configuração do relatório (colunas, parâmetros para a interface) e os dados filtrados por parâmetros.
  • Pré-visualizar dados de uma consulta com execute-sqlquery (sem criar relatório).

Mapa do documento

  1. Conceitos da SQL — restrições de segurança, marcadores @WHERE / @OTHERS, padrão @p_. Valores de fieldType / parameterType: Contrato de tipos; restantes campos e enumerados: estrutura dos DTOs e documentação OpenAPI em Endpoints disponíveis.
  2. Fluxos de implementação — criação, atualização e execução (incluindo paralelo com Relatórios Padrão); secção Fluxos de implementação.
  3. Parâmetros: configuração e visualização — enriquecimento após verify-sqlquery, Lookup, impacto na UI; secção Parâmetros: lógica de configuração e visualização.
  4. jsonParameters — objeto JSON na cadeia de consulta (GET / POST); secção Parâmetro de query jsonParameters.
  5. ReferênciaEndpoints disponíveis, estrutura dos DTOs, exemplos JSON, tratamento de erros, recursos relacionados.

SQL e regras de segurança

A API não aceita comandos de escrita ou de administração na SQL enviada para validação ou execução. Em termos práticos, entre outras rejeições:

  • Instruções INSERT, UPDATE, DELETE
  • DDL (DROP, ALTER, CREATE, TRUNCATE, …)
  • Execução de procedimentos ou comandos perigosos (EXEC, EXECUTE, extensões sp_ / xp_, etc.)

A consulta principal deve ser orientada a leitura (SELECT e cláusulas associadas). Mensagens de erro devolvidas pela API indicam falhas de validação ou erros da base de dados durante o teste limitado.


Composição da consulta: @WHERE e @OTHERS

O modelo interno concatena sqlQuery com o filtro e cláusulas extra:

  • O texto de sqlQuery pode incluir os marcadores @WHERE e @OTHERS, que são substituídos pelo conteúdo de whereClause e otherClauses, respetivamente.
  • Se não estiverem presentes na sqlQuery, o mapeamento a partir de SqlQueryDTO anexa-os ao final da consulta principal, para que whereClause e otherClauses tenham sempre efeito.

Certifique-se de que a SQL final permanece sintaticamente válida após a substituição (por exemplo, whereClause começa normalmente por AND ... ou WHERE ..., consoante o que já existe na sqlQuery).


Parâmetros na SQL: padrão @p_

Os marcadores de posição reconhecidos pelo motor de relatórios dinâmicos seguem a expressão regular @p_ seguida de identificador (ex.: @p_dataInicio, @p_entidadeId).

  • Utilize sempre este prefixo (@p_) nos nomes dos parâmetros na SQL.
  • Os nomes devolvidos em parameters pela validação coincidem com estes marcadores.
  • Nos pedidos GET /gateway/dynamic-reports/{guid}/data e POST /gateway/dynamic-reports/execute-sqlquery, o JSON em jsonParameters deve usar as mesmas chaves que os nomes dos parâmetros (ex.: {"@p_dataInicio":"2025-01-01"}).

Fluxos de implementação

O comportamento recomendado segue dois percursos distintos: criação de um relatório novo e atualização de um já existente. A atualização não repete todo o texto da criação: primeiro obtém-se o identificador a partir do catálogo e carrega-se a configuração completa; depois aplicam-se os mesmos passos de validação e fusão de metadados descritos para a criação.

Execução no tempo de execução (mesmo modelo que relatórios padrão)

Para executar um relatório já existente, o fluxo no cliente é o mesmo que em Relatórios Padrão: catálogoinformação para montar o ecrã (colunas e parâmetros) → pedido GET com jsonParameters na cadeia de consulta (objeto JSON serializado e codificado em URL, com todos os parâmetros obrigatórios). O contrato de jsonParameters e o modo de codificação são equivalentes; alteram-se apenas os URLs.

PassoRelatórios padrãoRelatórios dinâmicos
CatálogoGET /gateway/reportsGET /gateway/dynamic-report
ConfiguraçõesGET /gateway/reports/{reportId}/infoGET /gateway/dynamic-reports/{guid}/config
Obter dados / execuçãoGET /gateway/reports/{reportId}?jsonParameters=…GET /gateway/dynamic-reports/{guid}/data?jsonParameters=…

Nos relatórios dinâmicos, as chaves do JSON seguem os marcadores @p_… da SQL (tal como devolvidos na configuração), da mesma forma que nos padrões se usam as chaves indicadas pelo paramHolder no passo de informação.

Criação de um relatório novo

Diagrama apenas para o fluxo de primeira gravação (sem listagem nem PUT).

  1. Construir o corpo SqlQueryDTO com sqlQuery, whereClause e otherClauses.
  2. Chamar POST /gateway/dynamic-reports/verify-sqlquery para confirmar que a consulta é válida e obter fields e parameters com tipos normalizados (fieldType / parameterType; valores permitidos em Contrato de tipos).
  3. Para cada coluna ou parâmetro devolvido, se já existir configuração guardada com o mesmo name, preservar legendas, visibilidade, formatação, área pivot, listas de valores, etc.; caso contrário, usar o objeto devolvido pela validação (em criação pura, costuma ser só o devolvido pela validação).
  4. Enviar POST com DynamicReportDataDTO completo e guardar o guid do relatório para pedidos subsequentes.
  5. No tempo de execução: config para montar o ecrã de parâmetros; data com jsonParameters (JSON serializado e codificado na cadeia de consulta, como nos relatórios padrão).

Atualização de um relatório existente

Para alterar um relatório já guardado, não basta enviar PUT sem contexto: é preciso saber qual o relatório a mudar. O fluxo típico é:

  1. Listar os relatórios dinâmicos disponíveis para o utilizador através de GET /gateway/dynamic-report (paginação opcional com page e pageSize). — Ver mais →
  2. Escolher o identificador guid do relatório pretendido na resposta.
  3. Obter a configuração completa com GET /gateway/dynamic-reports/{guid} (DynamicReportDataDTO), para partir da versão atual guardada no serviço de relatórios.
  4. Aplicar as alterações pretendidas (por exemplo à SQL ou aos metadados).
  5. Repetir os passos 2 e 3 da criação: POST /gateway/dynamic-reports/verify-sqlquery sempre que sqlQuery, whereClause ou otherClauses tenham mudado face à última validação bem-sucedida; fundir fields e parameters por name como na criação (preservar legendas, pivot, listas de valores, etc.).
  6. Enviar PUT /gateway/dynamic-reports com o mesmo guid no corpo e o DynamicReportDataDTO atualizado.

Diagrama resumido da atualização (referência cruzada ao fluxo de criação nos passos de validação e fusão):

Catálogo e configuração completa guardada

GET /gateway/dynamic-report devolve o catálogo (lista paginada dos relatórios dinâmicos). GET /gateway/dynamic-reports/{guid} devolve a configuração completa guardada (DynamicReportDataDTO), necessária para montar o corpo do PUT.

Revalidar após alterar a SQL

Se sqlQuery, whereClause ou otherClauses mudarem face à última validação bem-sucedida, volte a chamar POST /gateway/dynamic-reports/verify-sqlquery antes de gravar (POST ou PUT), para sincronizar colunas, parâmetros e tipos.


Parâmetros: lógica de configuração e visualização

Além dos marcadores @p_... na SQL, cada parâmetro guardado em DynamicReportParameterDTO define como é escolhido o valor e que controlo de interface o cliente deve mostrar ao utilizador final. O servidor converte essa combinação num modo de apresentação (ViewType em ReportParameterDetail), devolvido em GET /gateway/dynamic-reports/{guid}/config.

Depois da validação da SQL

A resposta de POST /gateway/dynamic-reports/verify-sqlquery inclui uma entrada em parameters por cada @p_... encontrado, normalmente só com name (igual ao marcador) e parameterType inicial. Na criação ou atualização do relatório deve enriquecer cada parâmetro com:

CampoFunção
nameIgual ao marcador na SQL (ex.: @p_dataInicio). É também a chave em jsonParameters na execução do relatório.
captionTexto apresentado ao utilizador (rótulo do campo).
parameterTypeTipo lógico (string, number, date, datetime, boolean, …) — ver Contrato de tipos.
entityTypeOpcional. Identifica uma entidade de negócio para pesquisa assistida (cliente, artigo, etc.), nos valores que o produto aceita no GC.
defaultValue / defaultValueTypeValor inicial (None, valor fixo SpecifyValue, ou função de data DateTimeFunction).
availableValueTypeOrigem dos valores escolhíveis: nenhuma lista (None / 0), lista fixa (ValuesList / 1), lista por SQL (QueryResult / 2).
availableValuesListLista de { value, description } quando availableValueType = lista fixa.
queryConsulta SQL auxiliar quando availableValueType = lista por SQL (alimenta o controlo de escolha).

Regra especial: pesquisa por entidade (Lookup)

Se entityType está preenchido e availableValueType é None (ou seja, não está a usar lista fixa nem lista por SQL), o parâmetro é tratado como pesquisa assistida à entidade: o utilizador escolhe um registo (por exemplo cliente ou artigo) através do fluxo de pesquisa do produto, em vez de um campo de texto livre.

Se availableValueType for ValuesList ou QueryResult, prevalece a lista fixa ou a lista por SQL; nestes casos o modo entidade não aplica esta regra de Lookup da mesma forma.

Impacto na interface: parameterType × availableValueType

Quando não se está no modo pesquisa por entidade acima, a combinação típica é:

parameterTypeavailableValueTypeComportamento esperado no ecrã
stringNoneCaixa de texto
stringValuesListLista pendente com entradas de availableValuesList
stringQueryResultLista alimentada pela SQL em query
numberNoneEntrada numérica
numberValuesList / QueryResultLista com valores numéricos (fixos ou por query)
date ou datetimeNoneSeletor de data (ou data e hora)
date ou datetimeValuesList / QueryResultLista de datas ou lista por query
booleanNoneControlo sim / não
booleanValuesList / QueryResultLista de opções ou lista por query

Na prática, o cliente consome ReportParameterDetail.viewType (e campos como valueList, query, entityTypeValue, paramHolder) devolvidos pela configuração do relatório para desenhar o controlo correto.

Onde ver o resultado para integração

  • GET /gateway/dynamic-reports/{guid}/config — devolve a configuração já com parâmetros detalhados para montar o formulário antes de chamar GET /gateway/dynamic-reports/{guid}/data.
    Ver mais →
  • POST /gateway/dynamic-reports/parameters — envia uma lista de DynamicReportParameterDTO e recebe os ReportParameterDetail correspondentes sem gravar o relatório (útil para pré-visualizar comportamento da UI).
    Ver mais →

Parâmetro de query jsonParameters

Para obter dados (GET /gateway/dynamic-reports/{guid}/data) e para executar SQL (POST /gateway/dynamic-reports/execute-sqlquery), o parâmetro de consulta opcional jsonParameters deve ser um objeto JSON serializado em texto e codificado para URL (tal como em Relatórios Padrão): chaves iguais aos nomes dos marcadores (@p_...), valores no formato esperado para o tipo.

Importante

Envie jsonParameters como texto JSON codificado na cadeia de consulta da URL. As bibliotecas HTTP habitualmente tratam da serialização e da codificação para URL automaticamente.


Endpoints disponíveis

URL: /gateway/dynamic-report
Método: GET
Query (opcional): page, pageSize — paginação

Devolve a lista paginada de relatórios dinâmicos acessíveis ao contexto autenticado. É o passo habitual antes de GET /gateway/dynamic-reports/{guid} no fluxo de atualização.
Ver mais →


Operações sob /gateway/dynamic-reports

Os pedidos seguintes utilizam esse prefixo. Todos requerem autenticação.

Testar a validade da SQL (sem gravar)

URL: /gateway/dynamic-reports/verify-sqlquery
Método: POST
Corpo: SqlQueryDTO

Executa validações de segurança e uma execução de teste para inferir colunas e parâmetros.

CampoTipoObrigatórioDescrição
sqlQuerystringSimInstrução principal SELECT (pode incluir @WHERE e @OTHERS).
whereClausestringNãoFiltro substituído em @WHERE.
otherClausesstringNãoTexto substituído em @OTHERS (ex.: GROUP BY, ORDER BY).

Resposta (200): objeto com listas fields e parameters (tipos normalizados).
Ver mais →


Criar relatório dinâmico

URL: /gateway/dynamic-reports
Método: POST
Corpo: DynamicReportDataDTO

Resposta: 204 No Content em caso de sucesso.
Ver mais →


Atualizar relatório dinâmico

URL: /gateway/dynamic-reports
Método: PUT
Corpo: DynamicReportDataDTO (incluir o mesmo guid)

Resposta: 204 No Content em caso de sucesso.
Ver mais →


Eliminar relatório dinâmico

URL: /gateway/dynamic-reports/{guid}
Método: DELETE

Resposta: 204 No Content em caso de sucesso.
Ver mais →


Obter o relatório guardado (configuração completa)

URL: /gateway/dynamic-reports/{guid}
Método: GET

Resposta (200): DynamicReportDataDTO (dados completos tal como guardados).
Ver mais →


Obter configuração para apresentação (colunas e parâmetros da interface)

URL: /gateway/dynamic-reports/{guid}/config
Método: GET

Resposta (200): ReportConfig (colunas, parâmetros com detalhes para o ecrã, consulta final, etc.).
Ver mais →


Obter dados do relatório

URL: /gateway/dynamic-reports/{guid}/data
Método: GET
Query (opcional): jsonParameters — JSON com valores dos parâmetros @p_...

Equivalente, no modelo de integração, a GET /gateway/reports/{reportId}?jsonParameters=… nos relatórios padrão; ver Execução no tempo de execução e Relatórios Padrão — Step 3.

Resposta (200): ResultReport com contagem e linhas.
Ver mais →


Executar uma consulta SQL (sem relatório guardado)

URL: /gateway/dynamic-reports/execute-sqlquery
Método: POST
Query (opcional): jsonParameters
Corpo: SqlQueryDTO

Útil para pré-visualização; sujeito às mesmas regras de segurança que POST /gateway/dynamic-reports/verify-sqlquery.
Ver mais →


Detalhes de parâmetros a partir de DTOs

URL: /gateway/dynamic-reports/parameters
Método: POST
Corpo: lista de DynamicReportParameterDTO

Resposta (200): lista de ReportParameterDetail (apresentação / integração).
Ver mais →


Estrutura dos DTOs principais

As secções seguem a composição de dados de parte para o todo: SqlQueryDTO, elemento de cada coluna (DynamicReportFieldDTO), elemento de cada parâmetro (DynamicReportParameterDTO), valores canónicos partilhados (fieldType / parameterType), e por fim o documento guardado (DynamicReportDataDTO).

SqlQueryDTO

PropriedadeTipoObrigatórioDescrição
sqlQuerystringSimConsulta principal.
whereClausestringNãoCláusula de filtro.
otherClausesstringNãoOutras cláusulas SQL.

DynamicReportFieldDTO

PropriedadeTipoDescrição
namestringNome da coluna (deve coincidir com o resultado da SQL).
captionstringLegenda apresentada ao utilizador.
fieldTypestringTipo canónico da coluna na API — valores permitidos na tabela Contrato de tipos (abaixo).
entityTypestringTipo de entidade para navegação (opcional).
entityLinkobjectConfiguração de ligação a entidade (opcional).
areaenumSemântica pivot não aplicável enquanto isPivotReport for false (recomendado). Valores previstos para futuro suporte (PivotFieldArea): Column (0), RowArea (1), DataArea (2), FilterArea (3).
formattingstringMáscara / formatação de apresentação.
isVisiblebooleanColuna visível no resultado.

DynamicReportParameterDTO

PropriedadeTipoDescrição
namestringMarcador na SQL (ex.: @p_inicio).
captionstringLegenda do parâmetro.
parameterTypestringTipo canónico do parâmetro na API — mesmos valores permitidos que para fieldType; ver Contrato de tipos.
entityTypestringTipo de entidade para pesquisa assistida (opcional).
defaultValueobjectValor por omissão.
querystringSQL para lista de valores (AvailableValueType = resultado de query).
defaultValueTypeParamDefaultValueModo do valor por omissão.
availableValueTypeParamAvailableValueOrigem dos valores possíveis.
availableValuesListlista de ParamListValueLista fixa quando aplicável.

Contrato de tipos: fieldType e parameterType

Depois de conhecer as propriedades fieldType e parameterType nas tabelas acima, utilize apenas strings com um dos valores canónicos minúsculos seguintes (igual para colunas e parâmetros):

Valor APISignificado
stringTexto
numberValores numéricos
booleanVerdadeiro / falso
datetimeData e hora
dateData (tipo de data do domínio)
objectObjeto genérico

DynamicReportDataDTO (campos mais relevantes)

PropriedadeTipoObrigatórioDescrição
guidstringNa atualizaçãoIdentificador único (GUID).
namestringSimNome corto (máx. 25 caracteres).
descriptionstringSimDescrição (máx. 250 caracteres).
querySqlQueryDTOSimComposição da SQL.
fieldslista de campoSimColunas do relatório.
parameterslista de parâmetroNãoParâmetros da SQL.
permissionTypeintNãoModelo de permissões.
permissionGroupsIdlista de intCondicionalIDs dos grupos se permissionType = grupos.
reportTypeintSimTipo de relatório no sistema.
isPivotReportbooleanNãoRecomendado: false. Modo pivot não suportado pela API neste momento.
dataAccessstringNãoConfiguração de acesso a dados.
addToRibbonbooleanNãoMostrar no menu.
showInPosbooleanNãoMostrar no POS.
userIdintNãoUtilizador associado (permissões «utilizador atual»).

Exemplos JSON

Pedido a POST /gateway/dynamic-reports/verify-sqlquery

{
"sqlQuery": "SELECT Id, KeyId, GroupId, Description, NetPrice1, RetailPrice1 FROM items",
"whereClause": "WHERE (@p_itemgroupId IS NULL OR GroupId = @p_itemgroupId)",
"otherClauses": "ORDER BY KeyId DESC"
}

Resposta

{
"fields": [
{
"name": "Id",
"caption": null,
"fieldType": "number",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "KeyId",
"caption": null,
"fieldType": "string",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "GroupId",
"caption": null,
"fieldType": "number",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "Description",
"caption": null,
"fieldType": "string",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "NetPrice1",
"caption": null,
"fieldType": "number",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "RetailPrice1",
"caption": null,
"fieldType": "number",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
}
],
"parameters": [
{
"name": "@p_itemgroupId",
"caption": null,
"parameterType": "string",
"entityType": null,
"defaultValue": null,
"query": null,
"defaultValueType": 0,
"availableValueType": 0,
"availableValuesList": []
}
]
}

Corpo para criação (POST /gateway/dynamic-reports)

Modo pivot

Utilize sempre isPivotReport: false. A API ainda não suporta pivot em relatórios dinâmicos; os campos area nos exemplos são valores neutros (0) exigidos pelo contrato.

{
"guid": "d972a28e-4f6a-47b7-9342-650b7758d0a5",
"name": "Report test docs",
"description": "Report test docs desc",
"query": {
"sqlQuery": "SELECT Id, KeyId, GroupId, Description, NetPrice1, RetailPrice1 FROM items",
"whereClause": "WHERE (@p_itemgroupId IS NULL OR GroupId = @p_itemgroupId)",
"otherClauses": "ORDER BY KeyId DESC"
},
"isPivotReport": false,
"addToRibbon": true,
"showInPos": true,
"permissionType": 0,
"permissionGroupsId": [],
"reportType": 2,
"dataAccess": "",
"userId": 0,
"fields": [
{
"name": "Id",
"caption": null,
"fieldType": "number",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "KeyId",
"caption": null,
"fieldType": "string",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "GroupId",
"caption": null,
"fieldType": "number",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "Description",
"caption": null,
"fieldType": "string",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "NetPrice1",
"caption": null,
"fieldType": "number",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
},
{
"name": "RetailPrice1",
"caption": null,
"fieldType": "number",
"entityType": null,
"entityLink": null,
"area": 0,
"formatting": null,
"isVisible": true
}
],
"parameters": [
{
"name": "@p_itemgroupId",
"caption": "Família",
"parameterType": "string",
"entityType": "itemsgroups",
"defaultValue": null,
"query": null,
"defaultValueType": 0,
"availableValueType": 0,
"availableValuesList": []
}
]
}

(Os valores exatos de fields e parameters devem estar alinhados com o resultado de POST /gateway/dynamic-reports/verify-sqlquery e com as regras de negócio da sua instalação.)


Tratamento de erros

As operações devolvem respostas de problema (Problem Details) ou payload de erro alinhado com o padrão Result da API quando a operação falha (validação SQL, permissões, relatório não encontrado, etc.).

O cliente de integração deve:

  1. Verificar o código de estado HTTP (4xx / 5xx).
  2. Ler o corpo da resposta de erro (código, tipo, mensagem) e agir em conformidade (corrigir a SQL, a autenticação ou os dados enviados).

Este guia não lista códigos de erro fixos: consulte a mensagem devolvida e a documentação da operação em causa.


Conclusão

Este guia resume dois percursos: criação (POST /gateway/dynamic-reports/verify-sqlquery, fusão de metadados, POST /gateway/dynamic-reports para gravar, depois config / data) e atualização (catálogo em /gateway/dynamic-report, GET /gateway/dynamic-reports/{guid}, repetir POST /gateway/dynamic-reports/verify-sqlquery e fusão como na criação, PUT /gateway/dynamic-reports). Em ambos os casos, respeite o prefixo @p_, os marcadores @WHERE / @OTHERS e as strings canónicas de fieldType / parameterType definidas no Contrato de tipos. Para ensaios sem gravar, utilize POST /gateway/dynamic-reports/execute-sqlquery.

Recomenda-se testes exaustivos em ambiente de qualidade antes de produção, incluindo SQL inválida, parâmetros em falta e alterações de esquema.

Para gráficos dinâmicos construídos sobre relatórios dinâmicos, consulte o Guia de integração — Gráficos dinâmicos.


Recursos relacionados


Última atualização: 12 de maio de 2026