> For the complete documentation index, see [llms.txt](https://hacking-3.gitbook.io/barre/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://hacking-3.gitbook.io/barre/apuntes/blue-team/splunk/dashboards.md).

# Dashboards

### 1. Visión General - Simple XML vs. Dashboard Studio

Splunk Enterprise ofrece **dos frameworks** distintos para construir dashboards. Ambos conviven en la misma instancia.

<table data-search="false"><thead><tr><th>Aspecto</th><th>Simple XML (Classic)</th><th>Dashboard Studio</th></tr></thead><tbody><tr><td>Formato fuente</td><td>XML</td><td>JSON (embebido en XML con CDATA)</td></tr><tr><td>Editor visual</td><td>Sí (arrastrar y soltar)</td><td>Sí (WYSIWYG moderno)</td></tr><tr><td>Layout</td><td>Filas y columnas</td><td>Absoluto (px libre) o Grid</td></tr><tr><td>Visualizaciones</td><td>Chart, single, table, map, gauge, trellis, event, html</td><td>Todo lo de Classic + Sankey, coordenadas paralelas, choropleth SVG custom, formas/líneas/texto/imágenes libres</td></tr><tr><td>Inputs soportados</td><td>time, dropdown, text, radio, checkbox, link list</td><td>time, dropdown, text - <strong>no</strong> radio, checkbox ni link list</td></tr><tr><td>Tokens / drilldown</td><td>Completo: <code>&#x3C;condition></code>, <code>&#x3C;unset></code>, <code>&#x3C;eval></code></td><td>set/eval desde input/búsqueda/click, <code>visibility</code> condicional - <strong>sin</strong> <code>unset</code> ni if/then personalizado</td></tr><tr><td>JavaScript / CSS custom</td><td>Sí (<code>.js</code>/<code>.css</code> en <code>appserver/static</code>)</td><td>No soportado</td></tr><tr><td>Exportación</td><td>CSV por panel</td><td>CSV, PNG por viz, PDF/PNG del dashboard completo con estilo</td></tr><tr><td>Programar PDF email</td><td>Sí</td><td>Sí</td></tr><tr><td>Curva de aprendizaje</td><td>Baja-media</td><td>Media (requiere entender <code>dataSources</code>/<code>visualizations</code>/<code>layout</code>)</td></tr></tbody></table>

#### 1.1 Cuándo usar cada uno

| Escenario                                                      | Recomendación                               |
| -------------------------------------------------------------- | ------------------------------------------- |
| Dashboard operativo SOC (tablas, single values, timechart)     | Simple XML si el equipo ya tiene plantillas |
| JS/CSS custom, drilldown condicional avanzado                  | **Simple XML** (única opción)               |
| Inputs tipo radio/checkbox/link list                           | **Simple XML**                              |
| Dashboard ejecutivo, diseño libre, imágenes, choropleth custom | **Dashboard Studio**                        |
| Export PDF pixel-perfect para cliente                          | **Dashboard Studio**                        |
| Dashboard con macros pesadas y JS heredado                     | Mantener **Simple XML**                     |

***

### 2. Crear un Dashboard

#### 2.1 Desde Splunk Web

`Dashboards → Create New Dashboard`:

1. Nombre, ID (se usa en URL: `data/ui/views/<id>.xml`) y descripción.
2. Framework: **Classic Dashboards** o **Dashboard Studio**.
3. Si es Studio: elegir plantilla de layout (Absolute / Grid) o en blanco.
4. Añadir paneles: `+ Add Panel` → nueva búsqueda, búsqueda guardada o reporte.

#### 2.2 Desde una búsqueda en Search & Reporting

Tras ejecutar: `Save As → Dashboard Panel` → elegir dashboard existente o crear nuevo. Splunk convierte la visualización activa en panel automáticamente.

#### 2.3 Desde cero editando XML/JSON

`Dashboards → [dashboard] → Edit → Source` (Simple XML) o `Edit → Source Code Editor` (Dashboard Studio). Recomendado para control total y versionado en Git.

#### 2.4 Ubicación en disco

```bash
# Simple XML
$SPLUNK_HOME/etc/apps/<app>/local/data/ui/views/<dashboard_id>.xml

# Dashboard Studio (mismo path, JSON embebido en CDATA)
$SPLUNK_HOME/etc/apps/<app>/local/data/ui/views/<dashboard_id>.xml
# → <dashboard version="2" ...><definition><![CDATA[ {JSON} ]]></definition></dashboard>
```

#### 2.5 Versionado y Control de Cambios

```bash
# Exportar dashboard para Git
cp $SPLUNK_HOME/etc/apps/mdr/local/data/ui/views/salud_ingesta.xml \
   /repo/mdr/dashboards/salud_ingesta.xml

# Estructura recomendada del repositorio
mdr/
├── dashboards/
│   ├── salud_ingesta.xml
│   ├── salud_agentes.xml
│   └── resumen_detecciones.xml
├── savedsearches/
└── lookups/
```

***

### 3. Simple XML - Estructura y Sintaxis

#### 3.1 Esqueleto Mínimo

```xml
<dashboard version="1.1" theme="dark">
  <label>Salud de Ingesta - Cliente ACME</label>
  <description>Panel operativo de volumen e integridad de ingesta</description>
  <row>
    <panel>
      <title>Eventos por Índice (24h)</title>
      <chart>
        <search>
          <query>index=acme_* earliest=-24h | timechart span=1h count by index</query>
        </search>
        <option name="charting.chart">column</option>
      </chart>
    </panel>
  </row>
</dashboard>
```

#### 3.2 Elementos Principales

| Tag                                                                 | Función                                                                           |
| ------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `<dashboard>`                                                       | Raíz; `version="1.1"` = Classic, `theme="light"/"dark"`                           |
| `<label>`                                                           | Título visible del dashboard                                                      |
| `<description>`                                                     | Subtítulo/descripción                                                             |
| `<row>`                                                             | Fila de paneles; puede repetirse; atributo `grouping` para columnas               |
| `<panel>`                                                           | Contenedor de una visualización; `depends`/`rejects` para visibilidad condicional |
| `<chart>` / `<table>` / `<single>` / `<map>` / `<event>` / `<html>` | Tipo de visualización                                                             |
| `<search>`                                                          | Búsqueda embebida (`<query>`) o referencia a guardada (`ref`)                     |
| `<earliest>` / `<latest>`                                           | Rango temporal por defecto de la búsqueda                                         |
| `<option name="...">`                                               | Opciones de formato específicas de la visualización                               |
| `<fieldset>`                                                        | Contenedor de `<input>` globales del dashboard                                    |
| `<drilldown>`                                                       | Bloque de interactividad al hacer clic                                            |

#### 3.3 Panel con Múltiples Visualizaciones en la misma Fila

```xml
<row>
  <panel>
    <title>Top 10 Orígenes por Volumen</title>
    <table>
      <search base="base_ingesta"></search>
      <option name="drilldown">cell</option>
    </table>
  </panel>
  <panel>
    <title>Distribución por Sourcetype</title>
    <chart>
      <search base="base_ingesta"></search>
      <option name="charting.chart">pie</option>
    </chart>
  </panel>
</row>
```

#### 3.4 Panel con Panel HTML Embebido

```xml
<row>
  <panel>
    <html>
      <![CDATA[
        <div style="background:#1a1a2e;color:#e0e0e0;padding:16px;border-radius:6px;">
          <h2>📋 Resumen Operativo</h2>
          <p>Dashboard de salud de plataforma MDR · Actualizado cada 5 min</p>
        </div>
      ]]>
    </html>
  </panel>
</row>
```

***

### 4. Simple XML - Tipos de Visualización

#### 4.1 `<chart>` - Series y Comparativas

```xml
<chart>
  <search>
    <query>index=acme_fw earliest=-24h | timechart span=1h count by action</query>
  </search>
  <option name="charting.chart">line</option>
  <option name="charting.chart.stackMode">stacked</option>
  <option name="charting.legend.placement">bottom</option>
  <option name="charting.axisLabelsX.majorLabelStyle.rotation">-45</option>
</chart>
```

Valores de `charting.chart`: `line`, `column`, `bar`, `area`, `pie`, `scatter`, `bubble`, `radialGauge`, `fillerGauge`, `markerGauge`.

#### 4.2 `<single>` - Valor Único / KPI

```xml
<single>
  <search>
    <query>index=acme_fw earliest=-15m | stats count</query>
  </search>
  <option name="drilldown">none</option>
  <option name="colorMode">block</option>
  <option name="rangeColors">["0x53a051","0xf8be34","0xdc4e41"]</option>
  <option name="rangeValues">[1000,5000]</option>
  <option name="underLabel">eventos últimos 15 min</option>
  <option name="refresh.display">progressbar</option>
</single>
```

Ideal para KPIs de SOC: eventos/seg, alertas abiertas, agentes caídos, fuentes sin datos.

#### 4.3 `<table>` - Tablas de Datos

```xml
<table>
  <search>
    <query>
      index=acme_* earliest=-1h
      | stats count as eventos, dc(sourcetype) as sourcetypes by index, host
      | sort -eventos
    </query>
  </search>
  <option name="drilldown">cell</option>
  <option name="count">20</option>
  <option name="dataOverlayMode">heatmap</option>
  <format type="color" field="eventos">
    <colorPalette type="minMidMax" minColor="#53A051" maxColor="#DC4E41"/>
  </format>
</table>
```

#### 4.4 `<map>` - Geolocalización

```xml
<map>
  <search>
    <query>
      index=acme_vpn earliest=-24h
      | iplocation src_ip
      | geostats count by Country
    </query>
  </search>
  <option name="mapping.type">marker</option>
  <option name="mapping.map.zoom">3</option>
</map>
```

#### 4.5 Gauges

```xml
<chart>
  <search><query>| stats count</query></search>
  <option name="charting.chart">fillerGauge</option>
  <option name="charting.chart.rangeValues">[0,70,90,100]</option>
  <option name="charting.chart.rangeColors">["0x65a637","0xf7bc38","0xd93f3c"]</option>
</chart>
```

#### 4.6 Trellis (Multi-serie en Grid)

```xml
<chart>
  <search><query>index=acme_* | timechart span=1h count by index</query></search>
  <option name="charting.chart">column</option>
  <option name="trellis.enabled">1</option>
  <option name="trellis.splitBy">index</option>
  <option name="trellis.size">medium</option>
</chart>
```

Útil para comparar el mismo panel por cliente/índice/host sin duplicar XML.

#### 4.7 `<event>` - Lista de Eventos Raw

```xml
<event>
  <search>
    <query>index=acme_edr sourcetype=crowdstrike:falcon:event earliest=-1h</query>
  </search>
  <option name="count">20</option>
  <option name="list.drilldown">none</option>
</event>
```

***

### 5. Dashboard Studio - Estructura JSON

Un dashboard de Studio es un objeto JSON con cuatro bloques principales: `dataSources`, `visualizations`, `inputs` y `layout`.

```json
{
  "title": "Salud de Ingesta - Cliente ACME",
  "description": "Vista consolidada de volumen e integridad de ingesta",
  "dataSources": {
    "ds_ingesta_24h": {
      "type": "ds.search",
      "options": {
        "query": "index=acme_* earliest=-24h | timechart span=1h count by index",
        "queryParameters": { "earliest": "-24h", "latest": "now" }
      },
      "name": "Ingesta 24h"
    }
  },
  "visualizations": {
    "viz_ingesta_24h": {
      "type": "splunk.line",
      "dataSources": { "primary": "ds_ingesta_24h" },
      "options": {
        "showLegend": true,
        "y": { "title": { "text": "Eventos" } }
      },
      "title": "Eventos por Índice (24h)"
    }
  },
  "inputs": {
    "input_time": {
      "type": "input.timerange",
      "options": { "token": "global_time", "defaultValue": "-24h,now" },
      "title": "Rango temporal"
    }
  },
  "defaults": {
    "dataSources": {
      "ds.search": { "options": { "queryParameters": { "latest": "now" } } }
    }
  },
  "layout": {
    "type": "grid",
    "options": { "width": 1440, "height": 960 },
    "structure": [
      {
        "item": "viz_ingesta_24h",
        "type": "block",
        "position": { "x": 0, "y": 0, "w": 720, "h": 400 }
      }
    ],
    "globalInputs": ["input_time"]
  }
}
```

#### 5.1 Bloques Clave

| Bloque           | Función                                                          |
| ---------------- | ---------------------------------------------------------------- |
| `dataSources`    | Búsquedas (`ds.search`) o derivadas (`ds.chain`)                 |
| `visualizations` | Cada panel visual, referenciando un `dataSources`                |
| `inputs`         | Inputs globales o de panel; referenciados por `token` en queries |
| `defaults`       | Valores por defecto para tipos de dataSource/visualización       |
| `layout`         | `"absolute"` (px libre) o `"grid"` (bloques con posición)        |

#### 5.2 Tipos de Visualización Exclusivos de Studio

| Tipo                                             | Caso de uso                                           |
| ------------------------------------------------ | ----------------------------------------------------- |
| `splunk.sankeydiagram`                           | Flujos entre categorías (p.ej. alertas→estado→cierre) |
| `splunk.parallelcoordinates`                     | Análisis multidimensional                             |
| `splunk.choroplethsvg`                           | Mapas custom con SVG propio (topologías, plantas)     |
| `splunk.timeline`                                | Líneas de tiempo de eventos                           |
| `viz.shape`, `viz.text`, `viz.image`, `viz.icon` | Elementos decorativos libres                          |

#### 5.3 Editor de Código Fuente

`Dashboard → Edit → View Source` - recomendado para copiar/pegar snippets y diffs en Git.

***

### 6. Tokens y Variables

Los tokens son el mecanismo de variables de un dashboard. Se establecen desde inputs, resultados de búsqueda o clics, y se referencian con `$nombre_token$`.

#### 6.1 Definir Token desde Input (Simple XML)

```xml
<fieldset submitButton="false">
  <input type="dropdown" token="tok_cliente" searchWhenChanged="true">
    <label>Cliente</label>
    <choice value="*">Todos</choice>
    <choice value="acme">ACME</choice>
    <choice value="contoso">Contoso</choice>
    <default>*</default>
  </input>
</fieldset>

<row>
  <panel>
    <chart>
      <search>
        <query>index=$tok_cliente$_fw earliest=-24h | timechart count</query>
      </search>
    </chart>
  </panel>
</row>
```

#### 6.2 Tokens Calculados con `<eval>` y `<set>`

```xml
<drilldown>
  <eval token="tok_severidad_color">
    case(severity=="critical","0xdc4e41", severity=="high","0xf8be34", 1==1,"0x53a051")
  </eval>
  <set token="tok_host">$click.value$</set>
  <unset token="tok_detalle_anterior"></unset>
</drilldown>
```

#### 6.3 Visibilidad Condicional con `depends`/`rejects`

```xml
<!-- Panel visible solo si tok_host tiene valor -->
<panel depends="$tok_host_detalle$">
  <title>Detalle de $tok_host$</title>
  ...
</panel>

<!-- Panel oculto mientras tok_loading esté seteado -->
<panel rejects="$tok_loading$">
  ...
</panel>
```

#### 6.4 Tokens en Dashboard Studio

```json
"dataSources": {
  "ds_host": {
    "type": "ds.search",
    "options": {
      "query": "index=acme_* host=$tok_host$ earliest=$global_time.earliest$ latest=$global_time.latest$"
    }
  }
}
```

Studio soporta tokens de input, de resultado de búsqueda (`primary.result.<campo>`) y de clic (`row.<campo>` en handler `setToken`). No soporta `<unset>` explícito - la lógica condicional se resuelve con `visibility` por expresión booleana.

#### 6.5 Tokens de URL

Los tokens prefijados con `form.` se pasan como parámetros de URL y persisten al recargar:

```
/app/mdr/host_detail?form.tok_host=server01&form.tok_time.earliest=-24h
```

```xml
<!-- Recibir token de URL en el dashboard de destino -->
<input type="text" token="tok_host">
  <label>Host</label>
  <default>*</default>
</input>
```

***

### 7. Inputs

| Tipo       | Simple XML | Dashboard Studio       | Uso típico                          |
| ---------- | ---------- | ---------------------- | ----------------------------------- |
| Time range | Sí         | Sí (`input.timerange`) | Selector temporal global            |
| Dropdown   | Sí         | Sí (`input.dropdown`)  | Cliente, índice, severidad          |
| Text       | Sí         | Sí (`input.text`)      | Búsqueda por host/usuario/IP        |
| Radio      | Sí         | **No**                 | Alternativa visual a dropdown corto |
| Checkbox   | Sí         | **No**                 | Selección múltiple                  |
| Link list  | Sí         | **No**                 | Navegación tipo pestañas            |

#### 7.1 Input de Tiempo (Simple XML)

```xml
<input type="time" token="tok_time" searchWhenChanged="true">
  <label>Rango</label>
  <default>
    <earliest>-24h@h</earliest>
    <latest>now</latest>
  </default>
</input>
```

#### 7.2 Dropdown Dinámico (poblado desde búsqueda)

```xml
<input type="dropdown" token="tok_indice">
  <label>Índice</label>
  <fieldForLabel>index</fieldForLabel>
  <fieldForValue>index</fieldForValue>
  <search>
    <query>| eventcount summarize=false index=* | dedup index | table index | sort index</query>
  </search>
  <choice value="*">Todos</choice>
  <default>*</default>
</input>
```

#### 7.3 Input de Radio (alternativa visual a dropdown)

```xml
<input type="radio" token="tok_severidad">
  <label>Severidad</label>
  <choice value="*">Todas</choice>
  <choice value="critical">Critical</choice>
  <choice value="high">High</choice>
  <default>*</default>
</input>
```

#### 7.4 Input de Tiempo en Dashboard Studio

```json
"inputs": {
  "input_global_time": {
    "type": "input.timerange",
    "options": { "token": "global_time", "defaultValue": "-24h@h,now" },
    "title": "Rango temporal"
  }
}
```

***

### 8. Drilldown e Interactividad

#### 8.1 Ir a Otro Dashboard con Contexto (Simple XML)

```xml
<table>
  <search><query>index=acme_edr | stats count by host</query></search>
  <drilldown>
    <link target="_blank">/app/mdr/host_detail?form.tok_host=$row.host$&amp;form.tok_time.earliest=$tok_time.earliest$</link>
  </drilldown>
</table>
```

#### 8.2 Set Token al Hacer Clic

```xml
<drilldown>
  <set token="tok_host_click">$click.value2$</set>
  <set token="tok_host_detalle">true</set>
</drilldown>
```

#### 8.3 Drilldown Condicional (exclusivo de Simple XML)

```xml
<drilldown>
  <condition field="severity" match="critical">
    <set token="tok_color">red</set>
    <link>/app/mdr/critical_incident?form.tok_host=$row.host$</link>
  </condition>
  <condition>
    <set token="tok_color">green</set>
  </condition>
</drilldown>
```

#### 8.4 Drilldown en Dashboard Studio

```json
"visualizations": {
  "viz_tabla_hosts": {
    "type": "splunk.table",
    "dataSources": { "primary": "ds_hosts" },
    "eventHandlers": [
      {
        "type": "drilldown.setToken",
        "options": { "tokens": [ { "token": "tok_host", "key": "row.host.value" } ] }
      },
      {
        "type": "drilldown.linkToSearch",
        "options": { "search": "index=acme_edr host=$tok_host$" }
      }
    ]
  }
}
```

#### 8.5 JavaScript Custom (solo Simple XML)

```xml
<!-- data/ui/views/mi_dashboard.xml -->
<dashboard script="mi_dashboard.js" stylesheet="mi_dashboard.css">
```

```
$SPLUNK_HOME/etc/apps/<app>/appserver/static/mi_dashboard.js
$SPLUNK_HOME/etc/apps/<app>/appserver/static/mi_dashboard.css
```

**Patrón JS de ejemplo: refrescar un panel dinámicamente**

```jsx
require(["splunkjs/mvc/simplexml/ready!"], function() {
  var mvc = require("splunkjs/mvc");
  // Obtener el token manager
  var tokens = mvc.Components.get("default");
  tokens.on("change:tok_host", function(model, value) {
    console.log("Host seleccionado:", value);
    // Aquí lógica custom
  });
});
```

***

### 9. Búsquedas Base, Post-Process y Chained Searches

Reutilizar una única búsqueda pesada para varios paneles reduce drásticamente la carga en Search Head e indexers.

#### 9.1 Simple XML - `base` + Post-Process

```xml
<!-- Búsqueda base ejecutada una sola vez -->
<search id="base_ingesta">
  <query>index=acme_* earliest=-24h | eval hour=strftime(_time,"%H")</query>
</search>

<row>
  <panel>
    <title>Por Índice</title>
    <chart>
      <search base="base_ingesta">
        <query>| timechart span=1h count by index</query>
      </search>
    </chart>
  </panel>
  <panel>
    <title>Por Sourcetype</title>
    <chart>
      <search base="base_ingesta">
        <query>| stats count by sourcetype</query>
      </search>
    </chart>
  </panel>
</row>
```

> **Límite:** el post-process solo puede usar comandos sobre los campos que la búsqueda base ya devuelve. No puede añadir filtros en campos no proyectados por la base.

#### 9.2 Dashboard Studio - `ds.chain`

```json
"dataSources": {
  "ds_base": {
    "type": "ds.search",
    "options": { "query": "index=acme_* earliest=-24h" }
  },
  "ds_por_indice": {
    "type": "ds.chain",
    "options": {
      "extend": "ds_base",
      "query": "| timechart span=1h count by index"
    }
  },
  "ds_por_sourcetype": {
    "type": "ds.chain",
    "options": {
      "extend": "ds_base",
      "query": "| stats count by sourcetype | sort -count"
    }
  }
}
```

#### 9.3 Búsqueda Guardada Compartida

```xml
<search ref="rep_volumen_diario_por_cliente" app="mdr"/>
```

Reutiliza un `savedsearches.conf` existente - recomendado cuando la misma búsqueda alimenta un dashboard y una alerta.

***

### 10. Permisos, Compartir y Exportación

#### 10.1 Permisos de Knowledge Object

`Dashboards → [dashboard] → Edit Permissions`:

| Nivel                | Alcance                                                         |
| -------------------- | --------------------------------------------------------------- |
| Private              | Solo el creador                                                 |
| App                  | Todos los usuarios con acceso a la app                          |
| All apps (Global)    | Toda la instancia                                               |
| Read / Write por rol | Granularidad fina (`sc_admin`, `sc_analyst`, `customer_viewer`) |

Vía `.conf`:

```
# metadata/local.meta o default.meta de la app
[views/salud_ingesta]
access= read : [ * ], write : [ admin, sc_admin ]
export= system
```

#### 10.2 Programar Envío PDF por Email

```
# savedsearches.conf - reporte con delivery PDF
[Reporte Semanal Ingesta - ACME]
search= | savedsearch "Panel Ingesta ACME"
dispatch.earliest_time= -7d@d
dispatch.latest_time= now
cron_schedule= 0 7 * * 1
action.email=1
action.email.to= soc-acme@yes.com
action.email.sendresults=1
action.email.format= pdf
action.email.pdfview= salud_ingesta
action.email.subject= [MDR] Reporte Semanal ACME
```

#### 10.3 Exportación Manual

| Acción                                | Simple XML                                                | Dashboard Studio                     |
| ------------------------------------- | --------------------------------------------------------- | ------------------------------------ |
| Export CSV de un panel                | Sí                                                        | Sí                                   |
| Export PNG de una visualización       | No                                                        | Sí                                   |
| Export PDF/PNG del dashboard completo | Vía `Export → PDF` (pdfgen, puede diferir del layout web) | Vía `Export`, preserva estilo exacto |

#### 10.4 Clonar y Versionar

`Edit → Clone` para duplicar sin afectar el original. Para versionado real: exportar el `.xml` desde `$SPLUNK_HOME/etc/apps/<app>/local/data/ui/views/` y versionarlo en el repositorio Git de la app MDR.

***

### 11. Rendimiento y Buenas Prácticas

#### 11.1 Reglas Generales

* **`tstats` sobre Data Models acelerados** para dashboards consultados frecuentemente. Un panel con `tstats` puede ser 10-50x más rápido que el equivalente `stats` sobre datos crudos.
* **Evitar `join`** en paneles - preferir `stats`/`lookup` (ver `SPL.md` §7.3).
* **Filtrar por tiempo** - nunca dejar un panel sin `earliest`/rango de input.
* **Compartir búsquedas base** (§9) entre paneles del mismo dashboard en vez de repetir el mismo `index=...` N veces.
* **Limitar paneles por dashboard** - más de 8-10 paneles concurrentes degrada la carga inicial. Dividir en varios dashboards enlazados por drilldown.
* **Real-time dashboards tienen coste alto**: sustituir por `refresh="60"` sobre búsqueda histórica corta en lugar de `earliest=rt`.
* **Auto-refresh conservador**: `<dashboard refresh="300">` (5 minutos) como mínimo para dashboards operativos.
* **`sample_ratio`** en paneles de exploración (no en KPIs) para previsualizar rápido sobre grandes volúmenes.

#### 11.2 Auditoría de Rendimiento de Dashboards

```
# Búsquedas más lentas vinculadas a dashboards
index=_audit action=search info=completed earliest=-1d
| eval runtime_sec = round(total_run_time, 1)
| where runtime_sec > 30
| sort -runtime_sec
| table _time, user, search, runtime_sec, event_count

# Dashboards con más carga de vistas
index=_internal sourcetype=splunkd_ui_access uri_path="*view*"
| rex field=uri_path "/data/ui/views/(?<dashboard>[^/]+)"
| stats count by dashboard
| sort -count

# Búsquedas programadas omitidas (scheduled searches)
index=_audit action=search info=skipped earliest=-24h
| stats count by savedsearch_name, reason
| sort -count
```

#### 11.3 Impacto por Optimización SPL en Paneles

| Optimización                            | Reducción carga Indexer      | Reducción memoria SH |
| --------------------------------------- | ---------------------------- | -------------------- |
| Filtro `index=` + tiempo                | 80-90%                       | 70%                  |
| `tstats` con data model                 | 85%                          | 80%                  |
| `fields` temprano en pipeline           | 40%                          | 50%                  |
| `stats` en vez de `transaction`         | 60-70%                       | 40%                  |
| Búsqueda base compartida (post-process) | 60-80% (por panel adicional) | Variable             |

#### 11.4 Checklist Pre-Publicación

```
[ ] Todos los paneles tienen earliest/latest explícito o heredan de input global
[ ] No hay join innecesario - se usó stats/lookup
[ ] Paneles frecuentes usan tstats sobre data model acelerado
[ ] Permisos revisados (no quedó en Private un dashboard operativo)
[ ] Drilldown probado en al menos un panel por fila
[ ] Título, descripción y naming siguen convención de §15
[ ] Refresh configurado si aplica (no real-time salvo necesidad justificada)
[ ] Dashboard probado en modo oscuro y claro si se va a exportar PDF
```

***

### 12. Alertas Programadas vinculadas a Dashboards

#### 12.1 Patrón: Alerta que Abre Dashboard con Contexto

```
# savedsearches.conf
[MDR_Brute_Force_SSH]
search= index=fw ce.category="brute force" destinationPort=22 earliest=-15m@m latest=-5m@m \
         | stats count as attempts by src \
         | where attempts > 50
cron_schedule= */15 * * * *
alert.track=1
action.email=1
action.email.to= soc@yes.com
action.email.subject= [ALERTA] Brute Force SSH detectado
action.email.message.alert= Ataque desde $result.src$ ($result.attempts$ intentos). \
  Dashboard: <https://splunk.yes.com/app/mdr/salud_ingesta?form.tok_host=$result.src$>
```

#### 12.2 Panel de KPI con Umbral de Color

```xml
<single>
  <search>
    <query>
      index=fw ce.category="brute force" destinationPort=22 earliest=-15m
      | stats count as attacks
    </query>
    <refresh>300</refresh>
  </search>
  <option name="colorMode">block</option>
  <option name="rangeColors">["0x53a051","0xf8be34","0xdc4e41"]</option>
  <option name="rangeValues">[10,50]</option>
  <option name="underLabel">ataques SSH últimos 15 min</option>
  <option name="drilldown">all</option>
  <drilldown>
    <link>/app/mdr/brute_force_detail</link>
  </drilldown>
</single>
```

***

### 13. Migración Simple XML → Dashboard Studio

#### 13.1 Proceso

`Dashboard → Edit → Convert to Dashboard Studio` genera una copia en el nuevo formato dejando el original Simple XML intacto.

#### 13.2 Qué Se Pierde o Cambia al Migrar

| Elemento en Simple XML                              | Resultado tras convertir                             |
| --------------------------------------------------- | ---------------------------------------------------- |
| JavaScript / CSS custom                             | Se descarta - rediseñar sin JS                       |
| Inputs `radio`/`checkbox`/`link list`               | Rehacer como `dropdown` o eliminar                   |
| Drilldown `<condition>`/`<unset>`                   | Se simplifica a `visibility` - revisar caso por caso |
| Thresholding de color (`rangeColors`/`rangeValues`) | Cambia a `options.colorPalette` - reconfigurar       |
| Post-process `base=`                                | Se traduce a `ds.chain` - verificar query            |

#### 13.3 Recomendación MDR

Migrar primero los dashboards **de reporting a cliente** (bajo uso de JS/condicionales, alto valor visual/exportación). Mantener en Simple XML los dashboards operativos con drilldown condicional pesado hasta confirmar equivalencia funcional en Studio.

#### 13.4 Checklist de Migración

```
[ ] Inventariar inputs del dashboard - identificar radio/checkbox a reemplazar
[ ] Inventariar drilldowns condicionales - mapear a visibility expressions
[ ] Verificar que no hay JS embebido o scripts externos referenciados
[ ] Convertir (mantener original Simple XML como backup)
[ ] Validar que todos los paneles cargan correctamente en Studio
[ ] Validar el export PDF
[ ] Probar drilldowns y tokens
[ ] Actualizar documentación interna
```

***

### 14. Dashboards SOC - Ejemplos MDR

#### 14.1 Salud de Ingesta (Simple XML - Dashboard Operativo)

```xml
<dashboard version="1.1" theme="dark" refresh="300">
  <label>Salud de Ingesta</label>
  <description>Volumen e integridad de ingesta por cliente · MDR</description>
  <fieldset submitButton="false">
    <input type="dropdown" token="tok_cliente">
      <label>Cliente</label>
      <choice value="*">Todos</choice>
      <search><query>| rest /services/data/indexes | dedup title | table title | sort title</query></search>
      <fieldForLabel>title</fieldForLabel>
      <fieldForValue>title</fieldForValue>
      <default>*</default>
    </input>
    <input type="time" token="tok_time">
      <label>Rango</label>
      <default><earliest>-24h@h</earliest><latest>now</latest></default>
    </input>
  </fieldset>

  <row>
    <panel>
      <title>Eventos Indexados (Total)</title>
      <single>
        <search>
          <query>index=$tok_cliente$_* earliest=$tok_time.earliest$ latest=$tok_time.latest$ | stats count</query>
        </search>
      </single>
    </panel>
    <panel>
      <title>Fuentes Sin Datos > 30 min</title>
      <single>
        <search>
          <query>
            | metadata type=sourcetypes index=$tok_cliente$_*
            | eval mins_sin_datos=round((now()-lastTime)/60,0)
            | where mins_sin_datos > 30
            | stats count
          </query>
        </search>
        <option name="rangeColors">["0x53a051","0xdc4e41"]</option>
        <option name="rangeValues">[0,1]</option>
      </single>
    </panel>
    <panel>
      <title>Índices Activos</title>
      <single>
        <search>
          <query>| rest /services/data/indexes | where NOT disabled | stats count</query>
        </search>
      </single>
    </panel>
  </row>
  <row>
    <panel>
      <title>Volumen por Índice (por hora)</title>
      <chart>
        <search>
          <query>index=$tok_cliente$_* earliest=$tok_time.earliest$ latest=$tok_time.latest$ | timechart span=1h count by index</query>
        </search>
        <option name="charting.chart">column</option>
        <option name="charting.chart.stackMode">stacked</option>
        <option name="charting.legend.placement">bottom</option>
      </chart>
    </panel>
  </row>
  <row>
    <panel>
      <title>Fuentes con retraso > 30 min</title>
      <table>
        <search>
          <query>
            | metadata type=sourcetypes index=$tok_cliente$_*
            | eval mins=round((now()-lastTime)/60,0)
            | where mins > 30
            | table index, sourcetype, mins, totalCount
            | sort -mins
          </query>
        </search>
        <option name="drilldown">none</option>
      </table>
    </panel>
  </row>
</dashboard>
```

#### 14.2 Salud de Agentes y Forwarders

```xml
<row>
  <panel>
    <title>Forwarders Sin Check-in > 15 min</title>
    <table>
      <search>
        <query>
          | rest /services/deployment/server/clients
          | eval mins=round((now()-utcStamp)/60,0)
          | where mins > 15
          | table hostname, ip, mins, dependentApps
          | sort -mins
        </query>
      </search>
      <option name="drilldown">cell</option>
    </table>
  </panel>
  <panel>
    <title>Errores de Parsing por TA (24h)</title>
    <chart>
      <search>
        <query>
          index=_internal sourcetype=splunkd log_level=ERROR component=AggregatorMiningProcessor earliest=-24h
          | rex field=data_source "(?&lt;ta&gt;TA-[^/\\]+)"
          | stats count by ta
          | sort -count
        </query>
      </search>
      <option name="charting.chart">bar</option>
    </chart>
  </panel>
</row>
```

#### 14.3 Resumen de Detecciones / Notables (Dashboard Studio)

```json
{
  "title": "Resumen de Detecciones - 24h",
  "dataSources": {
    "ds_notables": {
      "type": "ds.search",
      "options": {
        "query": "`notable` earliest=-24h | stats count by urgency, security_domain"
      }
    },
    "ds_notables_trend": {
      "type": "ds.search",
      "options": {
        "query": "`notable` earliest=-7d | timechart span=1d count by urgency"
      }
    }
  },
  "visualizations": {
    "viz_por_urgencia": {
      "type": "splunk.pie",
      "dataSources": { "primary": "ds_notables" },
      "title": "Notables por Urgencia"
    },
    "viz_por_dominio": {
      "type": "splunk.column",
      "dataSources": { "primary": "ds_notables" },
      "title": "Notables por Dominio de Seguridad"
    },
    "viz_trend": {
      "type": "splunk.line",
      "dataSources": { "primary": "ds_notables_trend" },
      "title": "Tendencia 7 días"
    }
  },
  "layout": {
    "type": "grid",
    "structure": [
      { "item": "viz_por_urgencia", "type": "block", "position": { "x": 0, "y": 0, "w": 360, "h": 360 } },
      { "item": "viz_por_dominio", "type": "block", "position": { "x": 360, "y": 0, "w": 360, "h": 360 } },
      { "item": "viz_trend", "type": "block", "position": { "x": 0, "y": 360, "w": 720, "h": 300 } }
    ]
  }
}
```

#### 14.4 Vista 360 por Cliente

```xml
<dashboard version="1.1">
  <label>Vista 360 - Cliente</label>
  <fieldset submitButton="true">
    <input type="text" token="tok_cliente"><label>Prefijo de Cliente</label><default>acme</default></input>
    <input type="time" token="tok_time"><default><earliest>-24h</earliest><latest>now</latest></default></input>
  </fieldset>
  <row>
    <panel>
      <title>Login Fallidos → Éxito (posible brute force)</title>
      <table>
        <search>
          <query>
            index=$tok_cliente$_win earliest=$tok_time.earliest$ latest=$tok_time.latest$
            | eval outcome=if(EventCode="4625","fail","success")
            | stats count(eval(outcome="fail")) as fails, count(eval(outcome="success")) as successes by user, src_ip
            | where fails > 5 AND successes > 0
            | sort -fails
          </query>
        </search>
      </table>
    </panel>
  </row>
  <row>
    <panel>
      <title>Tráfico Denegado por Firewall (Top 10 destinos)</title>
      <chart>
        <search>
          <query>index=$tok_cliente$_fw action=denied earliest=$tok_time.earliest$ | top limit=10 dest</query>
        </search>
        <option name="charting.chart">bar</option>
      </chart>
    </panel>
    <panel>
      <title>Eventos por Sourcetype</title>
      <chart>
        <search>
          <query>index=$tok_cliente$_* earliest=$tok_time.earliest$ latest=$tok_time.latest$ | stats count by sourcetype | sort -count | head 10</query>
        </search>
        <option name="charting.chart">pie</option>
      </chart>
    </panel>
  </row>
</dashboard>
```

#### 14.5 Dashboard de Threat Hunting (Simple XML)

```xml
<dashboard version="1.1" theme="dark">
  <label>Threat Hunting - Análisis Ad-Hoc</label>
  <fieldset submitButton="true">
    <input type="time" token="tok_time">
      <label>Ventana de análisis</label>
      <default><earliest>-7d</earliest><latest>now</latest></default>
    </input>
    <input type="text" token="tok_src">
      <label>IP Origen (opcional)</label>
      <default>*</default>
    </input>
  </fieldset>
  <row>
    <panel>
      <title>Beaconing - Conexiones Regulares (jitter bajo)</title>
      <table>
        <search>
          <query>
            index=fw_production action=allow src=$tok_src$ earliest=$tok_time.earliest$ latest=$tok_time.latest$
            | bin _time span=1m
            | stats count as connections by src, dest, _time
            | stats count as active_minutes, stdev(connections) as jitter, avg(connections) as avg_rate by src, dest
            | where active_minutes > 60 AND jitter < 2 AND avg_rate >= 1
            | eval regularity = round(jitter, 3)
            | sort regularity
          </query>
        </search>
        <option name="drilldown">cell</option>
      </table>
    </panel>
  </row>
  <row>
    <panel>
      <title>DNS Tunneling (queries largas o alta frecuencia)</title>
      <table>
        <search>
          <query>
            index=dns_production src=$tok_src$ earliest=$tok_time.earliest$ latest=$tok_time.latest$
            | eval query_length = len(query)
            | stats count as dns_queries, avg(query_length) as avg_qlen, max(query_length) as max_qlen, dc(query) as unique_queries by src, dest
            | where avg_qlen > 40 OR max_qlen > 100 OR dns_queries > 500
            | sort -dns_queries
          </query>
        </search>
      </table>
    </panel>
  </row>
</dashboard>
```

***

### 15. Convenciones de Nomenclatura y Gobernanza

#### 15.1 Convención de Nombres de Dashboards

**Formato:** `DASH_<SERVICIO>_<OBJETIVO>_<PLATAFORMA>`

```
DASH_MDR_THREAT_OVERVIEW_SPLUNK
DASH_FIREWALL_ANOMALY_DETECTION_SPLUNK
DASH_WAF_DDoS_ANALYSIS_SPLUNK
DASH_AUTH_PRIVILEGE_ESCALATION_SPLUNK
DASH_MDR_INGESTA_SALUD_SPLUNK
DASH_MDR_AGENTES_SALUD_SPLUNK
```

#### 15.2 Convención de IDs de Dashboard (URL)

El ID en la URL debe ser minúsculas con guiones bajos:

```
salud_ingesta          → /app/mdr/salud_ingesta
resumen_detecciones    → /app/mdr/resumen_detecciones
vista_360_cliente      → /app/mdr/vista_360_cliente
```

#### 15.3 Campos de Metadatos Obligatorios

Todo dashboard publicado debe incluir:

```xml
<dashboard version="1.1" theme="dark">
  <label>DASH_MDR_INGESTA_SALUD_SPLUNK</label>
  <description>
    Propósito: Monitorización de volumen e integridad de ingesta por cliente
    Audiencia: SOC L1/L2, Plataforma
    Owner: team-splunk-platform
    Revisión: Trimestral
    Última actualización: 2026-07
  </description>
  ...
```

#### 15.4 Ciclo de Vida y Auditoría

```
# Dashboards sin uso reciente (posiblemente obsoletos)
index=_internal sourcetype=splunkd_ui_access uri_path="*/data/ui/views/*" earliest=-90d
| rex field=uri_path "/data/ui/views/(?<dashboard>[^?]+)"
| stats dc(date_mday) as days_used, max(_time) as last_access by dashboard
| where days_used < 5
| sort last_access
| convert ctime(last_access)
```

***

### 16. Referencias Oficiales

* **Dashboard Studio - Introducción:** [Dashboard Studio Guide](https://docs.splunk.com/Documentation/Splunk/latest/Viz/NewDashboards)
* **Dashboard Studio - Fuentes de datos:** [Data Sources in Dashboard Studio](https://docs.splunk.com/Documentation/Splunk/latest/Viz/DataSources)
* **Dashboard Studio - Interactividad:** [Make Dashboards Dynamic](https://docs.splunk.com/Documentation/Splunk/latest/Viz/DynamicDrilldown)
* **Dashboard Studio - Referencia de opciones:** [Configuration Options Reference](https://docs.splunk.com/Documentation/Splunk/latest/Viz/DashboardOptions)
* **Simple XML - Guía general:** [Simple XML Dashboards](https://docs.splunk.com/Documentation/Splunk/latest/Viz/Dashboardsoverview)
* **Simple XML - Referencia completa de tags:** [Simple XML Reference](https://docs.splunk.com/Documentation/Splunk/latest/Viz/SimplexmlReferences)
* **Simple XML - Drilldown:** [Drilldown and Dashboard Interactivity](https://docs.splunk.com/Documentation/Splunk/latest/Viz/Drilldown)
* **API para Dashboards:** [Splunk REST API → saved/searches](https://docs.splunk.com/Documentation/Splunk/latest/RESTREF/RESTsearch#saved.2Fsearches)
* **Gestionar y compartir dashboards:** [Manage and Share Dashboards](https://docs.splunk.com/Documentation/Splunk/latest/Viz/ManageDashboards)
* **Cross-referencia interna:**
  * `SPL.md` / `Documentación Completa de SPL.md` - Patrones SPL para dashboards y alertas programadas
  * `Integraciones.md` - Fuentes de datos usadas en los ejemplos de §14
  * `Plataforma.md` §7 - Monitoring Console (dashboards de salud de plataforma)
