# unit_tests

id: metabase_mbql_validate_py_core_t5
function_id: metabase_mbql_validate_py_core
name: test_name_collision_detected
file_path: python/functions/metabase/test_metabase_mbql_validate.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_name_collision_detected():
      """Dos expressions con mismo lib/expression-name en la misma stage generan error."""
      dq = {
          "stages": [
              {
                  "lib/type": "mbql.stage/mbql",
                  "expressions": [
                      ["-", {"lib/uuid": "e1", "lib/expression-name": "MiCalculo"},
                       ["field", {"base-type": "type/Decimal", "lib/uuid": "f1"}, "A"],
                       ["field", {"base-type": "type/Decimal", "lib/uuid": "f2"}, "B"]],
                      ["+", {"lib/uuid": "e2", "lib/expression-name": "MiCalculo"},
                       ["field", {"base-type": "type/Decimal", "lib/uuid": "f3"}, "C"],
                       1],
                  ],
              }
          ]
      }
      errors = metabase_mbql_validate(dq)
      assert any("MiCalculo" in e for e in errors), f"Esperaba error de name collision, got: {errors}"


  # ---------------------------------------------------------------------------
  # Check: stages ausente
  # ---------------------------------------------------------------------------


id: metabase_mbql_validate_py_core_t6
function_id: metabase_mbql_validate_py_core
name: test_missing_stages_returns_error
file_path: python/functions/metabase/test_metabase_mbql_validate.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_missing_stages_returns_error():
      """dataset_query sin 'stages' devuelve error de estructura."""
      errors = metabase_mbql_validate({"database": 1})
      assert any("stages" in e.lower() for e in errors), f"Esperaba error de stages ausente, got: {errors}"


id: metabase_validate_card_payload_py_infra_t0
function_id: metabase_validate_card_payload_py_infra
name: test_card_valido_retorna_lista_vacia
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_valido_retorna_lista_vacia():
      issues = metabase_validate_card_payload(_base_card())
      assert issues == [], f"Se esperaba lista vacia, got: {issues}"


