docs: def read_local_xlsx_catalog(xlsx_path, logger=None):
      """Genera un diccionario de metadatos de catálogo a partir de un XLSX bien
      formado.
  
      Args:
          xlsx_path (str): Path a un archivo XLSX "template" para describir la
              metadata de un catálogo.
  
      Returns:
          dict: Diccionario con los metadatos de un catálogo.
      """
      logger = logger or pydj_logger
  
      wb = pyxl.load_workbook(open(xlsx_path, 'rb'), data_only=True,
                              read_only=True)
  
      # Toma las hojas del modelo, resistente a mayúsuculas/minúsculas
      ws_catalog = helpers.get_ws_case_insensitive(wb, "catalog")
      ws_dataset = helpers.get_ws_case_insensitive(wb, "dataset")
      ws_distribution = helpers.get_ws_case_insensitive(wb, "distribution")
      ws_theme = helpers.get_ws_case_insensitive(wb, "theme")
      ws_field = helpers.get_ws_case_insensitive(wb, "field")
  
      catalogs = helpers.sheet_to_table(ws_catalog)
      # Debe haber exactamente un catálogo en la hoja 'Catalog'
      assert (len(catalogs) != 0), "No hay ningun catálogo en la hoja 'Catalog'"
      assert (len(catalogs) < 2), "Hay mas de un catálogo en la hoja 'Catalog'"
      # Genero el catálogo base
      catalog = catalogs[0]
  
      # Agrego themes y datasets al catálogo
      catalog["catalog_dataset"] = helpers.sheet_to_table(ws_dataset)
  
      # Me aseguro que los identificadores de dataset se guarden como cadenas
      for dataset in catalog["catalog_dataset"]:
          dataset["dataset_identifier"] = text_type(
              dataset["dataset_identifier"])
  
      catalog["catalog_themeTaxonomy"] = (
          helpers.sheet_to_table(ws_theme))
  
      # Agrego lista de distribuciones vacía a cada dataset
      for dataset in catalog["catalog_dataset"]:
          dataset["dataset_distribution"] = []
  
      # Ubico cada distribución en su dataset
      distributions = helpers.sheet_to_table(ws_distribution)
      for distribution in distributions:
          # Me aseguro que los identificadores se guarden como cadenas
          distribution["dataset_identifier"] = text_type(
              distribution["dataset_identifier"])
          distribution["distribution_identifier"] = text_type(
              distribution["distribution_identifier"])
  
          dataset_index = _get_dataset_index(
              catalog, distribution["dataset_identifier"],
              distribution["dataset_title"], logger)
          if dataset_index is None:
              logger.error("""La distribucion con ID '{}' y titulo '{}' no se
  pudo asignar a un dataset, y no figurara en el data.json de salida.""".format(
                  distribution["distribution_identifier"],
                  distribution["distribution_title"]))
          else:
              dataset = catalog["catalog_dataset"][dataset_index]
              dataset["dataset_distribution"].append(distribution)
  
      # Ubico cada campo en su distribución
      fields = helpers.sheet_to_table(ws_field)
      for idx, field in enumerate(fields):
          # Me aseguro que los identificadores se guarden como cadenas
          field["dataset_identifier"] = text_type(field["dataset_identifier"])
          field["distribution_identifier"] = text_type(
              field["distribution_identifier"])
  
          dataset_index, distribution_index = _get_distribution_indexes(
              catalog, field["dataset_identifier"], field["dataset_title"],
              field["distribution_identifier"], field["distribution_title"],
              logger)
  
          if dataset_index is None:
              logger.error(
                  """No se encontro el dataset '{}' especificado para el campo
                  '{}' (fila #{} de la hoja "Field"). Este campo no figurara en
                  el data.json de salida.""".format(
                      unidecode(field["dataset_title"]),
                      unidecode(field["field_title"]), idx + 2))
  
          elif distribution_index is None:
              logger.error(
                  """No se encontro la distribucion '{}' especificada para el
                  campo'{}' (fila #{} de la hoja "Field"). Este campo no figurara
                  en el data.json de salida.""".format(
                      unidecode(field["distribution_title"]),
                      unidecode(field["field_title"]), idx + 2))
  
          else:
              dataset = catalog["catalog_dataset"][dataset_index]
              distribution = dataset["dataset_distribution"][distribution_index]
  
              if "distribution_field" in distribution:
                  distribution["distribution_field"].append(field)
              else:
                  distribution["distribution_field"] = [field]
  
      # Transformo campos de texto separado por comas en listas
      if "catalog_language" in catalog:
          catalog["catalog_language"] = helpers.string_to_list(
              catalog["catalog_language"], filter_empty=True)
  
      for dataset in catalog["catalog_dataset"]:
          array_fields = ["dataset_superTheme", "dataset_theme", "dataset_tags",
                          "dataset_keyword", "dataset_language"]
          for field in array_fields:
              if field in dataset:
                  dataset[field] = helpers.string_to_list(
                      dataset[field], filter_empty=True)
  
      # Elimino los prefijos de los campos a nivel catálogo
      _remove_prefixes_and_unprefixed_fields(catalog, 'catalog_')
  
      # Elimino los prefijos de los campos a nivel tema
      for theme in catalog["themeTaxonomy"]:
          _remove_prefixes_and_unprefixed_fields(theme, 'theme_')
  
      # Elimino los prefijos de los campos a nivel dataset
      for dataset in catalog["dataset"]:
          _remove_prefixes_and_unprefixed_fields(dataset, 'dataset_')
  
      # Elimino los campos auxiliares y los prefijos de los campos a nivel
      # distribución
      for dataset in catalog["dataset"]:
          for distribution in dataset["distribution"]:
              _remove_prefixes_and_unprefixed_fields(
                  distribution, 'distribution_')
  
      # Elimino campos auxiliares y los prefijos de los campos a nivel "campo"
      for dataset in catalog["dataset"]:
          for distribution in dataset["distribution"]:
              if "field" in distribution:
                  for field in distribution["field"]:
                      _remove_prefixes_and_unprefixed_fields(field, "field_")
  
      # Agrupo las claves de "publisher" y "contactPoint" en sendos diccionarios
      catalog = _make_publisher(catalog)
      for dataset in catalog["dataset"]:
          dataset = _make_publisher(dataset)
          dataset = _make_contact_point(dataset)
  
      return catalog