---
name: sign_metabase_embed_jwt
kind: function
lang: py
domain: infra
version: "1.0.0"
purity: impure
signature: "def sign_metabase_embed_jwt(secret: str, resource_type: str, resource_id: int, base_url: str, params: dict | None = None, exp_seconds: int = 3600, theme: str | None = None, bordered: bool = True, titled: bool = True) -> dict"
description: "Firma un JWT de static-embedding de Metabase (HS256 con PyJWT) y construye la URL del iframe (/embed/<type>/<token>#opciones). Soporta question y dashboard, params locked/enabled, TTL configurable y tema opcional."
tags: [metabase, embed, jwt]
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: ["jwt", "time"]
tested: true
tests: ["test_firma_y_decodifica_round_trip", "test_resource_type_invalido_lanza_valueerror", "test_embed_url_dashboard"]
test_file_path: "python/functions/infra/sign_metabase_embed_jwt_test.py"
file_path: "python/functions/infra/sign_metabase_embed_jwt.py"
params:
  - name: secret
    desc: "Secret de embedding de Metabase (Settings > Embedding). NUNCA va al cliente."
  - name: resource_type
    desc: '"question" o "dashboard" — tipo del recurso a embeber.'
  - name: resource_id
    desc: "Id numerico de la card o dashboard en Metabase."
  - name: base_url
    desc: 'URL base de la instancia, ej. "https://reports.autingo.es".'
  - name: params
    desc: "Parametros de embedding locked/enabled (dict). Default {}."
  - name: exp_seconds
    desc: "TTL del token en segundos desde ahora. Default 3600 (1h)."
  - name: theme
    desc: 'Tema opcional: "night" | "transparent" | None.'
  - name: bordered
    desc: "Si el iframe muestra borde. Default True."
  - name: titled
    desc: "Si el iframe muestra titulo. Default True."
output: 'dict con {"token": jwt str, "embed_url": url completa /embed/<type>/<token>#opciones, "exp": unix int}.'
---

## Ejemplo

```python
import sys, os
sys.path.insert(0, os.path.join("python", "functions"))
from infra import sign_metabase_embed_jwt

# El secret lo pasa el caller (p.ej. desde pass: metabase/aurgi-embed-secret)
result = sign_metabase_embed_jwt(
    secret=os.environ["MB_EMBED_SECRET"],
    resource_type="question",
    resource_id=8048,
    base_url="https://reports.autingo.es",
)
print(result["embed_url"])
# https://reports.autingo.es/embed/question/<jwt>#bordered=true&titled=true
print(result["exp"])  # unix timestamp de expiracion
```

## Cuando usarla

Cuando necesites embeder una card/dashboard de Metabase en un iframe sin exponerla publicamente: firma server-side en cada carga, nunca hardcodees el token.

## Gotchas

- El `secret` NUNCA va al cliente — la firma se hace server-side y solo viaja el token al iframe.
- El token expira (`exp_seconds`, default 1h). Refirma en cada carga; no caches el token.
- La card/dashboard necesita `enable_embedding=true` en Metabase, o el endpoint de embed devuelve error.
- Los `params` deben estar registrados en `embedding_params` como `locked` o `enabled` en Metabase, o se ignoran silenciosamente.
- Verificado contra reports.autingo.es: el endpoint `/api/embed/card/<token>/query` devuelve 202 + filas con token valido, 400 con token manipulado.