id: metabase_validate_card_payload_py_infra_t1
function_id: metabase_validate_card_payload_py_infra
name: test_card_display_invalido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_display_invalido():
      payload = _base_card()
      payload["display"] = "foobar"
      issues = metabase_validate_card_payload(payload)
      assert any("display" in i and "foobar" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t2
function_id: metabase_validate_card_payload_py_infra
name: test_card_display_ausente
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_display_ausente():
      payload = _base_card()
      del payload["display"]
      issues = metabase_validate_card_payload(payload)
      assert any("display" in i and "ausente" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t3
function_id: metabase_validate_card_payload_py_infra
name: test_card_name_ausente
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_name_ausente():
      payload = _base_card()
      del payload["name"]
      issues = metabase_validate_card_payload(payload)
      assert any("name" in i and "ausente" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t4
function_id: metabase_validate_card_payload_py_infra
name: test_card_name_vacio
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_name_vacio():
      payload = _base_card()
      payload["name"] = "   "
      issues = metabase_validate_card_payload(payload)
      assert any("name" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t5
function_id: metabase_validate_card_payload_py_infra
name: test_card_dataset_query_ausente
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_dataset_query_ausente():
      payload = _base_card()
      del payload["dataset_query"]
      issues = metabase_validate_card_payload(payload)
      assert any("dataset_query" in i and "ausente" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t6
function_id: metabase_validate_card_payload_py_infra
name: test_card_dataset_query_sin_database
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_dataset_query_sin_database():
      payload = _base_card()
      del payload["dataset_query"]["database"]
      issues = metabase_validate_card_payload(payload)
      assert any("database" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t7
function_id: metabase_validate_card_payload_py_infra
name: test_card_nativa_sin_sql
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_nativa_sin_sql():
      payload = _base_card()
      payload["dataset_query"]["native"]["query"] = ""
      issues = metabase_validate_card_payload(payload)
      assert any("SQL" in i or "native" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t8
function_id: metabase_validate_card_payload_py_infra
name: test_card_nativa_mbql5
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_nativa_mbql5():
      """Acepta SQL en stages[0].native (formato MBQL5)."""
      payload = {
          "name": "MBQL5 card",
          "display": "table",
          "dataset_query": {
              "database": 2,
              "stages": [{"native": "SELECT 1"}],
          },
      }
      issues = metabase_validate_card_payload(payload)
      assert issues == [], f"Se esperaba lista vacia, got: {issues}"


id: metabase_validate_card_payload_py_infra_t9
function_id: metabase_validate_card_payload_py_infra
name: test_card_type_invalido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_type_invalido():
      payload = _base_card()
      payload["type"] = "unknown_type"
      issues = metabase_validate_card_payload(payload)
      assert any("type" in i and "unknown_type" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t10
function_id: metabase_validate_card_payload_py_infra
name: test_card_type_valido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_type_valido():
      for t in ("question", "model", "metric"):
          payload = _base_card()
          payload["type"] = t
          issues = metabase_validate_card_payload(payload)
          assert issues == [], f"type={t!r} no deberia dar issues: {issues}"


id: metabase_validate_card_payload_py_infra_t11
function_id: metabase_validate_card_payload_py_infra
name: test_card_visualization_settings_no_dict
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_visualization_settings_no_dict():
      payload = _base_card()
      payload["visualization_settings"] = "no es un dict"
      issues = metabase_validate_card_payload(payload)
      assert any("visualization_settings" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t12
function_id: metabase_validate_card_payload_py_infra
name: test_card_parameters_no_list
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_parameters_no_list():
      payload = _base_card()
      payload["parameters"] = {"not": "a list"}
      issues = metabase_validate_card_payload(payload)
      assert any("parameters" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t13
function_id: metabase_validate_card_payload_py_infra
name: test_card_archived_no_bool
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_archived_no_bool():
      payload = _base_card()
      payload["archived"] = "yes"
      issues = metabase_validate_card_payload(payload)
      assert any("archived" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t14
function_id: metabase_validate_card_payload_py_infra
name: test_card_acumula_multiples_errores
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_card_acumula_multiples_errores():
      """Un payload con varios errores debe retornar todos los issues, no solo el primero."""
      payload = {
          "display": "invalid_display",
          "dataset_query": "not a dict",
          "archived": "yes",
      }
      issues = metabase_validate_card_payload(payload)
      assert len(issues) >= 3, f"Se esperaban >= 3 issues, got {len(issues)}: {issues}"


  # ---------------------------------------------------------------------------
  # metabase_validate_dashboard_payload
  # ---------------------------------------------------------------------------

  def _base_dashboard() -> dict:
      return {"name": "My Dashboard"}


id: metabase_validate_card_payload_py_infra_t15
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_valido_sin_dashcards
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_valido_sin_dashcards():
      issues = metabase_validate_dashboard_payload(_base_dashboard(), known_card_ids=set())
      assert issues == [], issues


id: metabase_validate_card_payload_py_infra_t16
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_valido_con_dashcards
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_valido_con_dashcards():
      payload = {
          "name": "KPIs",
          "dashcards": [
              {"card_id": 1, "row": 0, "col": 0, "size_x": 6, "size_y": 4},
              {"card_id": 2, "row": 0, "col": 6, "size_x": 6, "size_y": 4},
          ],
      }
      issues = metabase_validate_dashboard_payload(payload, known_card_ids={1, 2})
      assert issues == [], issues


id: metabase_validate_card_payload_py_infra_t17
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_card_id_desconocido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_card_id_desconocido():
      payload = {
          "name": "Dashboard",
          "dashcards": [{"card_id": 999, "row": 0, "col": 0, "size_x": 6, "size_y": 4}],
      }
      issues = metabase_validate_dashboard_payload(payload, known_card_ids={1, 2})
      assert any("999" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t18
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_card_virtual_null_permitido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_card_virtual_null_permitido():
      """card_id null = dashcard virtual (texto/heading), siempre permitido."""
      payload = {
          "name": "Dashboard",
          "dashcards": [{"card_id": None, "row": 0, "col": 0, "size_x": 6, "size_y": 2}],
      }
      issues = metabase_validate_dashboard_payload(payload, known_card_ids=set())
      assert issues == [], issues


id: metabase_validate_card_payload_py_infra_t19
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_dashcards_solapadas
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_dashcards_solapadas():
      payload = {
          "name": "Dashboard",
          "dashcards": [
              {"card_id": 1, "row": 0, "col": 0, "size_x": 6, "size_y": 4},
              {"card_id": 2, "row": 2, "col": 2, "size_x": 4, "size_y": 4},
          ],
      }
      issues = metabase_validate_dashboard_payload(payload, known_card_ids={1, 2})
      assert any("solapan" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t20
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_dashcards_adyacentes_no_solapan
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_dashcards_adyacentes_no_solapan():
      """Dos dashcards que se tocan en el borde NO solapan."""
      payload = {
          "name": "Dashboard",
          "dashcards": [
              {"card_id": 1, "row": 0, "col": 0, "size_x": 6, "size_y": 4},
              {"card_id": 2, "row": 0, "col": 6, "size_x": 6, "size_y": 4},
          ],
      }
      issues = metabase_validate_dashboard_payload(payload, known_card_ids={1, 2})
      assert not any("solapan" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t21
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_col_fuera_de_bounds
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_col_fuera_de_bounds():
      payload = {
          "name": "Dashboard",
          "dashcards": [{"card_id": 1, "row": 0, "col": 25, "size_x": 1, "size_y": 1}],
      }
      issues = metabase_validate_dashboard_payload(payload, known_card_ids={1})
      assert any("col" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t22
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_col_mas_size_x_excede_grid
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_col_mas_size_x_excede_grid():
      payload = {
          "name": "Dashboard",
          "dashcards": [{"card_id": 1, "row": 0, "col": 20, "size_x": 6, "size_y": 4}],
      }
      issues = metabase_validate_dashboard_payload(payload, known_card_ids={1})
      assert any("24" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t23
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_size_y_fuera_de_bounds
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_size_y_fuera_de_bounds():
      payload = {
          "name": "Dashboard",
          "dashcards": [{"card_id": 1, "row": 0, "col": 0, "size_x": 6, "size_y": 0}],
      }
      issues = metabase_validate_dashboard_payload(payload, known_card_ids={1})
      assert any("size_y" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t24
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_name_ausente
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_name_ausente():
      issues = metabase_validate_dashboard_payload({}, known_card_ids=set())
      assert any("name" in i and "ausente" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t25
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_tabs_invalidos
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_tabs_invalidos():
      payload = {"name": "Dashboard", "tabs": [{"name": "Tab1"}]}  # falta id
      issues = metabase_validate_dashboard_payload(payload, known_card_ids=set())
      assert any("id" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t26
function_id: metabase_validate_card_payload_py_infra
name: test_dashboard_parameters_no_list
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_dashboard_parameters_no_list():
      payload = {"name": "Dashboard", "parameters": "not a list"}
      issues = metabase_validate_dashboard_payload(payload, known_card_ids=set())
      assert any("parameters" in i for i in issues), issues


  # ---------------------------------------------------------------------------
  # metabase_validate_document_payload
  # ---------------------------------------------------------------------------

  from metabase.validation import metabase_validate_document_payload


  def _base_doc(content):
      return {"name": "Notas", "document": {"type": "doc", "content": content}}


id: metabase_validate_card_payload_py_infra_t27
function_id: metabase_validate_card_payload_py_infra
name: test_document_valido_minimo
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_valido_minimo():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "paragraph", "content": [{"type": "text", "text": "hola"}]}
      ]))
      assert issues == [], issues


id: metabase_validate_card_payload_py_infra_t28
function_id: metabase_validate_card_payload_py_infra
name: test_document_name_ausente
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_name_ausente():
      issues = metabase_validate_document_payload({"document": {"type": "doc", "content": []}})
      assert any("name" in i and "ausente" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t29
function_id: metabase_validate_card_payload_py_infra
name: test_document_nodo_desconocido_callout
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_nodo_desconocido_callout():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "callout", "content": [{"type": "paragraph", "content": [{"type": "text", "text": "x"}]}]}
      ]))
      assert any("callout" in i and "no soportado" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t30
function_id: metabase_validate_card_payload_py_infra
name: test_document_nodo_desconocido_taskList
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_nodo_desconocido_taskList():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "taskList", "content": []}
      ]))
      assert any("taskList" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t31
function_id: metabase_validate_card_payload_py_infra
name: test_document_mark_desconocido_underline
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_mark_desconocido_underline():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "paragraph", "content": [
              {"type": "text", "marks": [{"type": "underline"}], "text": "x"}
          ]}
      ]))
      assert any("underline" in i and "no soportado" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t32
function_id: metabase_validate_card_payload_py_infra
name: test_document_heading_level_invalido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_heading_level_invalido():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "heading", "attrs": {"level": 9}, "content": [{"type": "text", "text": "x"}]}
      ]))
      assert any("level" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t33
function_id: metabase_validate_card_payload_py_infra
name: test_document_cardEmbed_sin_id_ni_slug
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_cardEmbed_sin_id_ni_slug():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "cardEmbed", "attrs": {}}
      ]))
      assert any("cardEmbed" in i and "id" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t34
function_id: metabase_validate_card_payload_py_infra
name: test_document_cardEmbed_con_id_valido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_cardEmbed_con_id_valido():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "cardEmbed", "attrs": {"id": 42}}
      ]))
      assert issues == [], issues


