# Horizun PBI MCP (pypi · horizun-pbi-mcp)

Build, audit and repair Power BI Desktop models and PBIP reports with DAX, TMDL and PBIR.

- Trust score: 76/100 (medium)
- Change this week: +19
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-09-21

## Components

- pypi · `horizun-pbi-mcp`: 76/100 (this document), [markdown](https://verifymcp.io/servers/horizungroup-horizun-pbi-mcp/horizun-pbi-mcp.md), [page](https://verifymcp.io/servers/horizungroup-horizun-pbi-mcp/horizun-pbi-mcp)

## Channel facts

- Registry: `pypi`
- Package: `horizun-pbi-mcp`
- Version: `2.1.1`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-09-21.

- **Supply Chain Security**: 100/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs setuptools.build_meta at install time, a recognised native-build step with no shell scripting around it.
  - 1 of 46 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 35/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - License check failed: no license is declared.
  - Actively maintained (last published 15 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 60/100
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 23980 tokens (~172/item across 139 items; 139 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 87/100
  - Stability observed for 26 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 71/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 0% of tool parameters carry a description.
  - Structured output schemas are declared (100% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 5 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 139 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.

## Install

### How do I install the Horizun PBI MCP server?

Horizun PBI MCP runs locally as a PyPI package, launched with uvx horizun-pbi-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add horizungroup-horizun-pbi-mcp -- uvx horizun-pbi-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "horizungroup-horizun-pbi-mcp": {
      "command": "uvx",
      "args": [
        "horizun-pbi-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "horizungroup-horizun-pbi-mcp": {
      "command": "uvx",
      "args": [
        "horizun-pbi-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add horizungroup-horizun-pbi-mcp -- uvx horizun-pbi-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "horizungroup-horizun-pbi-mcp": {
      "type": "local",
      "command": [
        "uvx",
        "horizun-pbi-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add horizungroup-horizun-pbi-mcp --command uvx --arg horizun-pbi-mcp
```

### Hermes

```yaml
mcp_servers:
  horizungroup-horizun-pbi-mcp:
    command: "uvx"
    args: ["horizun-pbi-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "horizungroup-horizun-pbi-mcp": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "horizun-pbi-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add horizungroup-horizun-pbi-mcp -t stdio -c uvx -a horizun-pbi-mcp
```

### Other

```json
{
  "mcpServers": {
    "horizungroup-horizun-pbi-mcp": {
      "command": "uvx",
      "args": [
        "horizun-pbi-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-09-21 (score 76, +1)

No change was recorded against any check on this day. Stability & Change Management went from 83 to 87. That category is still filling its 30-day observation window: 25 days of observed history at the previous scan, 26 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-19 (score 75, +1)

No change was recorded against any check on this day. Stability & Change Management went from 77 to 80. That category is still filling its 30-day observation window: 23 days of observed history at the previous scan, 24 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-17 (score 74, +1)

No change was recorded against any check on this day. Stability & Change Management went from 70 to 73. That category is still filling its 30-day observation window: 21 days of observed history at the previous scan, 22 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-15 (score 73, +16)

- [security improvement] Malware scan: unverified → pass

### 2026-09-14 (score 57, −15)

- [security regression] Malware scan: pass → unverified

### 2026-09-13 (score 72, +1)

No change was recorded against any check on this day. Stability & Change Management went from 57 to 60. That category is still filling its 30-day observation window: 17 days of observed history at the previous scan, 18 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-11 (score 71, +1)

No change was recorded against any check on this day. Stability & Change Management went from 50 to 53. That category is still filling its 30-day observation window: 15 days of observed history at the previous scan, 16 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-09-08 (score 70, +1)

No change was recorded against any check on this day. Stability & Change Management went from 40 to 43. That category is still filling its 30-day observation window: 12 days of observed history at the previous scan, 13 at this one. The score rises as the window fills, whether or not the server changes.

## MCP tools (139)

### `pbi_profile_data` (~160 tokens)

Perfila los VALORES del modelo abierto y devuelve lo que no cuadra.

Complementa a pbi_audit_model, que revisa la estructura: un porcentaje
que vale -800 no es un defecto del modelo sino de los datos, y solo se
ve consultandolos.

Detecta porcentajes fuera de 0-100, columnas vacias, columnas de un
solo valor y columnas mayormente vacias. Cada hallazgo trae la consulta
que lo demuestra y la consecuencia concreta sobre el tablero.

Solo lectura. `tables` acota el trabajo; `max_columns` evita que un
modelo grande agote el timeout y devuelva un perfil a medias.

Input parameters:

- `max_columns` (integer)
- `tables`

Output parameters:

- `result` (object)

### `pbi_audit_project` (~215 tokens)

Auditoria integral: modelo semantico + informe + layout.

Devuelve puntaje global y por dominio, resumen ejecutivo, hallazgos
priorizados con evidencia y recomendacion, y que reglas tienen
correccion automatica.

\`formats`: ['markdown','html'] escribe tambien esos informes en
outputs/ y devuelve sus rutas. `rules` y `min_severity` acotan.

\`compact=true` devuelve la MISMA auditoria sin repetir texto: cada
hallazgo lleva un `finding_id`, `priority` pasa a ser la lista de esos
identificadores en orden en vez de copias completas, y `groups` agrupa
las repeticiones por regla con su recuento y una muestra. Los puntajes
y recuentos por dominio no cambian. Los informes de `formats` se
escriben siempre completos.

Input parameters:

- `compact` (boolean)
- `formats`
- `min_severity` (string)
- `rules`

Output parameters:

- `result` (object)

### `pbi_audit_report_only` (~63 tokens)

Audita solo el informe PBIR (sin las reglas del modelo semantico).

Cubre paginas vacias, visuales sin titulo, campos rotos, duplicados,
tamanos de lienzo inconsistentes y la geometria de cada pagina.

Output parameters:

- `result` (object)

### `pbi_plan_audit_fixes` (~88 tokens)

Planifica correcciones para reglas CONCRETAS. No escribe nada.

No existe "arreglar todo": hay que indicar `rules` explicitamente.
\`objects` acota mas todavia (ids de visual o de pagina). Devuelve las
acciones exactas que se aplicarian, con su motivo.

Input parameters:

- `objects`
- `rules` (array, required)

Output parameters:

- `result` (object)

### `pbi_apply_audit_fixes` (~94 tokens)

Aplica las acciones devueltas por pbi_plan_audit_fixes.

Requiere confirm=true. Cada accion se aplica por su propia via segura
(transaccion, verificacion y rollback); si una falla, se reporta sin
detener las demas y sin ocultarlo.

Input parameters:

- `actions` (array, required)
- `confirm` (boolean)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_list_autofix_rules` (~29 tokens)

Reglas que tienen correccion automatica, y en que consiste cada una.

Output parameters:

- `result` (object)

### `pbi_diagnose_data` (~336 tokens)

Diagnostico de CONTENIDO contra el modelo VIVO: lo que rompe
tableros y ningun metadato ve.

Cuatro chequeos deterministas, cada uno con la consulta DAX que lo
demuestra y muestras de los valores culpables:

\- **claves_huerfanas**: filas del lado muchos cuya clave no existe en
  el lado uno (caen al Blank de la relacion; los totales cuadran de
  menos sin error). Incluye claves EN BLANCO.
\- **grano_duplicado**: el lado uno con claves repetidas (todo se
  multiplica al cruzar).
\- **calendario_con_huecos**: dias faltantes en la tabla de fechas.
\- **umbral_del_brief_violado** y **campo_critico_inexistente**: los
  \`critical_fields` del brief contra los datos reales. La severidad
  la decide el dueño: lo que declaro critico sale como `error`.

No hay heuristicas "inteligentes" de outliers ni escalas: lo generico
es determinista y lo subjetivo viene del brief. Un chequeo que no se
pudo correr sale en `skipped` con su motivo — "no se comprobo" y
"esta bien" no son lo mismo.

Requiere el modelo ABIERTO en Desktop (consulta datos, no archivos).
\`tables` acota a las relaciones que tocan esas tablas.

Input parameters:

- `request_id` (string)
- `tables`

Output parameters:

- `result` (object)

### `pbi_define_port_contract` (~212 tokens)

Escribe el CONTRATO del puerto del ecosistema (pbi-port-contract.json).

El puerto NO es un bus de APIs entre Revit/Navisworks/Project —eso es
fragil sin arreglo—: es un contrato de datos. Cada herramienta EMITE
un dataset normalizado con una llave compartida, y este MCP lo valida
y lo consume.

\`datasets`: [{name, key, columns: [{name, type, required?}],
emitted_by?, description?}]. La llave es obligatoria: es lo que
permite cruzar los datasets entre si (p.ej. HRZ_COD_PRES entre el
modelo BIM, el presupuesto y el cronograma).

Vive versionado junto al .pbip, como el brief. Se valida con
pbi_check_contract: archivos entrantes antes de cargar, y el modelo
activo despues.

Input parameters:

- `datasets` (array, required)
- `name` (string)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_check_contract` (~211 tokens)

Valida contra el contrato del puerto: un archivo entrante o el modelo.

Con `source_path` (+ `dataset`): el export de Revit/Navisworks/Project
ANTES de cargarlo — columnas que faltan, tipos incompatibles, llave
ausente. Chequeo ESTRUCTURAL y honesto: unicidad y huerfanas de la
llave exigen los datos completos, y eso es pbi_diagnose_data con la
tabla ya cargada; la respuesta lo dice en `not_checked`.

Sin `source_path`: el MODELO activo contra el contrato entero, y el
circulo que cierra todo: `suggested_critical_fields` — las llaves del
puerto listas para pbi_define_brief, de modo que el diagnostico las
trate como criticas del dueño sin teclearlas dos veces.

Input parameters:

- `dataset` (string)
- `request_id` (string)
- `source_path` (string)

Output parameters:

- `result` (object)

### `pbi_inspect_pbix` (~170 tokens)

Radiografia de un .pbix SIN convertirlo ni abrir Power BI Desktop.

Dice en que formato esta el informe ('pbir' si ya trae el formato
mejorado y solo hay que copiarlo, 'layout' si es el heredado y hay que
traducirlo), si lleva modelo de datos propio o es un informe con
conexion en vivo, y cuantas paginas, recursos y visuales personalizados
tiene. Sirve para saber que esperar antes de lanzar la conversion.

\`path`: ruta al archivo .pbix. Es el UNICO nombre del parametro: no
hay alias `pbix_path`, porque `path` es obligatorio en el contrato
congelado y un alias no podria sustituirlo.

Input parameters:

- `path` (string, required)

Output parameters:

- `result` (object)

### `pbi_convert_pbix_to_pbip` (~409 tokens)

Convierte uno o varios .pbix en proyectos .pbip (PBIR + TMDL).

El informe sale del propio archivo: si el .pbix ya guarda PBIR se copia
tal cual, y si trae el formato heredado se traduce pagina a pagina.

El modelo NO se puede leer del archivo (es un backup comprimido del
motor), asi que se ABRE EL .pbix EN POWER BI DESKTOP y se serializa a
TMDL desde ahi. Cuenta con eso: cada archivo tarda lo que tarde Desktop
en cargarlo. Si el informe ya esta abierto se reutiliza esa sesion; si
lo abre esta tool, lo cierra al terminar (`close_desktop`). El .pbix
original nunca se modifica.

\`path`: un .pbix o una CARPETA (`recursive` incluye subcarpetas).
\`out_dir`: carpeta donde crear el proyecto; se crea una subcarpeta por
informe. Los dos son obligatorios y esos son sus nombres: `pbix_path`
y `output_dir` NO existen como alias, porque un parametro obligatorio
del contrato congelado no se puede sustituir por otro nombre.
\`include_model=false` genera solo la mitad del informe, sin
tocar Desktop. `dataset_connection_string` es obligatorio para informes
con conexion en vivo (los que no llevan modelo propio).

Devuelve, por archivo, que se escribio y —lo importante— los avisos y
lo que se quedo por el camino (`dropped`), como los marcadores.

Input parameters:

- `close_desktop` (boolean)
- `dataset_connection_string`
- `desktop_timeout` (integer)
- `include_model` (boolean)
- `out_dir` (string, required)
- `overwrite` (boolean)
- `path` (string, required)
- `project_name`
- `recursive` (boolean)
- `request_id`

Output parameters:

- `result` (object)

### `pbi_list_convertible_pbix` (~123 tokens)

Lista los .pbix de una carpeta y como se convertiria cada uno.

Recorre los archivos sin abrirlos en Desktop y dice, por cada uno, si
el informe se copiaria (ya esta en PBIR) o habria que traducirlo, y si
hace falta Desktop para sacar el modelo. Es la vista previa del lote.

\`path`: carpeta (o un .pbix suelto). `recursive`: incluir subcarpetas.

Input parameters:

- `path` (string, required)
- `recursive` (boolean)

Output parameters:

- `result` (object)

### `pbi_list_desktop_models` (~165 tokens)

Lista los modelos de Power BI Desktop abiertos localmente.

Detecta el motor de Analysis Services (localhost:<puerto>) de cada informe
abierto y devuelve puerto, connection string, catalogo y nº de tablas.

Cada instancia trae ademas su IDENTIDAD: `engine_pid` es el proceso
del motor (msmdsrv.exe) y `desktop_pid` el de la ventana
(PBIDesktop.exe) —son distintos—, mas el titulo de la ventana, la ruta
del documento cuando puede demostrarse, `identity_confidence` y la
\`identity_evidence` que la sostiene. Un .pbip no deja descriptor
abierto sobre su carpeta: ahi la ruta sale `null` en vez de adivinada.

Output parameters:

- `result` (object)

### `pbi_select_model` (~110 tokens)

Selecciona el modelo local activo para futuras operaciones.

Si hay un solo modelo abierto no hace falta indicar nada. Si hay varios,
pasa `port` **o** `connection_string` TAL CUAL lo devuelve
\`pbi_list_desktop_models` (`"Data Source=localhost:56057"`): lo que sale
de esa tool entra en esta sin tener que extraer el puerto a mano.

Input parameters:

- `catalog`
- `connection_string`
- `port`

Output parameters:

- `result` (object)

### `pbi_open_in_desktop` (~307 tokens)

Abre un .pbip o .pbix en Power BI Desktop y espera a que sirva el modelo.

Cierra el ciclo de trabajo: despues de editar un proyecto, esto permite
comprobar que ABRE de verdad y consultar sus medidas, sin pedirle al
usuario que lo haga a mano. Un TMDL que no carga se manifiesta aqui.

Espera a que el motor local aparezca y deje de crecer, identifica cual
de las instancias corresponde a este archivo (el puerto es dinamico) y,
con `select=true`, lo deja como modelo activo.

Si el archivo ya estaba abierto se reutiliza esa sesion y no se toca
nada (`reuse_open`). Nunca cierra una ventana del usuario.

\`path` (o `pbip_path`, como lo llama `pbi_session_info`, o
\`project_path`) se puede omitir: entonces se abre el proyecto .pbip
activo. Tambien acepta la CARPETA del proyecto si contiene un unico
\`.pbip`; con varios, dice cuales hay y no elige.

Ojo: un .pbip recien abierto trae el modelo SIN DATOS. Refresca despues
con pbi_refresh_model si vas a comprobar valores.

Input parameters:

- `path`
- `pbip_path`
- `project_path`
- `reuse_open` (boolean)
- `select` (boolean)
- `timeout` (integer)

Output parameters:

- `result` (object)

### `pbi_validate_desktop_render` (~946 tokens)

Abre un .pbip/.pbix y captura su ventana real sin depender del foco.

Es la comprobacion visual automatizable que complementa al validador
PBIR: espera el modelo, identifica la ventana por PID y hora de inicio,
y la renderiza con PrintWindow directamente a ``outputs/desktop_captures``.
La captura no activa ni trae Desktop al frente, por lo que no puede
fotografiar por accidente otra ventana que tape el informe.

\`path` (o `pbip_path`, el mismo nombre que devuelve `pbi_session_info`)
se puede omitir: entonces se usa el proyecto .pbip activo.

\**`refresh=true` es lo que hace que la captura signifique algo en un
.pbip**: ese formato guarda la definicion, no los datos, asi que recien
abierto el modelo viene VACIO y las tablas y matrices salen en blanco
aunque el informe este perfecto. Con `refresh` se abre, se refresca, se
espera a que la ventana deje de repintar y se captura. Ese es tambien
el camino para fotografiar OTRA pagina con datos: `page` exige abrir el
proyecto, y al abrirlo hay que volver a refrescar.

Como el refresh modifica de forma irreversible el modelo en memoria,
\`refresh=true` exige ademas `confirm=true`. `confirm_reuse` autoriza
mover una ventana que ya era del usuario; son autorizaciones distintas.

Pase lo que pase, la respuesta lleva `data_loaded`: si es `false`, la
captura no es representativa del informe, es la foto de un modelo sin
datos. Si no se pudo comprobar, se dice; no se afirma ninguna de las dos.

\`page`: captura ESA pagina (id o nombre visible), no la que quedo
activa. `fit_to_page` (activado por defecto): fuerza la vista
"Ajustar a la pagina" para que salga el lienzo COMPLETO y no el tercio
superior al zoom guardado. Con el proyecto CERRADO (.pbip) ambos se
aplican como una vista temporal en disco que se restaura byte a byte
al terminar. Con la ventana YA ABIERTA -que es del usuario- se hacen
en la propia interfaz, pero SOLO con `confirm_reuse=true`: elegir una
pestaña y cambiar el zoom mueven esa ventana, y una captura que solo
debi…

Input parameters:

- `capture_timeout` (integer)
- `confirm` (boolean)
- `confirm_reuse` (boolean)
- `fit_to_page` (boolean)
- `page`
- `path`
- `pbip_path`
- `project_path`
- `refresh` (boolean)
- `refresh_timeout_seconds`
- `request_id` (string)
- `reuse_open` (boolean)
- `timeout` (integer)

Output parameters:

- `result` (object)

### `pbi_run_dax` (~267 tokens)

Ejecuta una consulta DAX de SOLO LECTURA contra el modelo activo.

Solo se admiten formas reconocidas: EVALUATE, DEFINE...EVALUATE y DMVs
de $SYSTEM. Cualquier otra cosa se rechaza (politica fail-closed).

\`max_rows`: limite de filas. `max_bytes`: tope de tamano del resultado,
para no devolver megas al cliente. `timeout_seconds`: timeout del
comando. `export=true` vuelca el resultado completo a outputs/ y
devuelve la ruta.

Devuelve columnas, tipos observados, filas, estadisticas de ejecucion y
si se trunco (y por que: filas o tamano). Los errores DAX del motor se
devuelven tal cual.

\`query` es el unico nombre de la consulta: no hay alias `dax`, porque
\`query` es obligatorio en el contrato congelado y un alias no podria
sustituirlo. Ejemplo: `pbi_run_dax(query="EVALUATE TOPN(5, 'Ventas')")`.

Input parameters:

- `export` (boolean)
- `max_bytes`
- `max_rows`
- `query` (string, required)
- `timeout_seconds`

Output parameters:

- `result` (object)

### `pbi_test_connection` (~22 tokens)

Valida la conexion al modelo activo con una consulta trivial.

Output parameters:

- `result` (object)

### `pbi_validate_measures` (~95 tokens)

Valida DAX de medidas SIN modificar el modelo (dry-run con DEFINE MEASURE).

Ideal para probar medidas ANTES de crearlas con pbi_create_measure.
\`measures`: lista de {"name","dax","table"(opcional)}. Las medidas pueden
referenciarse entre si. Devuelve por cada una: valid, value (muestra) y error.

Input parameters:

- `measures` (array, required)

Output parameters:

- `result` (object)

### `pbi_close_desktop` (~395 tokens)

Cierra la instancia de Power BI Desktop que sirve ESE proyecto.

Es la salida que faltaba del ciclo editar-abrir-mirar-editar: escribir
el TMDL exige Desktop cerrado, y no habia forma de cerrarlo desde aqui
\-tocaba matar el proceso a mano en PowerShell-.

Cierra SOLO la instancia con ese archivo abierto, verificando la
identidad del proceso (nombre + hora de arranque, nunca el PID a
secas). Al final re-comprueba que el archivo ya no este abierto y lo
dice en `verified_closed`.

DESTRUCTIVA: los cambios SIN GUARDAR de esa ventana se pierden -y en
un .pbip eso incluye los datos refrescados de la sesion-. Por eso
exige `confirm=true`. Si el archivo no estaba abierto, no hace nada y
lo dice (`was_open: false`).

\`path` (o `pbip_path` / `project_path`) se puede omitir: se cierra
el proyecto activo, que es el que el servidor ya conoce.

\**Tras `pbi_export_pbix(leave_open=true)`** la ventana queda sobre el
archivo exportado y ya no responde a la ruta del .pbip original.
Pasa `desktop_pid` y `desktop_started` tal cual los devuelve
\`desktop_session` de la exportacion: se cierra exactamente esa
instancia, verificando nombre del proceso y hora de arranque (nunca
el PID a secas, que Windows recicla). Con `path` ademas se exige que
la ventana no este sirviendo demostrablemente otro documento.

Input parameters:

- `confirm` (boolean)
- `desktop_pid`
- `desktop_started`
- `path`
- `pbip_path`
- `project_path`
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_start_here` (~159 tokens)

Por donde empezar. Mira el estado real y dice los siguientes pasos.

Ciento treinta y dos tools con buen nombre siguen siendo ciento treinta y dos tools.
Esta responde «¿y ahora que?» con tres o cuatro pasos concretos, cada
uno con el nombre exacto de la tool y **por que** toca ahora: si hay
proyecto activo, si tiene modelo o solo informe, si esta vacio, y si
Power BI Desktop lo tiene abierto —que impide escribir el TMDL—.

Devuelve tambien `common_tasks`: tareas frecuentes y la secuencia de
tools que las resuelve.

No escribe nada. Empieza por aqui cuando no sepas que sigue.

Input parameters:

- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_list_design_systems` (~146 tokens)

Sistemas de diseno disponibles: para que sirve cada uno y que trae.

Un sistema decide a la vez el tema (color y tipografia, con paletas ya
verificadas contra daltonismo), el tamano del lienzo, la rejilla sobre
la que se coloca todo y la escala de texto. Son la misma decision: un
tablero de sala se lee a cuatro metros y uno en PDF a cuarenta
centimetros, y eso no es el mismo diseno con otro color.

Eligelo ANTES de la primera pagina; cambiarlo despues obliga a
recolocarlo todo.

Input parameters:

- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_apply_design_system` (~123 tokens)

Aplica el sistema al informe y devuelve su rejilla.

Escribe el tema y lo declara en `report.json`. Devuelve el lienzo, la
rejilla (columnas, margen y medianil) y la escala tipografica, para que
lo que se coloque a mano despues use las mismas guias que
\`pbi_compose_page`.

Escribe en el informe (PBIR): conviene tener el proyecto CERRADO en
Power BI Desktop.

Input parameters:

- `request_id` (string)
- `system` (string, required)

Output parameters:

- `result` (object)

### `pbi_compose_page` (~426 tokens)

Compone una pagina entera sobre la rejilla del sistema de diseno.

Se describe la INTENCION y el servidor decide la geometria:

\- `title` / `subtitle`: banda de cabecera, con el tamano y el color del
  sistema y la altura minima que el texto necesita para no cortarse.
\- `kpis`: lista de campos (`"[Medida]"` o `{"field":..., "title":...}`).
  Se reparten la fila entera sin dejar huecos.
\- `hero`: el grafico protagonista.
  \`{"type":"lineChart", "category":"T[C]", "values":["[M]"], "title":...}`.
\- `supports`: los que van apilados a su derecha, mismo formato.
\- `detail`: tabla al pie. `{"values": [...], "title": ...}`.

La composicion es siempre la misma de arriba abajo, a proposito: la
coherencia entre paginas sale de que ninguna pueda inventarse su propio
orden. Si algo no cabe se dice con la cuenta hecha, en vez de encogerlo
hasta que no se lea.

El COLOR del texto sale del tema que el informe tiene puesto, no del
sistema: un informe solo admite un tema, y escribir el color del
sistema pintaba el titulo casi invisible en cuanto los dos no
coincidian. La geometria si es de la pagina. Si no cuadran se avisa.

\`dry_run=true` devuelve el spec con todas las posiciones y no escribe.
Aplica el mismo camino que `pbi_apply_page_spec`, asi que pasa por la
misma validacion y la misma transaccion.

Input parameters:

- `detail`
- `dry_run` (boolean)
- `hero`
- `kpis`
- `page_name` (string)
- `request_id` (string)
- `subtitle` (string)
- `supports`
- `system` (string, required)
- `title` (string, required)

Output parameters:

- `result` (object)

### `pbi_define_brief` (~352 tokens)

Escribe el BRIEF DE INTENCION del tablero: para que existe.

Es la pieza que gobierna a las demas: la propuesta de paginas, el
sistema de diseño y las auditorias leen este artefacto para servir a
un proposito en vez de deducirlo todo del modelo.

\**Las respuestas son del HUMANO, no tuyas.** Antes de llamar, pregunta
en conversacion: ¿para que quieres este tablero? ¿quien lo va a mirar?
¿que decisiones debe sostener? ¿como se va a ver (sala, escritorio,
PDF, movil)? Un brief inventado por el agente es peor que ninguno:
fija en un archivo con autoridad lo que nadie dijo.

\`delivery`: pantalla_sala | escritorio | lectura_pdf | movil — decide
el sistema de diseño recomendado (legibilidad fisica, no estetica).
\`critical_fields`: [{field, why, min?, max?}] — que campos son
criticos y sus umbrales; los usara el diagnostico de datos.

Se guarda como `pbi-brief.json` JUNTO al .pbip (fuera de .Report/
.SemanticModel, que Desktop reescribe al guardar): versionado con el
proyecto y editable en cualquier momento. Reescribirlo es normal:
los tableros cambian de proposito.

Input parameters:

- `audience` (string, required)
- `critical_fields`
- `decisions`
- `delivery`
- `key_questions`
- `non_goals`
- `purpose` (string, required)
- `request_id` (string)
- `update_cadence`

Output parameters:

- `result` (object)

### `pbi_get_brief` (~105 tokens)

Lee el brief de intencion del proyecto activo.

Devuelve `defined: false` si no existe —con las preguntas que hay que
hacerle al usuario para definirlo—, o el brief completo y el sistema
de diseño que recomienda. Consultalo ANTES de proponer paginas o
elegir sistema: es la diferencia entre servir al proposito del
tablero y deducirlo todo del modelo.

Input parameters:

- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_reflow_pages` (~318 tokens)

Reescala las paginas ya escritas al lienzo de otro sistema.

El camino de vuelta que faltaba. Aplicar un sistema cambia el tema del
informe, pero NO reescribe lo ya compuesto: las paginas se quedan con
el lienzo anterior —visuales fuera de limites, basura invisible que si
viaja al render— y con los colores que se cocieron al componerlas: un
titulo compuesto en tema oscuro queda BLANCO SOBRE BLANCO al pasar a
claro, sin que falle nada.

Esto hace las dos cosas: reescala cada visual proporcionalmente al
lienzo nuevo (acotandolo si no cabe) y recalcula el color de texto de
los elementos decorativos con el tema del sistema destino.

\**No recompone**: no se puede saber que intencion tenia cada visual, y
adivinarla seria peor. Si una pagina necesita otra estructura,
recomponla con `pbi_compose_page`. Esto la deja utilizable, no optima.

\`pages`: subconjunto opcional (id o nombre visible); por defecto todas.
\`dry_run=true` (por defecto) devuelve el plan visual por visual, con
cuales estaban ya fuera de limites, sin escribir nada.

Escribe en el informe (PBIR): requiere el proyecto CERRADO en Desktop.

Input parameters:

- `dry_run` (boolean)
- `pages`
- `request_id` (string)
- `system` (string, required)

Output parameters:

- `result` (object)

### `pbi_list_tables` (~194 tokens)

Lista tablas con columnas, tipos, visibilidad y conteos.

\`source`: 'live' (modelo abierto, por defecto) o 'pbip' (archivos TMDL).

\**Empieza por `detail='summary'`.** Devuelve nombre, visibilidad y
recuentos, sin la lista de columnas. Con `detail='full'` (por defecto,
por compatibilidad) un modelo de siete tablas ocupa ~28.000 caracteres y
uno corporativo puede llenar buena parte de la ventana de contexto en
una sola llamada.

\`tables`: acota a esas tablas por nombre. Es lo que se usa despues del
resumen para pedir el detalle solo de las que interesan. Un nombre que
no existe falla y devuelve los disponibles, en vez de una lista vacia.

Input parameters:

- `detail` (string)
- `source` (string)
- `tables`

Output parameters:

- `result` (object)

### `pbi_list_measures` (~145 tokens)

Lista medidas con tabla, expresion DAX, formato, descripcion y carpeta.

\**Empieza por `detail='summary'`.** Omite la expresion DAX, que es el
grueso del peso y rara vez hace falta para orientarse; para leer el DAX
de una medida concreta usa `pbi_get_object`, y para buscar dentro del
DAX, `pbi_search_model`. `detail='full'` sigue siendo el valor por
defecto por compatibilidad.

\`tables`: acota a las medidas de esas tablas.

Input parameters:

- `detail` (string)
- `source` (string)
- `tables`

Output parameters:

- `result` (object)

### `pbi_list_relationships` (~35 tokens)

Lista relaciones: tablas/columnas, cardinalidad, filtro cruzado y estado.

Input parameters:

- `source` (string)

Output parameters:

- `result` (object)

### `pbi_analyze_model_quality` (~64 tokens)

Detecta problemas tipicos del modelo (calidad).

Revisa medidas sin carpeta, DAX muy largo, relaciones bidireccionales/
inactivas, columnas calculadas, IDs visibles, ausencia de calendario, etc.

Input parameters:

- `source` (string)

Output parameters:

- `result` (object)

### `pbi_document_model` (~68 tokens)

Genera documentacion completa del modelo en Markdown.

Incluye resumen, tablas, columnas, medidas, relaciones, jerarquias, roles
(RLS) y advertencias de calidad. Guarda el archivo en outputs/.

Input parameters:

- `include_quality` (boolean)
- `source` (string)

Output parameters:

- `result` (object)

### `pbi_export_report_content` (~420 tokens)

Exporta el CONTENIDO del informe: los datos que muestra el tablero.

A diferencia de `pbi_export_excel` y `pbi_generate_pdf_report`, que
documentan el proyecto, esto exporta lo que el cliente ve. `select`
admite tres formas, combinables:

\- `pages`: ["Matriz de Riesgos"] -> la tabla que hay detras de CADA
  visual de esas paginas (por nombre visible o id interno).
\- `visuals`: ["15b0fc11e628..."] -> solo esos visuales.
\- `queries`: [{name, rows:["Tabla[Columna]"], values:["Medida"],
  filters:[{field, values, exclude?}], top_n?}] -> lo que el cliente
  declare, sin referirse a ningun visual.

Cada consulta se reconstruye a partir de los campos del visual, se
ejecuta en SOLO LECTURA contra el modelo en vivo y sale como una hoja
de Excel (`format`: `xlsx|pdf|both`).

Necesita el modelo en vivo: los datos solo existen en el motor, no en
el .pbip. Con `auto_open` abre el informe en Desktop si hace falta, y
se NIEGA a exportar si el modelo esta abierto pero sin procesar, en
vez de publicar un archivo en blanco. Con `dry_run` devuelve el DAX
que ejecutaria sin tocar el motor ni escribir nada.

Cada hoja declara con que filtros se saco y, sobre todo, cuales no se
pudieron aplicar. Los visuales sin consulta tabular -textos, imagenes,
formas- se listan aparte con el motivo.

Input parameters:

- `auto_open` (boolean)
- `dry_run` (boolean)
- `file_name` (string)
- `format` (string)
- `max_rows` (integer)
- `max_rows_pdf` (integer)
- `select` (object, required)
- `title` (string)

Output parameters:

- `result` (object)

### `pbi_export_excel` (~186 tokens)

Exporta la informacion disponible a un libro Excel verificado.

Crea hojas para resumen, tablas, columnas, medidas, relaciones y, si
hay PBIP activo, paginas, visuales y auditoria. `source` admite
\`auto|live|pbip`. Si se proporciona `query`, ejecuta DAX de solo lectura
contra Desktop y agrega `Datos_DAX`, declarando cualquier truncamiento.

El archivo se escribe en `outputs/excel/`, nunca dentro del PBIP. No
sobrescribe nombres existentes, neutraliza formulas inyectadas y vuelve
a abrir el XLSX antes de informar exito.

Input parameters:

- `file_name` (string)
- `include_audit` (boolean)
- `include_report` (boolean)
- `max_rows` (integer)
- `query` (string)
- `source` (string)

Output parameters:

- `result` (object)

### `pbi_generate_pdf_report` (~172 tokens)

Genera un PDF ejecutivo, tecnico o de auditoria con capturas.

Compone informacion existente del modelo, paginas, visuales y auditoria.
\`capture_paths` acepta hasta 20 PNG/JPEG, por ejemplo las rutas devueltas
por `pbi_validate_desktop_render`. No inventa graficas si no hay captura.

El PDF se reabre con pypdf y, cuando Poppler esta disponible, su primera
pagina se renderiza a PNG como prueba visual. Se guarda en `outputs/pdf/`.

Input parameters:

- `capture_paths`
- `file_name` (string)
- `include_audit` (boolean)
- `max_findings` (integer)
- `report_type` (string)
- `source` (string)
- `title` (string)

Output parameters:

- `result` (object)

### `pbi_sharepoint_list_folder` (~172 tokens)

Conecta con SharePoint Online y lista una carpeta mediante Graph.

\`site_url` es la URL HTTPS del sitio; `library` es el nombre o id de la
biblioteca documental; `folder_path` usa `/`. Sigue la paginacion de
Graph y puede recorrer subcarpetas con un limite total explicito.

Autenticacion app-only: las credenciales SOLO se leen de las variables
\`HORIZUN_PBI_MCP_SHAREPOINT_TENANT_ID`, `_CLIENT_ID` y `_CLIENT_SECRET`.
Nunca se aceptan secretos como parametros ni se devuelven tokens.

Input parameters:

- `folder_path` (string)
- `library` (string)
- `max_items` (integer)
- `recursive` (boolean)
- `site_url` (string, required)

Output parameters:

- `result` (object)

### `pbi_sharepoint_download_folder` (~187 tokens)

Descarga una carpeta de SharePoint a outputs/ de forma todo-o-nada.

Primero inventaria el lote y valida `max_files`, `max_total_mb` y el
filtro opcional `extensions` (por ejemplo `['xlsx','csv']`). Luego usa
URLs temporales emitidas por Graph, verifica tamano y SHA-256 y publica
el directorio completo bajo `outputs/sharepoint/`. Un fallo elimina el
staging y no deja una carpeta final parcial.

Es una descarga de solo lectura remota: no sube, mueve ni elimina nada
en SharePoint.

Input parameters:

- `extensions`
- `folder_path` (string)
- `library` (string)
- `max_files` (integer)
- `max_total_mb` (integer)
- `recursive` (boolean)
- `site_url` (string, required)

Output parameters:

- `result` (object)

### `pbi_model_summary` (~101 tokens)

Resumen compacto del modelo, pensado para leerlo de un vistazo.

Conteos, tablas con su tamano, medidas por tabla, columnas calculadas,
tablas desconectadas, relaciones bidireccionales y referencias rotas.
Es la primera tool que conviene llamar para orientarse en un modelo.
\`source`: 'live' (Desktop abierto) o 'pbip' (archivos TMDL).

Input parameters:

- `source` (string)

Output parameters:

- `result` (object)

### `pbi_search_model` (~98 tokens)

Busca objetos del modelo por nombre (y en el DAX de las medidas).

\`term`: texto a buscar, sin distinguir mayusculas.
\`kinds`: filtra por tipo — table, column, measure, hierarchy, role.
Para las medidas indica si coincidio el nombre o la expresion.

Input parameters:

- `kinds`
- `limit` (integer)
- `source` (string)
- `term` (string, required)

Output parameters:

- `result` (object)

### `pbi_get_object` (~77 tokens)

Devuelve un objeto del modelo con todo su detalle.

\`kind`: table | column | measure. Para una columna usa 'Tabla[Columna]'.
En una medida incluye ademas las referencias que aparecen en su DAX.

Input parameters:

- `kind` (string, required)
- `name` (string, required)
- `source` (string)

Output parameters:

- `result` (object)

### `pbi_measure_dependencies` (~98 tokens)

De que depende una medida y quien depende de ella.

Devuelve dependencias directas (medidas, columnas y referencias ROTAS),
el cierre transitivo sobre medidas hasta `depth`, y la lista de medidas
que la usan. Analisis lexico: detecta referencias escritas, no las
construidas dinamicamente.

Input parameters:

- `depth` (integer)
- `name` (string, required)
- `source` (string)

Output parameters:

- `result` (object)

### `pbi_column_dependencies` (~64 tokens)

Que usa una columna: medidas, columnas calculadas, relaciones y jerarquias.

Util antes de ocultar o eliminar una columna: dice si algo se rompe.

Input parameters:

- `column` (string, required)
- `source` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_list_hierarchies` (~33 tokens)

Lista las jerarquias del modelo con sus niveles y columnas.

Input parameters:

- `source` (string)

Output parameters:

- `result` (object)

### `pbi_list_roles` (~32 tokens)

Lista los roles de seguridad (RLS) y sus filtros por tabla.

Input parameters:

- `source` (string)

Output parameters:

- `result` (object)

### `pbi_list_perspectives` (~62 tokens)

Lista las perspectivas del modelo.

Requiere la capa EN VIVO: el lector TMDL de este proyecto no las extrae.
Si no hay ninguna, devuelve una lista vacia con la explicacion.

Input parameters:

- `source` (string)

Output parameters:

- `result` (object)

### `pbi_list_partitions` (~155 tokens)

Lista las particiones por tabla (modo de almacenamiento y origen).

Con `source='live'` se leen del motor (DMV `TMSCHEMA_PARTITIONS`):
tabla, nombre, tipo de origen (m, calculated...), modo (import,
directQuery, dual...), estado y hora del ultimo refresco. Aparte, en
\`expressions`, las consultas COMPARTIDAS (`TMSCHEMA_EXPRESSIONS`:
parametros y funciones M), que no pertenecen a ninguna tabla. Con
\`source='pbip'` se leen del TMDL en disco. El texto M no viaja aqui:
lo da `pbi_get_power_query`.

Input parameters:

- `source` (string)

Output parameters:

- `result` (object)

### `pbi_audit_model` (~119 tokens)

Audita el modelo semantico con reglas de identificador estable.

Cada hallazgo trae `rule`, `severity`, `object`, `evidence`,
\`recommendation` y `auto_fix_available`. Ninguna heuristica se presenta
como certeza: la evidencia acompana siempre al hallazgo.
\`rules`: subconjunto de reglas (ver pbi_list_audit_rules).
\`min_severity`: info | warning | error.

Input parameters:

- `min_severity` (string)
- `rules`
- `source` (string)

Output parameters:

- `result` (object)

### `pbi_list_audit_rules` (~28 tokens)

Catalogo de reglas de auditoria disponibles, con su dominio y severidad.

Output parameters:

- `result` (object)

### `pbi_create_measure` (~256 tokens)

Crea (o reemplaza con overwrite=true) una medida DAX.

Valida que la tabla exista y que la medida no exista salvo overwrite.
\`data_category`: opcional, p.ej. "ImageUrl" para medidas que devuelven un
data-URI SVG (Power BI las renderiza como imagen en tablas/matrices).
Devuelve el diff antes/despues.

mode='both' esta temporalmente deshabilitado bajo la politica estricta:
'live' necesita Power BI Desktop abierto y 'pbip' lo necesita cerrado,
asi que una sola llamada aplicaria solo uno de los dos destinos. Elige
'live' o 'pbip', o usa 'auto' y se mira el estado para elegir. Si estas
construyendo desde cero, 'auto' o 'pbip': el defecto es 'live' y exige
Desktop abierto.

Input parameters:

- `data_category`
- `description`
- `display_folder`
- `expression` (string, required)
- `format_string`
- `mode` (string)
- `name` (string, required)
- `overwrite` (boolean)
- `request_id` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_update_measure` (~175 tokens)

Actualiza una medida existente. Lo no especificado se conserva.

mode='both' esta temporalmente deshabilitado bajo la politica estricta:
'live' necesita Power BI Desktop abierto y 'pbip' lo necesita cerrado,
asi que una sola llamada aplicaria solo uno de los dos destinos. Elige
'live' o 'pbip', o usa 'auto' y se mira el estado para elegir. Si estas
construyendo desde cero, 'auto' o 'pbip': el defecto es 'live' y exige
Desktop abierto.

Input parameters:

- `description`
- `display_folder`
- `expression`
- `format_string`
- `mode` (string)
- `name` (string, required)
- `request_id` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_delete_measure` (~161 tokens)

Elimina una medida. Operacion destructiva: requiere confirm=true.

mode='both' esta temporalmente deshabilitado bajo la politica estricta:
'live' necesita Power BI Desktop abierto y 'pbip' lo necesita cerrado,
asi que una sola llamada aplicaria solo uno de los dos destinos. Elige
'live' o 'pbip', o usa 'auto' y se mira el estado para elegir. Si estas
construyendo desde cero, 'auto' o 'pbip': el defecto es 'live' y exige
Desktop abierto.

Input parameters:

- `confirm` (boolean)
- `mode` (string)
- `name` (string, required)
- `request_id` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_rename_measure` (~253 tokens)

Renombra una medida actualizando TODO lo que la referencia.

Renombrar a mano rompe el informe en silencio: cualquier visual o
medida que apuntara al nombre viejo abre y sale VACIO, sin error. Esta
tool compila primero y escribe en UNA transaccion: la cabecera TMDL,
las expresiones DAX de otras medidas que usan `[old_name]`, y los
visual.json del informe. Devuelve la lista de lo tocado.

Limite honesto: las referencias DAX CALIFICADAS (`Tabla[old]`) no se
reescriben —pueden ser una columna homonima de otra tabla, y adivinar
seria corromper—; si quedan, salen en `warnings` con su ubicacion,
igual que las de bookmarks y filtros.

Solo `pbip` (escribe modelo E informe a la vez: exige Desktop
CERRADO). `dry_run=true` (defecto) devuelve el plan sin escribir.

Input parameters:

- `dry_run` (boolean)
- `new_name` (string, required)
- `old_name` (string, required)
- `request_id` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_create_calculated_column` (~174 tokens)

Crea una columna calculada (DAX) en una tabla del modelo .pbip.

\`data_type`: string | int64 | double | decimal | boolean | dateTime.
\`summarize_by`: como se agrega por defecto; 'none' para clasificaciones
y textos, que es lo que casi siempre se quiere.

Escribe en TMDL: requiere el proyecto CERRADO en Power BI Desktop.

Input parameters:

- `data_type` (string)
- `description`
- `display_folder`
- `expression` (string, required)
- `format_string`
- `is_hidden` (boolean)
- `name` (string, required)
- `overwrite` (boolean)
- `request_id` (string)
- `summarize_by` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_create_calculated_table` (~205 tokens)

Crea una tabla calculada (DAX) en el modelo .pbip.

Resuelve el caso tipico de tener diez metricas guardadas en diez
COLUMNAS en vez de en filas: con una tabla calculada que las dinamice
se obtiene una matriz de verdad, en lugar de escribir una medida por
columna.

TMDL exige declarar las columnas y no se pueden adivinar leyendo el
DAX: si no pasas `columns`, se deducen EJECUTANDO la expresion contra
el modelo abierto en Power BI Desktop y leyendo el esquema que devuelve
el motor. Para eso hace falta el modelo abierto Y seleccionado.

Escribe en TMDL: requiere el proyecto CERRADO en Power BI Desktop.

Input parameters:

- `columns`
- `description`
- `expression` (string, required)
- `name` (string, required)
- `overwrite` (boolean)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_add_table_from_file` (~767 tokens)

Carga un archivo al modelo como lo haria una persona: abrir, transformar, cargar.

El mismo recorrido de Power Query —Obtener datos, promover encabezados,
cambiar tipos, Cargar— pero escrito directo en el proyecto. Los pasos
de la consulta se llaman como los pone Power BI ('Origen',
'Encabezados promovidos', 'Tipo cambiado'), asi que se puede abrir y
editar en el editor sin que desentone.

Admite .csv, .txt, .tsv, .xlsx, .xlsm, .json, .html/.htm y '.xls'.
Sin dependencias nuevas: el .xlsx se lee como lo que es, un zip con
XML, y el HTML con `html.parser` de la biblioteca estandar.

Un '.xls' NO se toma por la extension: se mira la firma real del
archivo. En la practica, casi ningun '.xls' que exportan los ERPs es
el binario OLE2 que la extension promete -- es una tabla HTML con esa
extension porque Excel la abre igual. Si de verdad es OLE2 (Excel
97-2003), se rechaza con un mensaje claro en vez de leerlo mal; si es
un .xlsx renombrado, se lee como .xlsx.

Para HTML: si el archivo trae varias `<table>`, se elige la mas
grande y se avisa (usa `table_id` para elegir una en concreto, el
\`id` HTML de la tabla). Los encabezados solo se promueven si la
tabla usa `<th>`; un reporte sin esa marca (el caso normal en un
reporte de ERP, donde la fila 1 es el titulo del reporte, no un
encabezado) carga con columnas 'Column1', 'Column2'...

\**La cultura se deduce del archivo**, mirando como escribe los
decimales, y se emite SIEMPRE explicita en la consulta. Asumir la del
modelo es lo que convierte 10527.52 en diez millones sin que nada
falle: un informe que abre, pinta y miente. `culture` permite forzarla.

Lo escrito se valida antes de darlo por bueno: si el TMDL generado no
pasara `pbi_validate_tmdl`, se aborta en vez de dejar un proyecto que
no abre.

\**Filas de basura antes del encabezado**: es el patron de export mas
comun de un ERP -fila 1 con el titulo del reporte y el resto de la
fila vacia, encabezado real en la fila 2-. Por defecto se AUTODETECTA
la primera fila que pu…

Input parameters:

- `culture` (string)
- `description` (string)
- `dry_run` (boolean)
- `overwrite` (boolean)
- `path` (string, required)
- `request_id` (string)
- `sheet` (string)
- `skip_rows`
- `table_id` (string)
- `table_name` (string)

Output parameters:

- `result` (object)

### `pbi_set_storage_mode` (~166 tokens)

Cambia el modo de almacenamiento de una tabla: import | directQuery | dual.

Con directQuery el dato se consulta al origen en cada interaccion y
desaparece el refresco, pero NO es un interruptor inocuo: la consulta M
tiene que ser plegable al origen, las columnas y tablas calculadas
dejan de estar disponibles, y cada visual pasa a ser una consulta al
servidor.

Devuelve el modo anterior y cuantas particiones cambiaron, para poder
deshacerlo sabiendo exactamente que se toco.

Escribe en TMDL: requiere el proyecto CERRADO en Power BI Desktop.

Input parameters:

- `mode` (string, required)
- `request_id` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_create_relationship` (~186 tokens)

Crea una relacion entre dos columnas del modelo .pbip.

Por defecto muchos-a-uno con filtro en un sentido: es lo que crea Power
BI y lo unico que no introduce ambiguedad. `cross_filtering`
'bothDirections' resuelve casos concretos y complica el modelo entero,
asi que conviene justificarlo.

Escribe en TMDL: requiere el proyecto CERRADO en Power BI Desktop.

Input parameters:

- `cross_filtering` (string)
- `from_cardinality` (string)
- `from_column` (string, required)
- `from_table` (string, required)
- `is_active` (boolean)
- `name`
- `overwrite` (boolean)
- `request_id` (string)
- `to_cardinality` (string)
- `to_column` (string, required)
- `to_table` (string, required)

Output parameters:

- `result` (object)

### `pbi_create_hierarchy` (~143 tokens)

Crea una jerarquia sobre columnas de la misma tabla.

\`levels`: nombres de columna de MAYOR a MENOR granularidad (p.ej.
['Anio','Mes','Dia']). El orden es el de profundizacion y se respeta
tal cual: no se ordena ni se deduplica porque es informacion.

Escribe en TMDL: requiere el proyecto CERRADO en Power BI Desktop.

Input parameters:

- `description`
- `display_folder`
- `levels` (array, required)
- `name` (string, required)
- `overwrite` (boolean)
- `request_id` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_set_column_visibility` (~167 tokens)

Oculta o muestra una columna del modelo (p.ej. ocultar columnas de ID).

mode='both' esta temporalmente deshabilitado bajo la politica estricta:
'live' necesita Power BI Desktop abierto y 'pbip' lo necesita cerrado,
asi que una sola llamada aplicaria solo uno de los dos destinos. Elige
'live' o 'pbip', o usa 'auto' y se mira el estado para elegir. Si estas
construyendo desde cero, 'auto' o 'pbip': el defecto es 'live' y exige
Desktop abierto.

Input parameters:

- `column` (string, required)
- `hidden` (boolean)
- `mode` (string)
- `request_id` (string)
- `table` (string, required)

Output parameters:

- `result` (object)

### `pbi_hide_columns` (~256 tokens)

Oculta/muestra VARIAS columnas como un solo lote.

\`columns`: lista de {"table": ..., "column": ...}.

Valida todas las entradas antes de escribir: si alguna tabla o columna
no existe, no se modifica nada y el error indica el indice. Los archivos
TMDL se escriben en una sola transaccion y el modelo en vivo con un solo
SaveChanges. `count` es el numero de entradas SOLICITADAS (incluidos
duplicados); `results` trae una entrada por cada una, en el mismo orden.

mode='both' esta temporalmente deshabilitado bajo la politica estricta:
'live' necesita Power BI Desktop abierto y 'pbip' lo necesita cerrado,
asi que una sola llamada aplicaria solo uno de los dos destinos. Elige
'live' o 'pbip', o usa 'auto' y se mira el estado para elegir. Si estas
construyendo desde cero, 'auto' o 'pbip': el defecto es 'live' y exige
Desktop abierto.

Input parameters:

- `columns` (array, required)
- `hidden` (boolean)
- `mode` (string)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_set_relationship_direction` (~180 tokens)

Cambia el filtro cruzado de una relacion.

\`direction`: 'single' (una direccion, recomendado) o 'both' (bidireccional).
OJO: cambiar a 'single' puede alterar totales que dependian de la bidireccional;
verifica el informe despues.

No confundir `direction='both'` (bidireccional, valido) con `mode='both'`,
que esta temporalmente deshabilitado bajo la politica estricta: 'live'
necesita Power BI Desktop abierto y 'pbip' lo necesita cerrado, asi que
una sola llamada aplicaria solo uno de los dos destinos.

Input parameters:

- `direction` (string)
- `from_table` (string, required)
- `mode` (string)
- `request_id` (string)
- `to_table` (string, required)

Output parameters:

- `result` (object)

### `pbi_disable_auto_date_time` (~88 tokens)

Activa/desactiva 'Auto fecha y hora' (solo modo pbip).

Desactivarlo aligera el modelo: al reabrir el .pbip, Power BI elimina las
tablas de fecha automaticas (LocalDateTable_*). Requiere proyecto .pbip activo.

Input parameters:

- `enabled` (boolean)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_add_table_from_source` (~413 tokens)

Crea una tabla apuntando a una BASE DE DATOS o API externa.

\`source`: sqlserver | postgresql | odata | web_json.
\- sqlserver/postgresql: `server` + `database` + (`schema`/`source_table`
  o, solo en sqlserver, `native_query` con plegado activado).
\- odata: `url` del ENTITY SET (…/odata/Presupuestos), no la raiz.
\- web_json: `url` que devuelve un array de objetos; `json_path`
  desciende hasta el (["data","rows"]). Tipado con cultura en-US fija:
  JSON escribe numeros sin cultura, y la del sistema es el bug del
  10527.52 que se vuelve diez millones.

\`columns` ([{name, type}]) es OBLIGATORIO: sin credenciales no se
puede leer el esquema de la fuente, y las columnas no se inventan.

\**La verdad de las credenciales, por delante**: la consulta queda
escrita y validada, pero el PRIMER refresh lo completa una persona en
Desktop —pedira credenciales y nivel de privacidad, que viven en
Desktop, no en el .pbip—. Hasta entonces la tabla existe sin datos y
este servidor no puede verificar la conexion. Prometer otra cosa
seria mentir.

Escribe TMDL: requiere el proyecto CERRADO en Desktop.

Input parameters:

- `columns` (array, required)
- `database` (string)
- `description` (string)
- `dry_run` (boolean)
- `json_path`
- `native_query`
- `overwrite` (boolean)
- `request_id` (string)
- `schema` (string)
- `server` (string)
- `source` (string, required)
- `source_table` (string)
- `table_name` (string, required)
- `url` (string)

Output parameters:

- `result` (object)

### `pbi_get_power_query` (~334 tokens)

Lee la consulta Power Query (M) de una particion o expresion.

En un .pbip el M no tiene archivo propio: vive dentro del TMDL, en la
\`partition` de cada tabla y en `expressions.tmdl`. Esta tool lo saca
tal cual, con su SHA-256, que es lo que despues acepta
\`pbi_update_power_query` como `expected_sha256` para no escribir sobre
una version que ya cambio.

\`source`: 'pbip' (TMDL en disco), 'live' (motor de Desktop, DMV
\`TMSCHEMA_PARTITIONS` / `TMSCHEMA_EXPRESSIONS`) o vacio: pbip si hay
proyecto activo, si no el modelo vivo. Con `live` la respuesta trae
\`file: null` y `source: 'live'`: es la definicion cargada en Desktop,
no el archivo.

\`table` + `name` seleccionan una particion; `name` con
\`kind='expression'`, una expresion con nombre. `object_name` es un
alias de `name` (si llegan distintos, conflicto). Si la seleccion
queda ambigua o no existe, el error trae la lista de candidatos.

Solo lectura, y solo lectura de TEXTO: nadie ha ejecutado esta
consulta. No hay motor M fuera de Power BI Desktop.

Input parameters:

- `kind` (string)
- `name` (string)
- `object_name` (string)
- `source` (string)
- `table` (string)

Output parameters:

- `result` (object)

### `pbi_update_power_query` (~319 tokens)

Reemplaza ENTERA la consulta M de una particion o expresion.

No hay edicion parcial ni sustitucion por expresion regular: se
localiza el bloque por su estructura y se cambia completo. `dry_run`
viene ACTIVADO: la primera llamada enseña lo que se escribiria sin
tocar el disco, sin backup y sin journal.

\`expected_sha256` (el que devuelve `pbi_get_power_query`) rechaza la
escritura si el texto cambio desde que lo leiste, en vez de pisar el
trabajo de otro.

Escribe TMDL: exige proyecto .pbip y Power BI Desktop CERRADO. Va con
backup, journal, transaccion, relectura y rollback; si el modelo queda
con errores TMDL que antes no tenia, se revierte entero.

La respuesta distingue lo comprobado de lo no comprobado:
\`parse_checked` y `tmdl_load_checked` dicen que el TMDL se lee y que
el serializador oficial lo acepta; `m_engine_checked` y
\`refresh_checked` son SIEMPRE false. Que el archivo parsee no
significa que la consulta cargue: eso solo lo dice un refresh.

Input parameters:

- `dry_run` (boolean)
- `expected_sha256` (string)
- `kind` (string)
- `m` (string, required)
- `name` (string)
- `request_id` (string)
- `table` (string)

Output parameters:

- `result` (object)

### `pbi_health_check` (~73 tokens)

Estado general del servidor: dependencias, DLLs, sesion y proyecto.

Solo lectura. Es lo primero que conviene llamar: dice si la capa EN VIVO
esta disponible, si hay un proyecto .pbip abierto y si algo requiere
atencion (sesion obsoleta, journals pendientes).

Output parameters:

- `result` (object)

### `pbi_capabilities` (~71 tokens)

Que puede hacerse AHORA MISMO, y que no, con el motivo.

Un agente deberia consultarla antes de planificar: dice si la capa en
vivo esta disponible, si se puede escribir en el .pbip, y que
capacidades dependen de la version del motor.

Output parameters:

- `result` (object)

### `pbi_session_info` (~58 tokens)

Detalle de la sesion: modelo activo, proyecto activo y su frescura.

Distingue una sesion valida de una obsoleta (`stale`) o de otra que
ocupo el mismo puerto (`mismatch`).

Output parameters:

- `result` (object)

### `pbi_list_pending_journals` (~75 tokens)

Lista los journals del proyecto activo.

Un journal `pending` es el de una operacion que ni se confirmo ni se
revirtio (el proceso murio en medio). Contiene los originales.
\`only_pending=false` lista tambien los ya cerrados.

Input parameters:

- `only_pending` (boolean)

Output parameters:

- `result` (object)

### `pbi_inspect_journal` (~81 tokens)

Inspecciona un journal y lo compara con el estado ACTUAL del proyecto.

Solo lectura: no restaura nada. Por cada archivo dice si sigue como el
original, si hay respaldo disponible y cual fue su desenlace.
\`journal`: ruta devuelta por pbi_list_pending_journals.

Input parameters:

- `journal` (string, required)

Output parameters:

- `result` (object)

### `pbi_recover_from_journal` (~178 tokens)

Restaura los originales guardados en un journal. DESTRUCTIVA.

Sin `confirm` devuelve la VISTA PREVIA: que archivos se restaurarian,
cual es su estado actual y si alguien los cambio despues.

Estados: `recoverable`, `recovered`, `conflict`, `incomplete`,
\`corrupted`. Si un archivo cambio despues de la transaccion, se rechaza
con `recovery_conflict` en vez de pisar ese trabajo; `force_conflict`
lo aplica de todas formas.

Cada archivo se verifica byte a byte tras restaurarlo, y se recrean los
directorios padre que hubieran desaparecido.

Input parameters:

- `confirm` (boolean)
- `force_conflict` (boolean)
- `journal` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_purge_backups` (~163 tokens)

Aplica la politica de retencion a los backups. DESTRUCTIVA.

Sin `confirm` devuelve el MANIFIESTO de lo que se eliminaria, sin tocar
nada. Solo se borran directorios de journal reconocibles (con su
\`manifest.json`) dentro de la carpeta de backups del proyecto activo:
nunca un archivo suelto, ni un enlace simbolico, ni una raiz amplia.

Se conserva siempre el journal mas reciente y TODOS los pendientes:
un journal pendiente guarda los unicos originales de una transaccion
que no llego a cerrarse.

Input parameters:

- `confirm` (boolean)
- `days` (integer)
- `max_journals` (integer)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_plan_change` (~103 tokens)

Calcula un PLAN sin aplicar nada, y devuelve un `plan_token`.

\`operation`: una de las que lista pbi_capabilities en
\`planned_operations`. `arguments`: los mismos que aceptaria la tool.

El plan incluye el diff por archivo y una huella del estado sobre el que
se calculo. Si el proyecto cambia despues, pbi_apply_plan lo rechaza.

Input parameters:

- `arguments` (object, required)
- `operation` (string, required)

Output parameters:

- `result` (object)

### `pbi_apply_plan` (~207 tokens)

Aplica un plan calculado con pbi_plan_change o con un dry_run.

Verifica que el proyecto siga en el estado sobre el que se planifico; si
cambio, rechaza el plan en vez de aplicar algo distinto de lo aprobado.

\`expected_operation` es opcional: si lo indicas, el plan solo se aplica
si fue generado para esa operacion (`plan_operation_mismatch` si no).

\**Exige `confirm=true`.** Hasta 2.0.0 el default era `True`, o sea que
omitir el parametro APLICABA: un gate que viene abierto no es un gate, y
ademas rompia la simetria con las otras ocho tools con `confirm`, que es
justo la inconsistencia que hace que un agente generalice mal.

Input parameters:

- `confirm` (boolean)
- `expected_operation` (string)
- `plan_token` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_propose_dashboard` (~156 tokens)

Mira el modelo y PROPONE varios diseños distintos, con su porque.

A diferencia de pbi_page_building_blocks, que entrega el inventario y
deja el diseño en manos de quien pregunta, esto clasifica lo que hay
—que columna es un estado, cual una fecha, cuales forman una familia de
metricas comparables— y devuelve paginas completas con un spec listo
para aplicar.

Devuelve tambien `blockers`: lo que hay que resolver ANTES de construir
(p.ej. un modelo sin medidas, donde todo visual caeria en sumas
implicitas), y `themes` con las paletas disponibles.

Usalo para ofrecer opciones al usuario en vez de decidir por el.

Output parameters:

- `result` (object)

### `pbi_page_building_blocks` (~73 tokens)

Entrega el material para diseniar una hoja: modelo (tablas/medidas/columnas),
catalogo de visuales existentes (reutilizables como plantilla), canvas y paginas.

Usa esto ANTES de proponer una hoja: te dice que campos y tipos de visual hay.

Output parameters:

- `result` (object)

### `pbi_preview_spec_html` (~80 tokens)

Genera una MAQUETA HTML de una hoja propuesta (sin escribir nada al .pbip).

\`spec`: {page_name, canvas?, layout?, visuals:[{type,title,fields,position?}]}.
Devuelve la ruta del HTML (abrelo en el navegador para revisar el diseno).

Input parameters:

- `spec` (object, required)

Output parameters:

- `result` (object)

### `pbi_export_page_html` (~38 tokens)

Exporta una MAQUETA HTML de una pagina EXISTENTE (layout + campos de cada visual).

Input parameters:

- `page` (string, required)

Output parameters:

- `result` (object)

### `pbi_create_page_from_spec` (~113 tokens)

Crea una hoja (pagina) PBIR completa a partir de un spec.

\`spec`: {page_name, canvas?, layout?(grid|dashboard|executive_summary),
         visuals:[{type,title,fields:{rol:refs},position?}]}.
Clona visuales existentes del mismo tipo como plantilla. Hace backup.
Omite 'position' en un visual para que se auto-acomode con 'layout'.

Input parameters:

- `request_id` (string)
- `spec` (object, required)

Output parameters:

- `result` (object)

### `pbi_open_pbip_project` (~88 tokens)

Abre un proyecto .pbip y lo marca como proyecto activo.

Detecta carpetas .SemanticModel (TMDL) y .Report (PBIR) y devuelve un
resumen con advertencias (p.ej. si el informe no usa PBIR).
\`path`: ruta al archivo .pbip o a su carpeta.

Input parameters:

- `path` (string, required)

Output parameters:

- `result` (object)

### `pbi_prepare_project` (~331 tokens)

Deja listo EXACTAMENTE el proyecto que le pasas, y dice cual es.

Es el punto de entrada de una sesion de trabajo. Acepta las tres cosas
que una persona tiene a mano y las resuelve con una sola politica:

\- un `.pbip`: se valida y se activa esa ruta, tal cual;
\- un `.pbix`: se convierte ESE archivo y se activa el `.pbip` que
  produjo la conversion (no uno que se le parezca en la carpeta);
\- una carpeta: solo se resuelve si contiene UN candidato. Con dos
  \`.pbip` falla con `ambiguous_pbip_project` y te los enumera, porque
  elegir el primero por orden alfabetico es como se acaba editando el
  proyecto equivocado con la respuesta en verde.

La ruta que pasas gana siempre: sobre el proyecto activo, sobre la
sesion restaurada y sobre lo que tenga abierto Power BI Desktop. Si
algo falla, el proyecto activo se queda como estaba.

La respuesta trae `requested_path`, `resolved_path`,
\`selection_reason`, `path_match`, `previous_active_project` y
\`active_project`, para que la decision sea auditable sin repetirla.

\`open_result=true` abre el resultado en Desktop y comprueba que la
ventana sirve ese archivo.

Input parameters:

- `open_result` (boolean)
- `out_dir` (string)
- `overwrite` (boolean)
- `path` (string, required)
- `project_name` (string)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_validate_pbip_project` (~136 tokens)

Valida a fondo el proyecto .pbip activo (estructura, PBIR, TMDL).

Incluye `references`: cada Measure/Column que un visual.json o su
filterConfig citan, cruzada contra el TMDL real. El esquema PBIR y la
sintaxis TMDL pueden pasar limpios por separado con un visual que
apunta a una medida borrada -- Desktop lo resuelve en silencio a
nada, sin marca visible. Solo corre si el TMDL es valido (comparar
contra un modelo que no abre es ruido, no una comprobacion).

Output parameters:

- `result` (object)

### `pbi_create_pbip_project` (~245 tokens)

Crea un proyecto .pbip vacio pero valido, y lo deja activo.

Es el punto de partida para armar un tablero solo con rutas de
archivos: crear el proyecto, cargarle los datos con
pbi_add_table_from_file y componer las paginas, sin abrir Power BI
Desktop hasta el final.

Escribe el minimo que Power BI acepta —informe y modelo semantico
apuntandose entre si— con la referencia en ruta RELATIVA: una absoluta
ataria el proyecto a esta maquina. Incluye una pagina, porque un
informe sin ninguna no abre.

No declara `sourceQueryCulture` a proposito: la cultura se fija en cada
consulta, que es lo unico que no obliga a suponer como escribe los
decimales cada origen.

Input parameters:

- `culture` (string)
- `height` (integer)
- `name` (string, required)
- `open_project` (boolean)
- `out_dir` (string, required)
- `overwrite` (boolean)
- `page_name` (string)
- `request_id` (string)
- `width` (integer)

Output parameters:

- `result` (object)

### `pbi_validate_tmdl` (~279 tokens)

Comprueba si un modelo TMDL abrira, sin abrir Power BI Desktop.

Dos capas. Un lint estatico que caza las trampas que solo se veian al
abrir (una propiedad de tabla colocada despues de sus hijos, un
comentario '///' sobre una relacion, una medida que se llama como una
columna de su tabla, medidas duplicadas, referencias rotas) y, si estan
las DLL, un parseo con el MISMO serializador que usa Power BI.

Cada hallazgo trae `rule`, `severity`, el archivo y la linea. Si el
parseo no se pudo ejecutar se dice (`parse_checked: false`) en vez de
darlo por bueno.

Hay fallos que NINGUN analisis estatico ve porque dependen de los datos
—un blanco en el lado 'uno' de una relacion, un separador decimal mal
interpretado—. Salen en `limitations`: para esos hay que refrescar.

\`path`: carpeta `definition` del modelo, la carpeta `.SemanticModel` o
el `.pbip`. Si se omite, el proyecto activo.
\`use_tom=False`: solo el lint estatico, sin tocar las DLL.

Input parameters:

- `path` (string)
- `use_tom` (boolean)

Output parameters:

- `result` (object)

### `pbi_backup_pbip_project` (~68 tokens)

Crea un backup con timestamp del proyecto .pbip activo.

\`mode`: folder | zip. `scope`: report | model | both. Devuelve la ruta.

Input parameters:

- `mode` (string)
- `request_id` (string)
- `scope` (string)

Output parameters:

- `result` (object)

### `pbi_refresh_model` (~454 tokens)

Refresca el modelo LOCAL abierto en Power BI Desktop (no el Service).

\`type`: full | calculate | clear_values (tambien automatic | data_only).
\`tables`: lista opcional de tablas a refrescar; si se omite, todo el modelo.
Los errores de credenciales/origen se reportan.

\`timeout_seconds` (600 por defecto, `0` lo desactiva): un refresh
lanzado por XMLA **no puede mostrar el dialogo de credenciales** de
Desktop, asi que un origen sin credenciales guardadas deja al motor
esperando para siempre y no hay ninguna ventana que cerrar. Al
agotarse el plazo se pide la cancelacion al motor y se devuelve
\`refresh_timeout` enumerando los origenes que REQUIEREN credenciales
\-no si las tienen: eso Desktop no lo expone- y si la cancelacion se
confirmo o el comando pudo quedar corriendo.

Devuelve estado, duracion y **`rows_by_table`**: cuantas filas quedaron
en cada tabla refrescada. Un refresh puede terminar en 'ok' y haber
cargado CERO filas -credenciales que devuelven vacio, un filtro de
fecha que no alcanza nada, un origen que cambio de esquema-, asi que
las tablas vacias salen ademas en `warnings`. Si no se pudo contar, se
dice; no se inventa el numero.

En un proyecto **.pbip los datos NO se guardan**: viven en la sesion de
Desktop y al reabrir hay que refrescar otra vez. Lo que persiste al
guardar es la definicion (TMDL + PBIR).

\**Exige `confirm=true` desde 2.0.0.** Hasta entonces era la unica tool
\`destructiveHint` sin confirmacion junto con `pbi_open_and_refresh`: un
agente que decide por «¿tiene `confirm`?» no veia nada que preguntar.

Input parameters:

- `confirm` (boolean)
- `request_id` (string)
- `tables`
- `timeout_seconds`
- `type` (string)

Output parameters:

- `result` (object)

### `pbi_open_and_refresh` (~481 tokens)

Abre el proyecto en Power BI Desktop y lo refresca, en una llamada.

Es la secuencia real de trabajo y siempre eran dos llamadas de unos
catorce segundos cada una, porque un `.pbip` recien abierto trae el
modelo SIN DATOS: abrirlo sin refrescar no sirve para comprobar nada.

\`path` y `pbip_path` son el mismo parametro -el segundo es como lo
llama `pbi_session_info`-, y se pueden omitir los dos: entonces se usa
el proyecto .pbip activo.

Devuelve lo mismo que las dos por separado, incluido `rows_by_table`.
Si el archivo ya estaba abierto se reutiliza esa sesion (`reuse_open`).

Si el refresh falla, la ventana se DEJA ABIERTA: ya cargo bien, y
cerrarla borraria justo el contexto que hace falta para ver por que
fallo. Sale en `desktop_left_open`.

\`page` y `fit_to_page` (opcionales) eligen, DESPUES de refrescar, la
pestaña de esa pagina y la vista "Ajustar a la pagina" en la propia
ventana, por UI Automation, sin tocar `pages.json`. Mueven la ventana
igual que el refresh la vacia: los cubre el mismo `confirm=true`. La
respuesta trae `navigation` con `verified` por accion y, en el zoom,
\`verified_means` con lo que esa prueba alcanza: que el nivel de zoom
cambio, no que el modo resultante sea el pedido. Lo que no se pudo
demostrar se dice en `warnings`, no se da por hecho.

\**Exige `confirm=true` desde 2.0.0.** Abre una aplicacion y refresca:
los dos efectos son visibles y el segundo descarta lo que hubiera en
memoria sin guardar.

Input parameters:

- `confirm` (boolean)
- `fit_to_page` (boolean)
- `page`
- `path`
- `pbip_path`
- `project_path`
- `refresh_timeout_seconds`
- `request_id` (string)
- `reuse_open` (boolean)
- `tables`
- `timeout` (integer)
- `type` (string)

Output parameters:

- `result` (object)

### `pbi_add_image_resource` (~129 tokens)

Incrusta una imagen en el informe y la deja lista para usar.

La copia a StaticResources y la declara en report.json: sin las dos
cosas Power BI no la encuentra y el visual sale vacio sin dar ningun
error. Devuelve `item_name`, que es lo que se le pasa al visual de tipo
'image' en options.resource.

\`path`: archivo local (.png .jpg .gif .bmp .svg).

Input parameters:

- `name`
- `overwrite` (boolean)
- `path` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_list_report_resources` (~69 tokens)

Recursos del informe: declarados, en disco y los que no cuadran.

Un archivo sin declarar no lo encuentra Power BI; una declaracion sin
archivo deja el visual vacio. Los dos casos son invisibles al abrir el
informe, asi que se listan aparte.

Output parameters:

- `result` (object)

### `pbi_create_bookmark` (~180 tokens)

Crea un marcador: un estado del informe al que volver con un boton.

Escribe el archivo del marcador Y lo mete en el indice: sin el indice
Power BI no lo muestra aunque el archivo exista.

\`page`: id o titulo de la pagina que activara. `filters`: filtros a
guardar, con la misma forma que en un page spec. `target_visuals`
limita a que visuales afecta. Devuelve `usage`, listo para pasarselo a
un boton con action='bookmark'.

Input parameters:

- `display_name` (string, required)
- `filters`
- `name`
- `overwrite` (boolean)
- `page` (string, required)
- `request_id` (string)
- `suppress_data` (boolean)
- `suppress_display` (boolean)
- `target_visuals`

Output parameters:

- `result` (object)

### `pbi_list_bookmarks` (~59 tokens)

Marcadores del informe, y los que no cuadran entre indice y disco.

Un marcador que no esta en el indice no se muestra; una entrada del
indice sin archivo rompe el panel. Los dos casos son mudos al abrir.

Output parameters:

- `result` (object)

### `pbi_delete_bookmark` (~51 tokens)

Borra un marcador y lo quita del indice. Destructiva: confirm=true.

Input parameters:

- `confirm` (boolean)
- `name` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_list_themes` (~104 tokens)

Temas de informe disponibles, con su paleta y para que sirve cada uno.

Devuelve, por tema: el escenario de uso, el color de fondo, los colores
de serie EN SU ORDEN (el orden es lo que garantiza que dos series
contiguas se distingan tambien con daltonismo) y los colores de estado.

Usalo para PROPONER un esquema antes de construir, en vez de decidirlo
por el usuario.

Output parameters:

- `result` (object)

### `pbi_apply_theme` (~416 tokens)

Aplica un tema de colores al informe .pbip activo.

Escribe el JSON del tema en StaticResources/RegisteredResources y lo
declara en report.json (themeCollection + resourcePackages). Sin las
tres cosas Power BI Desktop lo ignora en silencio.

\`preset`: ver pbi_list_themes. `data_colors`: sustituye la paleta de
series por la tuya (#RRGGBB); ojo, entonces el orden deja de estar
verificado contra daltonismo. `name`: nombre visible del tema.

\`theme_json`: un tema COMPLETO tuyo (el objeto JSON), que se escribe
tal cual — para tipografia corporativa o formatos que los presets no
cubren, sin tener que sobrescribir el archivo a mano despues. Cuando lo
pasas, `preset` no se usa; `data_colors` y `fonts` se aplican encima.

\`fonts`: {'title'|'body'|'callout': 'Familia'} fija las fuentes via
textClasses, sobre el preset o sobre tu theme_json.

\`patch`: cambios PARCIALES sobre el tema que el informe YA tiene, sin
reenviar el tema completo (p.ej. {"visualStyles": {...}} para retocar
un estilo). Los dicts se fusionan en profundidad; las listas se
reemplazan enteras. Con `patch`, el resto de parametros no se usa y
hace falta que el informe ya tenga un tema propio.

Si el informe ya tenia un tema con contenido DISTINTO (p.ej. editado a
mano), se reemplaza avisandolo en `warnings`; la version anterior queda
recuperable en el backup de la transaccion.

Requiere el proyecto CERRADO en Power BI Desktop.

Input parameters:

- `data_colors`
- `fonts`
- `name`
- `patch`
- `preset` (string)
- `request_id`
- `theme_json`

Output parameters:

- `result` (object)

### `pbi_get_visual` (~84 tokens)

Definicion completa y normalizada de un visual.

Devuelve tipo, posicion, orden Z, titulo, campos por rol, medidas y
columnas referenciadas, si tiene formato propio, sus filtros, y la
definicion cruda por si hace falta inspeccionarla.

Input parameters:

- `page` (string, required)
- `visual_id` (string, required)

Output parameters:

- `result` (object)

### `pbi_report_capabilities` (~65 tokens)

Version PBIR observada, tema, custom visuals y tipos clonables.

Solo se pueden crear visuales de tipos ya presentes en el informe: se
clona una estructura real en vez de inventarla. Esta tool dice cuales
hay disponibles antes de intentarlo.

Output parameters:

- `result` (object)

### `pbi_duplicate_visual` (~122 tokens)

Duplica un visual conservando campos, formato y filtros.

Solo se regenera el identificador, que debe ser unico. La copia se
desplaza `offset_x`/`offset_y` para que no quede tapando al original.
\`target_page` permite copiarlo a otra pagina.

Input parameters:

- `new_title`
- `offset_x` (number)
- `offset_y` (number)
- `page` (string, required)
- `request_id` (string)
- `target_page`
- `visual_id` (string, required)

Output parameters:

- `result` (object)

### `pbi_delete_visual` (~68 tokens)

Elimina un visual. Operacion destructiva: requiere confirm=true.

Devuelve la definicion previa, y el journal permite restaurarla.

Input parameters:

- `confirm` (boolean)
- `page` (string, required)
- `request_id` (string)
- `visual_id` (string, required)

Output parameters:

- `result` (object)

### `pbi_set_visual_title` (~125 tokens)

Cambia u oculta el titulo de un visual PRESERVANDO su formato.

\`title`: el texto nuevo. `show=false`: oculta el titulo SIN borrar su
texto ni formato (el rodeo de poner texto vacio dejaba la banda del
titulo ocupando altura); `show=true` lo vuelve a mostrar. Se puede
pasar solo uno de los dos, o ambos.

Input parameters:

- `page` (string, required)
- `request_id` (string)
- `show`
- `title`
- `visual_id` (string, required)

Output parameters:

- `result` (object)

### `pbi_set_conditional_format` (~363 tokens)

Colorea un visual segun el valor de un campo (degradado).

Es lo que convierte una matriz de numeros en un mapa de calor, o unas
barras planas en una escala de semaforo.

\`field`: 'Tabla[Campo]' o '[Medida]' de donde sale el VALOR.
\`target_column`: que columna del visual se pinta con ese valor; sin
ella se pinta la columna del propio `field`. Debe estar proyectada en
el visual (p.ej. pintar 'Resumen[semaforo]' con '[Puntaje promedio]').
\`target`: 'background' o 'font' para tablas y matrices; 'bars' para
barras, columnas y puntos. `mid_color`: si lo indicas, el degradado
tiene tres paradas en vez de dos, util cuando hay un punto neutro.
\`min_value`/`mid_value`/`max_value`: anclas numericas de las paradas
(sin ellas, Power BI usa el minimo y maximo observados).
\`null_strategy`: asZero | none | specificColor.

Si el visual ya tenia una regla en ese mismo destino, se sustituye:
dos degradados sobre la misma propiedad no se suman, se pisan.

Input parameters:

- `field` (string, required)
- `max_color` (string, required)
- `max_value`
- `mid_color`
- `mid_value`
- `min_color` (string, required)
- `min_value`
- `null_strategy` (string)
- `page` (string, required)
- `request_id` (string)
- `target` (string)
- `target_column`
- `visual_id` (string, required)

Output parameters:

- `result` (object)

### `pbi_set_color_from_field` (~215 tokens)

Colorea un visual con el color que DEVUELVE una medida.

Es el modo "valor de campo" de Power BI: la medida entrega '#D03B3B'
o un nombre de color, y el visual lo aplica tal cual. Es el patron
tipico de un semaforo calculado en DAX.

\`field`: '[Medida]' o 'Tabla[Medida]' que devuelve el color.
\`target`: 'background' o 'font' (tablas y matrices), 'bars' (barras,
columnas y puntos). La matriz de que combinacion funciona en que tipo
de visual vive en el servidor y se valida antes de escribir.
\`target_column`: columna proyectada a pintar; sin ella se pinta la
columna del propio campo.

Input parameters:

- `field` (string, required)
- `page` (string, required)
- `request_id` (string)
- `target` (string)
- `target_column`
- `visual_id` (string, required)

Output parameters:

- `result` (object)

### `pbi_set_visual_z_order` (~84 tokens)

Fija el orden Z de los visuales de una pagina.

\`order`: ids de MENOR a MAYOR z; el ultimo queda encima. Los visuales
que no menciones se colocan por encima, conservando su orden relativo.

Input parameters:

- `order` (array, required)
- `page` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_replace_visual_field` (~166 tokens)

Sustituye una referencia de campo dentro de un visual.

\`old_ref`/`new_ref`: 'Tabla[Campo]' o '[Medida]'. Trabaja sobre las
proyecciones existentes; no crea roles nuevos. Falla si el visual no
referencia `old_ref`, en vez de no hacer nada en silencio.

El destino se valida contra el modelo antes de escribir: si no existe,
es ambiguo o es de otro tipo (medida donde va una columna), se rechaza
con `field_not_found` en lugar de inventarlo.

Input parameters:

- `new_ref` (string, required)
- `old_ref` (string, required)
- `page` (string, required)
- `request_id` (string)
- `visual_id` (string, required)

Output parameters:

- `result` (object)

### `pbi_copy_visual_format` (~111 tokens)

Copia el formato de un visual a otros DEL MISMO TIPO.

Se copia el formato pero no el texto del titulo, que es contenido.
Copiar entre tipos distintos se rechaza: la estructura de formato no es
intercambiable y Power BI podria rechazar el informe.

Input parameters:

- `request_id` (string)
- `source_page` (string, required)
- `source_visual` (string, required)
- `target_page` (string, required)
- `target_visuals` (array, required)

Output parameters:

- `result` (object)

### `pbi_duplicate_page` (~80 tokens)

Duplica una pagina con todos sus visuales, en una sola transaccion.

Se regeneran los identificadores que deben ser unicos (el de la pagina
y el de cada visual) y se conserva todo lo demas.

Input parameters:

- `new_name` (string, required)
- `page` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_delete_page` (~74 tokens)

Elimina una pagina y actualiza el orden y la pagina activa.

Destructiva: requiere confirm=true. Se niega a borrar la ultima pagina
del informe, porque un informe sin paginas no abre.

Input parameters:

- `confirm` (boolean)
- `page` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_rename_page` (~49 tokens)

Cambia el nombre visible de una pagina. El id interno no cambia.

Input parameters:

- `new_name` (string, required)
- `page` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_reorder_pages` (~60 tokens)

Fija el orden de las paginas del informe.

Acepta ids o nombres visibles. Las paginas que no menciones quedan al
final, conservando su orden relativo.

Input parameters:

- `order` (array, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_detect_layout_issues` (~92 tokens)

Diagnostica la geometria de una pagina (o de todas). Solo lectura.

Detecta solapamientos, visuales fuera del lienzo, tamanos demasiado
pequenos, margenes, separaciones inconsistentes, orden Z duplicado o
ausente, paginas vacias y paginas saturadas. Cada hallazgo trae su
evidencia geometrica.

Input parameters:

- `page`

Output parameters:

- `result` (object)

### `pbi_align_visuals` (~81 tokens)

Alinea varios visuales por un borde.

\`edge`: left | right | top | bottom | center_h | center_v.
Determinista: la misma entrada produce siempre la misma salida.

Input parameters:

- `edge` (string)
- `page` (string, required)
- `request_id` (string)
- `visual_ids` (array, required)

Output parameters:

- `result` (object)

### `pbi_distribute_visuals` (~78 tokens)

Reparte visuales con separacion uniforme. `axis`: horizontal|vertical.

Necesita al menos tres visuales: con dos, la separacion ya es la que hay.

Input parameters:

- `axis` (string)
- `page` (string, required)
- `request_id` (string)
- `visual_ids` (array, required)

Output parameters:

- `result` (object)

### `pbi_normalize_page_layout` (~107 tokens)

Corrige lo corregible de una pagina sin reacomodarla entera.

Mete dentro del lienzo lo que se sale, sube al minimo lo demasiado
pequeno y respeta los margenes. NO mueve lo que ya esta bien: es una
correccion conservadora. `dry_run=true` devuelve el plan sin escribir.

Input parameters:

- `dry_run` (boolean)
- `page` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_list_page_presets` (~61 tokens)

Presets de pagina disponibles, con los bloques que compone cada uno.

Un preset describe la INTENCION de la pagina (KPIs arriba, grafico
protagonista, detalle abajo). Los campos concretos los eliges tu.

Output parameters:

- `result` (object)

### `pbi_generate_page_spec` (~129 tokens)

Genera un borrador de spec a partir de un preset y unos campos.

\`preset`: executive | financial | sales | operations | evm | detail.
\`measures`: medidas para los KPIs y los graficos. `category`: columna
para el eje de los graficos. El resultado es un spec editable: revisalo
y pasalo por pbi_validate_page_spec.

Input parameters:

- `category`
- `height` (integer)
- `measures`
- `page_name` (string, required)
- `preset` (string)
- `width` (integer)

Output parameters:

- `result` (object)

### `pbi_validate_page_spec` (~69 tokens)

Valida un spec: esquema, referencias contra el modelo y geometria.

Los errores traen su JSON path (`$.visuals[2].fields.values[0]`) para
que se puedan corregir sin adivinar. No escribe nada.

Input parameters:

- `spec` (object, required)

Output parameters:

- `result` (object)

### `pbi_preview_page_spec` (~80 tokens)

Maqueta HTML del spec con las posiciones FINALES. No escribe al .pbip.

Lo que muestra el preview es exactamente lo que se escribiria: tipos,
titulos, campos, tamanos y posiciones salen del mismo compilado que usa
la aplicacion.

Input parameters:

- `seed` (string)
- `spec` (object, required)

Output parameters:

- `result` (object)

### `pbi_diff_page_spec` (~67 tokens)

Compara el spec con una pagina existente antes de aplicarlo.

Dice que visuales se anadirian, cuales sobrarian y cuantos quedan
igual. Si la pagina no existe, informa que se creara.

Input parameters:

- `page`
- `spec` (object, required)

Output parameters:

- `result` (object)

### `pbi_apply_page_spec` (~372 tokens)

Materializa el spec como una pagina PBIR, en UNA transaccion.

Cuatro desenlaces explicitos: `create` (no existe), `update` (existe y
el spec cambia algo), `no_change` (ya coincide) y `conflict` (el nombre
no identifica una sola pagina).

\`page`: id o nombre visible de la pagina a actualizar. Si se omite, se
usa el nombre del spec. Al actualizar se CONSERVAN el id de la pagina y
el de cada visual que siga representando lo mismo.

\`sync_mode`: `merge` (por defecto) anade y actualiza pero no borra lo
que el spec no menciona; `replace` ademas elimina los visuales
ausentes. El defecto es conservador para que un spec parcial no pueda
vaciar una pagina por omision.

\`interactions`: que le hace un visual a otro al seleccionar en el.
Cada visual del spec se puede senalar por su POSICION en `visuals`
(0, 1, 2...), por el `id` que se le ponga en el spec, o por su titulo;
no hace falta conocer los ids finales, que se generan aqui. Tipos:
\`Default`, `DataFilter`, `HighlightFilter`, `NoFilter` (tambien valen
\`filter`, `highlight` y `none`).

\`dry_run=true` devuelve un plan con `plan_token` y no escribe nada;
aplicalo despues con pbi_apply_plan.

Input parameters:

- `dry_run` (boolean)
- `page` (string)
- `request_id` (string)
- `seed` (string)
- `spec` (object, required)
- `sync_mode` (string)

Output parameters:

- `result` (object)

### `pbi_validate_generated_page` (~59 tokens)

Verifica una pagina YA escrita: referencias rotas y geometria.

Se usa despues de aplicar un spec para comprobar que el resultado es
valido de verdad, no solo que la escritura no fallo.

Input parameters:

- `page` (string, required)

Output parameters:

- `result` (object)

### `pbi_list_report_pages` (~31 tokens)

Lista las paginas del informe PBIR activo (id, nombre, tamano, nº visuales).

Output parameters:

- `result` (object)

### `pbi_list_visuals` (~48 tokens)

Lista los visuales de una pagina: id, tipo, posicion, campos, titulo.

\`page`: id interno o nombre visible de la pagina.

Input parameters:

- `page` (string, required)

Output parameters:

- `result` (object)

### `pbi_document_report_layout` (~27 tokens)

Genera documentacion Markdown del layout del informe (paginas y visuales).

Output parameters:

- `result` (object)

### `pbi_create_visual` (~879 tokens)

Crea un visual PBIR en una pagina.

\`visual_type` acepta: actionButton, areaChart, barChart, button, card,
cardVisual, clusteredBarChart, clusteredColumnChart, columnChart, donut,
donutChart, funnel, gauge, htmlContent,
htmlContent443BE3AD55E043BF878BED274D3A6855, image, kpi, lineChart,
matrix, multiRowCard, navigation, pageNavigator, pieChart, pivotTable,
rectangle, ribbonChart, scatterChart, shape, slicer, table, tableEx,
text, textbox, treemap, waterfall y waterfallChart.

Para visuales con datos, valida antes de escribir los roles obligatorios,
la cardinalidad maxima de cada rol y el tipo de campo admitido: dimension
(`Grouping`), medida (`Measure`) o cualquiera de ambos
(`GroupingOrMeasure`). Los elementos decorativos (texto, forma, imagen,
navegador y boton) rechazan `fields` porque no llevan consulta.

\`fields`: rol -> campos, p.ej. {"category":"Tabla[Col]",
          "values":["[Medida]"], "legend":"Tabla[Col]"}.

El rol se reconoce escrito como sea: el logico (`values`), el nombre
que usa PBIR y que devuelve `pbi_list_visuals` (`Values`, `Y`,
\`Category`, `Data`) o un sinonimo (`measure`, `axis`). Cada campo puede
ser `"Tabla[Campo]"` o el objeto que devuelve `pbi_list_visuals`, para
poder leer una pagina y rehacerla sin traducir nada.

Un rol que ese tipo de visual no tiene se RECHAZA con la lista de los
validos. Antes se descartaba en silencio y el visual salia sin datos.

\`options`: formato adicional. Llaves reconocidas (una clave no
reconocida se ignora, no se rechaza; ver `pbi_get_visual` para
comprobar que quedo escrito):
\- `background_color`, `border_color` ('#RRGGBB'), `background_transparency`
  (0-100), `border_radius` (px): el marco de CUALQUIER visual (panel
  General > Efectos de Desktop). Sin `background_color`/`border_color`
  no se toca el marco.
\- `card`/`cardVisual`: `show_category_label`, `value_font_size`,
  \`bold_value`, `value_color`.
\- `shape`: `fill`, `transparency`, `text`, `font_size`, `text_color`.
\- `textbox`: `text`, `font_size`…

Input parameters:

- `fields` (object, required)
- `options`
- `page` (string, required)
- `position` (object, required)
- `request_id` (string)
- `title`
- `visual_type` (string, required)

Output parameters:

- `result` (object)

### `pbi_add_custom_visual` (~75 tokens)

Registra un custom visual de AppSource en el informe (publicCustomVisuals).

Sin argumentos registra "HTML Content" (renderiza HTML/SVG desde una medida
DAX). Power BI Desktop lo descarga de AppSource al abrir el informe.

Input parameters:

- `request_id` (string)
- `visual_id`

Output parameters:

- `result` (object)

### `pbi_create_html_visual` (~139 tokens)

Crea un visual "HTML Content" que renderiza el HTML/SVG devuelto por una medida.

\`html_measure`: medida cuyo resultado es HTML (p.ej. "[HTML Panel EVM]").
Registra el custom visual en el informe si aun no esta. La medida se crea
aparte con pbi_create_measure (el HTML se arma en DAX, tipicamente con VARs
y CONCATENATEX sobre los datos del modelo).

Input parameters:

- `html_measure` (string, required)
- `page` (string, required)
- `position` (object, required)
- `request_id` (string)
- `title`

Output parameters:

- `result` (object)

### `pbi_update_visual_position` (~76 tokens)

Mueve/redimensiona un visual existente.

Input parameters:

- `height` (number, required)
- `page` (string, required)
- `request_id` (string)
- `visual_id` (string, required)
- `width` (number, required)
- `x` (number, required)
- `y` (number, required)
- `z`

Output parameters:

- `result` (object)

### `pbi_set_visual_filter` (~482 tokens)

Filtra un visual EXISTENTE sin escribir `filterConfig` a mano.

\`filters`: lista de specs. Por COLUMNA,
\`{field: 'Tabla[Columna]', values?: [...], type?: 'Categorical' |
'Advanced' | 'TopN' | 'Range' | 'RelativeDate' | 'Passthrough',
exclude?: bool, raw?: {...}, hidden?: bool, locked?: bool,
display_name?: str}`. Con `values` se arma un filtro de lista
(Categorical); sin `values` ni `raw` el campo queda declarado pero
SIN acotar, como el panel de filtros vacio. `raw` pasa una consulta
semantica ya construida, para lo que este constructor no cubre.

Por MEDIDA, `{measure: 'Tabla[Medida]', condition: 'GreaterThan',
value: 0}` (`condition`: Equal | NotEqual | GreaterThan |
GreaterThanOrEqual | LessThan | LessThanOrEqual, o los simbolos
\`> >= < <= = !=`). Es la forma de ENCADENAR slicers de dimension
cuando varias tablas de hechos cuelgan de las mismas dimensiones y
una relacion bidireccional crearia ambiguedad.

\`field`/`measure` van con el NOMBRE real de la tabla. La mitad interna
de la consulta usa un ALIAS (`SourceRef.Source`) que esta tool
resuelve sola: escribirlo a mano ahi es el error mas comun al
construir un filtro de visual, y Power BI lo ignora sin decir por que.

Por defecto REEMPLAZA los filtros del visual, y una lista vacia los
quita todos. **Cuidado con los slicers**: suelen traer un filtro
\`Categorical` donde vive la seleccion del usuario, y reemplazarlo la
borra. Para anadir sin pisar, `merge=true` (ahi una lista vacia no
quita nada). No comprueba el campo contra el modelo: uno que no existe
se escribe igual y Power BI lo resuelve en silencio a nada.

Input parameters:

- `filters` (array, required)
- `merge` (boolean)
- `page` (string, required)
- `request_id` (string)
- `visual_id` (string, required)

Output parameters:

- `result` (object)

### `pbi_arrange_visuals` (~111 tokens)

Reorganiza los visuales de una pagina.

\`layout`: grid | dashboard | executive_summary | custom.
\`visual_ids`: subconjunto opcional (por defecto todos). `custom`: mapa
visual_id -> {x,y,width,height} para layout 'custom'.

Input parameters:

- `canvas`
- `custom`
- `layout` (string)
- `page` (string, required)
- `request_id` (string)
- `spacing` (number)
- `visual_ids`

Output parameters:

- `result` (object)

### `pbi_generate_report_page` (~122 tokens)

Genera una pagina con visuales propuestos a partir del modelo.

Revisa el modelo, valida los campos sugeridos (no inventa campos), crea la
pagina, agrega tarjetas para medidas y un grafico por categoria, y acomoda
todo con el layout indicado. Devuelve un resumen de lo creado.

Input parameters:

- `available_fields_hint`
- `create_missing_measures` (boolean)
- `layout` (string)
- `objective` (string)
- `page_name` (string, required)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_build_dashboard` (~121 tokens)

Construye un dashboard completo desde un objetivo, no desde primitivas.

Analiza el modelo, compone el spec segun el preset, calcula el layout,
genera preview, aplica en una transaccion y verifica el resultado.
\`dry_run=true` (por defecto) se detiene tras el preview.

Input parameters:

- `category`
- `dry_run` (boolean)
- `measures` (array, required)
- `name` (string, required)
- `preset` (string)
- `request_id` (string)
- `seed` (string)

Output parameters:

- `result` (object)

### `pbi_build_executive_page` (~70 tokens)

Pagina de resumen ejecutivo: fila de KPIs y grafico protagonista.

Input parameters:

- `category`
- `dry_run` (boolean)
- `measures` (array, required)
- `name` (string)
- `request_id` (string)
- `seed` (string)

Output parameters:

- `result` (object)

### `pbi_build_evm_page` (~99 tokens)

Pagina EVM (Earned Value Management).

Espera medidas del tipo PV, EV, AC, CPI y SPI; si no las reconoce, lo
avisa en vez de generar una pagina que no significa nada.

Input parameters:

- `category`
- `dry_run` (boolean)
- `measures` (array, required)
- `name` (string)
- `request_id` (string)
- `seed` (string)

Output parameters:

- `result` (object)

### `pbi_repair_broken_references` (~116 tokens)

Detecta referencias rotas en los visuales y las repara.

Sin `mapping` solo diagnostica: adivinar a que campo queria apuntar un
visual roto no es una decision que deba tomarse sola. Pasa
\`{"Tabla[Viejo]": "Tabla[Nuevo]"}` para repararlas, y el destino se
valida contra el modelo antes de escribir.

Input parameters:

- `dry_run` (boolean)
- `mapping`
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_normalize_report` (~89 tokens)

Normaliza la geometria de TODAS las paginas del informe.

Mete dentro del lienzo lo que se sale, sube al minimo lo demasiado
pequeno y respeta margenes. No reacomoda lo que ya cumple. Compara el
puntaje de auditoria antes y despues.

Input parameters:

- `dry_run` (boolean)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_compare_live_to_pbip` (~60 tokens)

Compara el modelo EN VIVO con el TMDL del disco.

Util para saber si hay cambios en memoria sin guardar: lista tablas y
medidas que solo estan en un lado, y medidas cuyo DAX difiere.

Output parameters:

- `result` (object)

### `pbi_prepare_delivery` (~78 tokens)

Checklist de pre-entrega con plan de correccion.

Audita el proyecto, produce un checklist de bloqueantes y propone las
correcciones automaticas disponibles. Con `dry_run=false` las aplica y
compara el puntaje antes y despues.

Input parameters:

- `dry_run` (boolean)
- `request_id` (string)

Output parameters:

- `result` (object)

### `pbi_generate_technical_documentation` (~67 tokens)

Documentacion tecnica completa en Markdown, guardada en outputs/.

Incluye el modelo (tablas, medidas con su DAX y dependencias), el
informe pagina a pagina con los campos de cada visual, y la auditoria
con puntaje por dominio.

Output parameters:

- `result` (object)

### `pbi_export_pbix` (~741 tokens)

Exporta el proyecto .pbip a un archivo .pbix (o .pbit) de verdad.

Microsoft no publica ninguna API para convertir el formato, asi que
esto automatiza el flujo OFICIAL: abre el proyecto en Power BI Desktop
y usa `Archivo > Guardar como`. No se fabrica el .pbix a mano -un zip
cosido con TOM abre a veces y rompe otras-.

Antes de tocar Desktop se resuelve la ruta exacta, se valida el TMDL,
se comprueba que el destino cabe en Windows y, si existe y pasas
\`overwrite=true`, se respalda. Si el destino existe y no lo pasas,
falla ANTES de abrir ninguna ventana.

\`refresh`: 'auto' intenta refrescar y declara el resultado sin
adornarlo; 'required' no exporta si no pudo refrescar -para no
entregar un .pbix como si tuviera datos-; 'skip' guarda el estado que
el modelo tenga ahora.

Que el dialogo desaparezca NO es que se haya guardado: se comprueba
que el archivo existe, que es .pbix y no .pbit, que pesa mas de cero,
que su fecha es de esta ejecucion y que el lector de .pbix lo puede
abrir con informe y modelo dentro. Si `saved_as_verified` no es true,
la respuesta no es un exito.

\`leave_open=true` (por defecto) deja abierto exactamente el .pbix
generado y lo selecciona como modelo activo. Nunca cierra una ventana
que no abrio esta operacion. La respuesta trae `desktop_session`
(`desktop_pid` + `desktop_started`): es lo que acepta
\`pbi_close_desktop` para cerrar ESA ventana despues, porque ya no
responde a la ruta del .pbip original.

\`format`: 'pbix' (por defecto) o 'pbit'. Con 'pbit' se elige el tipo
de PLANTILLA en el mismo `Guardar como`, se atiende el dialogo de
descripcion que Desktop abre despues y se verifica que el archivo
tenga forma de plantilla: informe y definicion del modelo, SIN datos.
No se fabrica quitando partes de un .pbix.

Antes de conducir la ventana se espera a que su titulo muestre el
documento pedido (hasta 90 s): `Sin titulo` es Desktop cargando, no
otra ventana. Un titulo estable de OTRO documento se rechaza.

\`project_path` es un alias de `pbip_pa…

Input parameters:

- `confirm_reuse` (boolean)
- `format` (string)
- `leave_open` (boolean)
- `out_path` (string)
- `overwrite` (boolean)
- `pbip_path` (string)
- `project_path` (string)
- `refresh` (string)
- `request_id` (string)
- `timeout` (integer)

Output parameters:

- `result` (object)

### `pbi_finalize_delivery` (~330 tokens)

El ULTIMO paso de una construccion: del proyecto al entregable.

Hace de extremo a extremo lo que hasta ahora eran cinco llamadas y un
par de suposiciones: resuelve el archivo exacto que le pasas, lo
prepara -convirtiendolo si le das un .pbix-, valida el proyecto,
exporta a .pbix conduciendo Power BI Desktop, inspecciona el resultado
y deja abierto justo el entregable, seleccionado como modelo activo.

Una sola respuesta verificable: `output_pbix`, `output_sha256`,
\`output_size`, `saved_as_verified` y `opened_path_verified`.

\`path` (o su alias `project_path`) se puede omitir para usar el
proyecto activo. `format`: 'pbix' o 'pbit' (plantilla: informe y
definicion del modelo, sin datos; producida por el propio `Guardar
como` de Desktop, nunca fabricada a mano). Requiere Windows con Power
BI Desktop instalado.

\`confirm_reuse=true` autoriza conducir una ventana que ya estaba
abierta por el usuario. Sin esa autorizacion el flujo falla cerrado,
igual que `pbi_export_pbix`.

Input parameters:

- `confirm_reuse` (boolean)
- `format` (string)
- `leave_open` (boolean)
- `out_path` (string)
- `overwrite` (boolean)
- `path` (string)
- `project_path` (string)
- `refresh` (string)
- `request_id` (string)

Output parameters:

- `result` (object)

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/horizungroup-horizun-pbi-mcp/horizun-pbi-mcp#diagnostics

## Score history

- 2026-09-21: 76
- 2026-09-20: 75
- 2026-09-19: 75
- 2026-09-18: 74
- 2026-09-17: 74
- 2026-09-16: 73
- 2026-09-15: 73
- 2026-09-14: 57
- 2026-09-13: 72
- 2026-09-12: 71
- 2026-09-11: 71
- 2026-09-10: 70
- 2026-09-09: 70
- 2026-09-08: 70
- 2026-09-07: 69
- 2026-09-06: 69
- 2026-09-05: 68
- 2026-09-04: 68
- 2026-09-03: 67
- 2026-09-02: 64
- 2026-09-01: 64
- 2026-08-31: 64
- 2026-08-30: 64
- 2026-08-29: 64
- 2026-08-28: 64
- 2026-08-27: 64
- 2026-08-26: 49

## Common questions

### What is the Horizun PBI MCP server?

Horizun PBI MCP is listed in the public MCP registry as io.github.HorizunGroup/horizun-pbi-mcp. Build, audit and repair Power BI Desktop models and PBIP reports with DAX, TMDL and PBIR. This page covers its PyPI package (horizun-pbi-mcp).

### Is the Horizun PBI MCP server safe to use?

Horizun PBI MCP scores 76 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 21 September 2026. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Horizun PBI MCP server expose?

Horizun PBI MCP exposes 139 tools: pbi_profile_data, pbi_audit_project, pbi_audit_report_only, pbi_plan_audit_fixes, pbi_apply_audit_fixes, and 134 more. Their descriptions and schemas cost roughly 23,980 tokens of context every time the server is loaded.

### Is the Horizun PBI MCP server still maintained?

Horizun PBI MCP is still listed as active in the MCP registry. We last reached this channel on 21 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

## Links

- PyPI project: https://pypi.org/project/horizun-pbi-mcp/
- Socket report: https://socket.dev/pypi/package/horizun-pbi-mcp
- Repository: https://github.com/HorizunGroup/horizun-pbi-mcp
- Website: https://horizunhub.com/
- Changelog RSS feed: https://verifymcp.io/servers/horizungroup-horizun-pbi-mcp/horizun-pbi-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/horizungroup-horizun-pbi-mcp/horizun-pbi-mcp.json
- HTML version of this page: https://verifymcp.io/servers/horizungroup-horizun-pbi-mcp/horizun-pbi-mcp