id: metabase_validate_card_payload_py_infra_t35
function_id: metabase_validate_card_payload_py_infra
name: test_document_cardEmbed_slug_desconocido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_cardEmbed_slug_desconocido():
      issues = metabase_validate_document_payload(
          _base_doc([{"type": "cardEmbed", "attrs": {"card": "inventado"}}]),
          known_card_slugs={"real_one", "real_two"},
      )
      assert any("inventado" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t36
function_id: metabase_validate_card_payload_py_infra
name: test_document_flexContainer_demasiados_hijos
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_flexContainer_demasiados_hijos():
      children = [{"type": "cardEmbed", "attrs": {"id": 1}} for _ in range(4)]
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "flexContainer", "attrs": {"columnWidths": [25, 25, 25, 25]}, "content": children}
      ]))
      assert any("1-3" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t37
function_id: metabase_validate_card_payload_py_infra
name: test_document_flexContainer_hijo_invalido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_flexContainer_hijo_invalido():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "flexContainer", "attrs": {"columnWidths": [100]}, "content": [
              {"type": "paragraph", "content": [{"type": "text", "text": "x"}]}
          ]}
      ]))
      assert any("flexContainer solo acepta" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t38
function_id: metabase_validate_card_payload_py_infra
name: test_document_flexContainer_columnWidths_mismatch
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_flexContainer_columnWidths_mismatch():
      issues = metabase_validate_document_payload(_base_doc([
          {"type": "flexContainer", "attrs": {"columnWidths": [50, 50]}, "content": [
              {"type": "cardEmbed", "attrs": {"id": 1}}
          ]}
      ]))
      assert any("columnWidths" in i for i in issues), issues


id: metabase_validate_card_payload_py_infra_t39
function_id: metabase_validate_card_payload_py_infra
name: test_document_vacio_string_es_valido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_vacio_string_es_valido():
      issues = metabase_validate_document_payload({"name": "vacio", "document": ""})
      assert issues == [], issues


id: metabase_validate_card_payload_py_infra_t40
function_id: metabase_validate_card_payload_py_infra
name: test_document_kitchen_sink_valido
file_path: python/functions/metabase/validation_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_document_kitchen_sink_valido():
      """Simula el kitchen_sink real y debe pasar sin issues."""
      content = [
          {"type": "heading", "attrs": {"level": 1}, "content": [{"type": "text", "text": "T"}]},
          {"type": "paragraph", "content": [
              {"type": "text", "text": "a "},
              {"type": "text", "marks": [{"type": "bold"}], "text": "b"},
              {"type": "text", "marks": [{"type": "link", "attrs": {"href": "https://x"}}], "text": "l"},
          ]},
          {"type": "bulletList", "content": [
              {"type": "listItem", "content": [
                  {"type": "paragraph", "content": [{"type": "text", "text": "i"}]}
              ]}
          ]},
          {"type": "blockquote", "content": [
              {"type": "paragraph", "content": [{"type": "text", "text": "q"}]}
          ]},
          {"type": "codeBlock", "attrs": {"language": "py"}, "content": [{"type": "text", "text": "x=1"}]},
          {"type": "horizontalRule"},
          {"type": "cardEmbed", "attrs": {"id": 1}},
          {"type": "flexContainer", "attrs": {"columnWidths": [50, 50]}, "content": [
              {"type": "cardEmbed", "attrs": {"id": 1}},
              {"type": "cardEmbed", "attrs": {"id": 2}},
          ]},
      ]
      issues = metabase_validate_document_payload(_base_doc(content))
      assert issues == [], issues


id: extraction_pipeline_py_pipelines_t0
function_id: extraction_pipeline_py_pipelines
name: test_documento_con_entidades_y_relaciones
file_path: python/functions/pipelines/extraction_pipeline_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_documento_con_entidades_y_relaciones():
      """documento con entidades y relaciones retorna ExtractionResult completo"""
      text = (
          "John Smith, a US national, operates the domain evil-corp.com. "
          "He was identified as the main administrator of the infrastructure."
      )
      with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
          f.write(text)
          tmp_path = f.name

      try:
          result = extraction_pipeline(
              file_path=tmp_path,
              entity_presets=ENTITY_PRESETS,
              relation_types=RELATION_TYPES,
              llm_chat_json=_llm_with_entities,
              chunk_size=500,
              chunk_overlap=50,
              confidence_threshold=0.5,
              dedup_threshold=0.85,
          )
          assert result is not None
          assert len(result.entities) >= 1
          assert result.stats.total_chunks >= 1
          assert result.stats.total_chars > 0
      finally:
          os.unlink(tmp_path)


id: extraction_pipeline_py_pipelines_t1
function_id: extraction_pipeline_py_pipelines
name: test_documento_vacio
file_path: python/functions/pipelines/extraction_pipeline_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_documento_vacio():
      """documento vacio retorna ExtractionResult con listas vacias"""
      with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
          f.write("")
          tmp_path = f.name

      try:
          result = extraction_pipeline(
              file_path=tmp_path,
              entity_presets=ENTITY_PRESETS,
              relation_types=RELATION_TYPES,
              llm_chat_json=_llm_empty,
          )
          assert result is not None
          assert result.entities == []
          assert result.relations == []
          assert result.stats.total_chunks == 0
      finally:
          os.unlink(tmp_path)


id: extraction_pipeline_py_pipelines_t2
function_id: extraction_pipeline_py_pipelines
name: test_documento_sin_entidades_detectables
file_path: python/functions/pipelines/extraction_pipeline_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_documento_sin_entidades_detectables():
      """documento sin entidades detectables retorna listas vacias"""
      text = "The weather is nice today. The sun shines brightly over the mountains."
      with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
          f.write(text)
          tmp_path = f.name

      try:
          result = extraction_pipeline(
              file_path=tmp_path,
              entity_presets=ENTITY_PRESETS,
              relation_types=RELATION_TYPES,
              llm_chat_json=_llm_empty,
              confidence_threshold=0.5,
          )
          assert result is not None
          assert result.entities == []
          assert result.relations == []
          assert result.stats.raw_entities_count == 0
      finally:
          os.unlink(tmp_path)


id: extraction_pipeline_py_pipelines_t3
function_id: extraction_pipeline_py_pipelines
name: test_archivo_no_encontrado_lanza_filenotfounderror
file_path: python/functions/pipelines/extraction_pipeline_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_archivo_no_encontrado_lanza_filenotfounderror():
      """archivo no encontrado lanza FileNotFoundError"""
      import pytest
      with pytest.raises(FileNotFoundError):
          extraction_pipeline(
              file_path="/tmp/no_existe_para_test_extraccion_pipeline.txt",
              entity_presets=ENTITY_PRESETS,
              relation_types=RELATION_TYPES,
              llm_chat_json=_llm_empty,
          )


id: extraction_pipeline_py_pipelines_t4
function_id: extraction_pipeline_py_pipelines
name: test_entity_presets_vacio_lanza_valueerror
file_path: python/functions/pipelines/extraction_pipeline_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_entity_presets_vacio_lanza_valueerror():
      """entity presets vacio lanza ValueError"""
      import pytest
      with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
          f.write("some text")
          tmp_path = f.name

      try:
          with pytest.raises(ValueError):
              extraction_pipeline(
                  file_path=tmp_path,
                  entity_presets=[],
                  relation_types=RELATION_TYPES,
                  llm_chat_json=_llm_empty,
              )
      finally:
          os.unlink(tmp_path)


id: extraction_pipeline_py_pipelines_t5
function_id: extraction_pipeline_py_pipelines
name: test_progress_callback_se_invoca
file_path: python/functions/pipelines/extraction_pipeline_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_progress_callback_se_invoca():
      """progress callback se invoca durante la ejecucion"""
      calls: list[tuple[str, float]] = []

      def _on_progress(msg: str, pct: float) -> None:
          calls.append((msg, pct))

      text = "John Smith operates evil-corp.com."
      with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
          f.write(text)
          tmp_path = f.name

      try:
          extraction_pipeline(
              file_path=tmp_path,
              entity_presets=ENTITY_PRESETS,
              relation_types=RELATION_TYPES,
              llm_chat_json=_llm_with_entities,
              on_progress=_on_progress,
          )
          assert len(calls) > 0
          messages = [c[0] for c in calls]
          assert any("Extracting" in m or "Done" in m or "Dedup" in m for m in messages)
      finally:
          os.unlink(tmp_path)


id: extraction_pipeline_py_pipelines_t6
function_id: extraction_pipeline_py_pipelines
name: test_stats_se_rellenan_correctamente
file_path: python/functions/pipelines/extraction_pipeline_test.py
lang: py
created_at: 2026-04-21T19:19:49Z
updated_at: 2026-04-21T19:19:49Z
code:
  def test_stats_se_rellenan_correctamente():
      """stats se rellenan correctamente con conteos y tiempo"""
      text = "John Smith, a US national, operates the domain evil-corp.com."
      with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False, encoding="utf-8") as f:
          f.write(text)
          tmp_path = f.name

      try:
          result = extraction_pipeline(
              file_path=tmp_path,
              entity_presets=ENTITY_PRESETS,
              relation_types=RELATION_TYPES,
              llm_chat_json=_llm_with_entities,
          )
          assert result.stats.total_chars > 0
          assert result.stats.total_chunks >= 1
          assert result.stats.processing_time_seconds >= 0.0
      finally:
          os.unlink(tmp_path)


# projects

id: app_turismo
name: app_turismo
tags: ["turismo","spain","app","datascience","mobile"]
dir_path: projects/app_turismo
content_hash: 7865e4287a5485a823f8c627006753ed99e47e8bf4ee5e1189dcdecb35060540
created_at: 2026-04-18T16:00:15Z
updated_at: 2026-04-18T16:00:15Z
description:
  App de turismo para Espana: asistente contextual, descubrimiento local y offline-first para turistas internacionales.

notes:
  Proyecto de app movil nativa (Kotlin/Swift) para turistas internacionales en Espana.
  MVP con tres pilares: asistente diario contextual (GPS+clima+hora), descubrimiento de zonas fuera del radar, y offline-first con datos pre-descargados.

  Mercado: 96.8M turistas internacionales (2025), 134.710M EUR gasto. 5 idiomas (EN/FR/DE/IT/NL) cubren el 62% del mercado.

  ### Componentes

  - **analysis/turismo_spain/** — Exploracion Jupyter con datos INE, FRONTUR, EGATUR, Turespaña
  - **vaults/turismo_spain/** — Datos crudos y procesados (symlink a /home/lucas/vaults/turismo_spain)
  - **apps/** — (pendiente) App movil y backend


id: element_agents
name: element_agents
tags: ["agents","matrix","element","bots","llm","chat","selfhosted","devops"]
dir_path: projects/element_agents
content_hash: e19b58804611d7f17baaceaa49c7f9b8a2c6892bbd8d05371736d3d1a6f0eaa1
created_at: 2026-04-18T16:00:15Z
updated_at: 2026-04-18T16:00:15Z
description:
  Suite de agentes autonomos Matrix (Element) con infraestructura de chat autohosteada. Agents & Robots + Element Matrix Chat.

notes:
  Proyecto que agrupa la plataforma de bots autonomos Matrix y la infraestructura de chat Element autohosteada.

  ### Componentes

  - **apps/agents_and_robots/** — Plataforma Go de bots autonomos Matrix (launcher + agentctl + dashboard + register). Arquitectura pure core / impure shell. Agentes con LLM, tools, E2EE, cron, skills.
  - **apps/element_matrix_chat/** — Stack completo Matrix autohosteado con Docker Compose (Synapse, Element Web, MAS, LiveKit, PostgreSQL, Synapse Admin).

  ### VPS: organic-machine.com

  - **SSH:** `organic-machine.com` (user: ubuntu, key: ~/.ssh/organic-machine)
  - **OS:** Ubuntu 24.04 LTS, 6 CPU, 12GB RAM, 96GB disco (40% usado)
  - **Orquestador:** Coolify (proxy en puertos 80/443/8080)
  - **agents_and_robots:** desplegado en `/home/ubuntu/CodeProyects/agents_and_robots/`, launcher corriendo como proceso (PID)
  - **element_matrix_chat:** desplegado en `/home/ubuntu/CodeProyects/element_matrix_chat/`, 10 containers Docker activos (5 semanas uptime)

  ### Puertos en uso

  | Servicio | Puerto |
  |----------|--------|
  | Coolify proxy | 80, 443, 8080 |
  | Coolify app | 8000 |
  | Coolify realtime | 6001-6002 |
  | Element Web | 8081 |
  | Synapse Admin | 8082 |
  | MAS (auth) | 8083 |
  | Synapse (Matrix) | 8008 (internal) |
  | LiveKit | 7880-7882, 50000-50200/udp |
  | Gitea SSH | 22222 |
  | Kanboard | 38060 |
  | KeeWeb | 38679 |
  | PostgreSQL (Matrix) | 5432 |


id: fn_monitoring
name: fn_monitoring
tags: ["monitoring","api","dashboard","sqlite","visualization"]
dir_path: projects/fn_monitoring
content_hash: 124049f04d0c7e4f6cce62983c5ffdbf7394bcf6740e573b2a256f4986eebe2c
created_at: 2026-04-15T00:01:11Z
updated_at: 2026-04-15T00:01:11Z
description:
  Monitoreo y visualizacion del estado del fn_registry. API HTTP read-only sobre las bases de datos SQLite y dashboard ImGui que consume la API.

documentation:
  ## Apps

  | App | Descripcion |
  |-----|-------------|
  | sqlite_api | API REST HTTP read-only para registry.db y operations.db (Go, net/http, puerto 8484) |
  | registry_dashboard | Dashboard ImGui con KPIs, charts y tablas del registry (C++, consume sqlite_api) |

  ## Arquitectura

  ```
  sqlite_api (Go, :8484)
    └── registry.db, apps/*/operations.db (read-only)
         ↑
  registry_dashboard (C++/ImGui)
    └── HTTP GET/POST → sqlite_api
    └── Fallback: SQLite directo si API no disponible
  ```


