diff --git a/pydatajson/ckan_utils.py b/pydatajson/ckan_utils.py
index f71570d..9724f44 100644
--- a/pydatajson/ckan_utils.py
+++ b/pydatajson/ckan_utils.py
@@ -1,6 +1,8 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 import json
+import re
+import sys
 from datetime import time
 from dateutil import parser, tz
 from .helpers import title_to_name
@@ -14,7 +16,7 @@ def append_attribute_to_extra(package, dataset, attribute, serialize=False):
         package['extras'].append({'key': attribute, 'value': value})
 
 
-def map_dataset_to_package(dataset, owner_org, theme_taxonomy, catalog_id=None,
+def map_dataset_to_package(catalog, dataset, owner_org, catalog_id=None,
                            demote_superThemes=True, demote_themes=True):
     package = dict()
     package['extras'] = []
@@ -66,7 +68,8 @@ def map_dataset_to_package(dataset, owner_org, theme_taxonomy, catalog_id=None,
     if themes and demote_themes:
         package['tags'] = package.get('tags', [])
         for theme in themes:
-            label = next(x['label'] for x in theme_taxonomy if x['id'] == theme)
+            label = catalog.get_theme(identifier=theme)['label']
+            label = re.sub(r'[^\wá-úÁ-ÚñÑ .-]+', '', label, flags=re.UNICODE)
             package['tags'].append({'name': label})
     else:
         package['groups'] = package.get('groups', []) + [{'name': title_to_name(theme, decode=False)}
diff --git a/pydatajson/federation.py b/pydatajson/federation.py
index 2c31623..9573040 100644
--- a/pydatajson/federation.py
+++ b/pydatajson/federation.py
@@ -29,9 +29,8 @@ def push_dataset_to_ckan(catalog, owner_org, dataset_origin_identifier, portal_u
     """
     dataset = catalog.get_dataset(dataset_origin_identifier)
     ckan_portal = RemoteCKAN(portal_url, apikey=apikey)
-    theme_taxonomy = catalog.themes
 
-    package = map_dataset_to_package(dataset, owner_org, theme_taxonomy, catalog_id,
+    package = map_dataset_to_package(catalog, dataset, owner_org, catalog_id,
                                      demote_superThemes, demote_themes)
 
     # Get license id
diff --git a/pydatajson/search.py b/pydatajson/search.py
index 0ab6391..1c3d25a 100644
--- a/pydatajson/search.py
+++ b/pydatajson/search.py
@@ -280,7 +280,7 @@ def get_theme(catalog, identifier=None, label=None):
 
     # filtra por id (preferentemente) o label
     if identifier:
-        filtered_themes = [theme for theme in themes if theme["id"] == identifier]
+        filtered_themes = [theme for theme in themes if theme["id"].lower() == identifier.lower()]
         if len(filtered_themes) > 1:
             raise ThemeIdRepeated([x["id"] for x in filtered_themes])
 

Robustecer el manejo de themes por parte de push_dataset_to_ckan()

Este es un issue que hace referencia al 24 de monitoreo apertura:
https://github.com/datosgobar/monitoreo-apertura/issues/24
Lo abro acá porque me parece que es más apropiado discutirlo en este repo.

--------------------------------------------------------------------------------------------------
**Contexto**

Se intentó federar el dataset de series de tiempo a la instancia andino.datos.gob.ar, de la siguiente manera:
```
catalog_modernizacion = DataJson("http://infra.datos.gob.ar/catalog/modernizacion/data.json")

catalog_modernizacion.push_dataset_to_ckan(
    "modernizacion", "ministerio-de-modernizacion", "7",
    portal_url, apikey
)
```
y dio la rta:
```
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-8-b4c2dae3c9ec> in <module>()
      1 catalog_modernizacion.push_dataset_to_ckan(
      2     "modernizacion", "ministerio-de-modernizacion", "7",
----> 3     portal_url, apikey
      4 )

/Users/abenassi/github/pydatajson/pydatajson/federation.pyc in push_dataset_to_ckan(catalog, catalog_id, owner_org, dataset_origin_identifier, portal_url, apikey)
     49         theme_taxonomy = catalog.themes
     50         for theme in themes:
---> 51             label = next(x['label'] for x in theme_taxonomy if x['id'] == theme)
     52             package['tags'].append({'name': label})
     53 

StopIteration: 
```

**Propuesta**

Investigar qué hizo que se frenara la operación. Puede ser que el problema sea que se requiere crear "temas" que la instancia destino no tiene. Si es así, lo mejor sería incorporar la creación automática de temas que no existan dentro de la operación de federación.
diff --git a/tests/samples/full_data.json b/tests/samples/full_data.json
index b349578..e80673c 100644
--- a/tests/samples/full_data.json
+++ b/tests/samples/full_data.json
@@ -193,7 +193,7 @@
             "id": "convocatorias"
         },
         {
-            "label": "Compras",
+            "label": "Adquisición",
             "description": "Datasets sobre compras realizadas.",
             "id": "compras"
         },
@@ -213,7 +213,7 @@
             "id": "normativa"
         },
         {
-            "label": "Proveedores",
+            "label": "Proveeduría",
             "description": "Datasets sobre proveedores del Estado.",
             "id": "proveedores"
         }
diff --git a/tests/test_ckan_utils.py b/tests/test_ckan_utils.py
index 3f20984..f90406e 100644
--- a/tests/test_ckan_utils.py
+++ b/tests/test_ckan_utils.py
@@ -1,6 +1,10 @@
+# -*- coding: utf-8 -*-
+
 import unittest
 import os
 import json
+import re
+import sys
 from dateutil import parser, tz
 from .context import pydatajson
 from pydatajson.ckan_utils import map_dataset_to_package, map_distributions_to_resources, convert_iso_string_to_utc
@@ -23,15 +27,15 @@ class DatasetConversionTestCase(unittest.TestCase):
         cls.distributions = cls.dataset['distribution']
 
     def test_catalog_id_is_prepended_to_dataset_id_if_passed(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes, catalog_id=self.catalog_id)
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner', catalog_id=self.catalog_id)
         self.assertEqual(self.catalog_id + '_' + self.dataset_id, package['id'])
 
     def test_dataset_id_is_preserved_if_catlog_id_is_not_passed(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes)
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner')
         self.assertEqual(self.dataset_id, package['id'])
 
     def test_replicated_plain_attributes_are_corrext(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes, catalog_id=self.catalog_id)
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner', catalog_id=self.catalog_id)
         plain_replicated_attributes = [('title', 'title'),
                                        ('notes', 'description'),
                                        ('url', 'landingPage')]
@@ -40,7 +44,7 @@ class DatasetConversionTestCase(unittest.TestCase):
         self.assertEqual('owner', package['owner_org'])
 
     def test_dataset_nested_replicated_attributes_stay_the_same(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes, catalog_id=self.catalog_id)
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner', catalog_id=self.catalog_id)
         contact_point_nested = [('maintainer', 'fn'),
                                 ('maintainer_email', 'hasEmail')]
         for fst, snd in contact_point_nested:
@@ -51,7 +55,7 @@ class DatasetConversionTestCase(unittest.TestCase):
             self.assertEqual(self.dataset.get('publisher').get(snd), package.get(fst))
 
     def test_dataset_array_attributes_are_correct(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes, catalog_id=self.catalog_id)
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner', catalog_id=self.catalog_id)
         groups = [group['name'] for group in package.get('groups', [])]
         super_themes = [title_to_name(s_theme.lower()) for s_theme in self.dataset.get('superTheme')]
         try:
@@ -65,7 +69,8 @@ class DatasetConversionTestCase(unittest.TestCase):
         themes = self.dataset.get('theme', [])
         theme_labels = []
         for theme in themes:
-            label = next(x['label'] for x in self.catalog.themes if x['id'] == theme)
+            label = self.catalog.get_theme(identifier=theme)['label']
+            label = re.sub(r'[^\w .-]+', '', label, flags=re.UNICODE)
             theme_labels.append(label)
 
         try:
@@ -74,7 +79,7 @@ class DatasetConversionTestCase(unittest.TestCase):
             self.assertCountEqual(keywords + theme_labels, tags)
 
     def test_themes_are_preserved_if_not_demoted(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes,
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner',
                                          catalog_id=self.catalog_id, demote_themes=False)
         groups = [group['name'] for group in package.get('groups', [])]
         super_themes = [title_to_name(s_theme.lower()) for s_theme in self.dataset.get('superTheme')]
@@ -92,7 +97,7 @@ class DatasetConversionTestCase(unittest.TestCase):
             self.assertCountEqual(keywords, tags)
 
     def test_superThemes_dont_impact_groups_if_not_demoted(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes,
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner',
                                          catalog_id=self.catalog_id, demote_superThemes=False)
         groups = [group['name'] for group in package.get('groups', [])]
         tags = [tag['name'] for tag in package['tags']]
@@ -100,7 +105,8 @@ class DatasetConversionTestCase(unittest.TestCase):
         themes = self.dataset.get('theme', [])
         theme_labels = []
         for theme in themes:
-            label = next(x['label'] for x in self.catalog.themes if x['id'] == theme)
+            label = self.catalog.get_theme(identifier=theme)['label']
+            label = re.sub(r'[^\wá-úÁ-ÚñÑ .-]+', '', label, flags=re.UNICODE)
             theme_labels.append(label)
         try:
             self.assertItemsEqual([], groups)
@@ -112,7 +118,7 @@ class DatasetConversionTestCase(unittest.TestCase):
             self.assertCountEqual(keywords + theme_labels, tags)
 
     def test_preserve_themes_and_superThemes(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes,
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner',
                                          self.catalog_id, False, False)
         groups = [group['name'] for group in package.get('groups', [])]
         tags = [tag['name'] for tag in package['tags']]
@@ -128,7 +134,7 @@ class DatasetConversionTestCase(unittest.TestCase):
             self.assertCountEqual(keywords, tags)
 
     def test_dataset_extra_attributes_are_correct(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes, catalog_id=self.catalog_id)
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner', catalog_id=self.catalog_id)
 #       extras are included in dataset
         if package['extras']:
             for extra in package['extras']:
@@ -144,7 +150,7 @@ class DatasetConversionTestCase(unittest.TestCase):
                     self.assertEqual(dataset_value, extra_value)
 
     def test_dataset_extra_attributes_are_complete(self):
-        package = map_dataset_to_package(self.dataset, 'owner', self.catalog.themes, catalog_id=self.catalog_id)
+        package = map_dataset_to_package(self.catalog, self.dataset, 'owner', catalog_id=self.catalog_id)
 #       dataset attributes are included in extras
         extra_attrs = ['issued', 'modified', 'accrualPeriodicity', 'temporal', 'language', 'spatial', 'superTheme']
         for key in extra_attrs:
diff --git a/tests/test_federation.py b/tests/test_federation.py
index e4a1d2e..e6804b9 100644
--- a/tests/test_federation.py
+++ b/tests/test_federation.py
@@ -1,6 +1,9 @@
+# -*- coding: utf-8 -*-
+
 import unittest
 import os
 import re
+import sys
 try:
     from mock import patch, MagicMock
 except ImportError:
@@ -83,10 +86,10 @@ class PushDatasetTestCase(unittest.TestCase):
     @patch('pydatajson.federation.RemoteCKAN', autospec=True)
     def test_tags_are_passed_correctly(self, mock_portal):
         themes = self.dataset['theme']
-        theme_taxonomy = self.catalog.themes
         keywords = [kw for kw in self.dataset['keyword']]
         for theme in themes:
-            label = next(x['label'] for x in theme_taxonomy if x['id'] == theme)
+            label = self.catalog.get_theme(identifier=theme)['label']
+            label = re.sub(r'[^\w .-]+', '', label, flags=re.UNICODE)
             keywords.append(label)
 
         def mock_call_action(action, data_dict=None):

pip install -e .[dev]
parse_log_pytest
requirements.txt
nose pytest
mkdir tests/temp
3.6
requirements.txt requirements_dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.13
argh==0.27.2
asn1crypto==1.5.1
attrs==22.2.0
Babel==2.11.0
bumpversion==0.5.3
certifi==2021.5.30
cffi==1.15.1
chardet==3.0.4
ckanapi==4.0
CommonMark==0.5.4
coverage==4.1
cryptography==2.1.4
distlib==0.3.9
docopt==0.6.2
docutils==0.18.1
et-xmlfile==1.1.0
filelock==3.4.1
flake8==2.6.0
idna==2.6
imagesize==1.4.1
importlib-metadata==4.8.3
importlib-resources==5.4.0
iniconfig==1.1.1
isodate==0.6.0
jdcal==1.4.1
Jinja2==3.0.3
jsonschema==2.6.0
MarkupSafe==2.0.1
mccabe==0.5.3
multidict==5.2.0
nose==1.3.7
openpyxl==2.4.11
packaging==21.3
pathtools==0.1.2
pkginfo==1.10.0
platformdirs==2.4.0
pluggy==0.13.1
pockets==0.9.1
py==1.11.0
pycodestyle==2.0.0
pycparser==2.21
-e git+https://github.com/datosgobar/pydatajson.git@ef99387305d7cd46831c715c7a443f4b056baeb4#egg=pydatajson
pyflakes==1.2.3
Pygments==2.14.0
pyparsing==3.1.4
pytest==7.0.1
python-dateutil==2.6.1
pytz==2025.2
PyYAML==3.11
recommonmark==0.4.0
requests==2.18.4
requests-toolbelt==1.0.0
rfc3987==1.3.7
six==1.11.0
snowballstemmer==2.2.0
Sphinx==1.5.2
sphinx-rtd-theme==0.2.4
sphinxcontrib-napoleon==0.6.1
tomli==1.2.3
tox==2.9.1
tqdm==4.64.1
twine==1.9.1
typing_extensions==4.1.1
unicodecsv==0.14.1
Unidecode==0.4.21
urllib3==1.22
vcrpy==1.11.1
virtualenv==20.17.1
watchdog==0.8.3
wrapt==1.16.0
yarl==1.7.2
zipp==3.6.0

name: pydatajson
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.13
      - argh==0.27.2
      - asn1crypto==1.5.1
      - attrs==22.2.0
      - babel==2.11.0
      - bumpversion==0.5.3
      - cffi==1.15.1
      - chardet==3.0.4
      - ckanapi==4.0
      - commonmark==0.5.4
      - coverage==4.1
      - cryptography==2.1.4
      - distlib==0.3.9
      - docopt==0.6.2
      - docutils==0.18.1
      - et-xmlfile==1.1.0
      - filelock==3.4.1
      - flake8==2.6.0
      - idna==2.6
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - importlib-resources==5.4.0
      - iniconfig==1.1.1
      - isodate==0.6.0
      - jdcal==1.4.1
      - jinja2==3.0.3
      - jsonschema==2.6.0
      - markupsafe==2.0.1
      - mccabe==0.5.3
      - multidict==5.2.0
      - nose==1.3.7
      - openpyxl==2.4.11
      - packaging==21.3
      - pathtools==0.1.2
      - pkginfo==1.10.0
      - platformdirs==2.4.0
      - pluggy==0.13.1
      - pockets==0.9.1
      - py==1.11.0
      - pycodestyle==2.0.0
      - pycparser==2.21
      - pyflakes==1.2.3
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - python-dateutil==2.6.1
      - pytz==2025.2
      - pyyaml==3.11
      - recommonmark==0.4.0
      - requests==2.18.4
      - requests-toolbelt==1.0.0
      - rfc3987==1.3.7
      - six==1.11.0
      - snowballstemmer==2.2.0
      - sphinx==1.5.2
      - sphinx-rtd-theme==0.2.4
      - sphinxcontrib-napoleon==0.6.1
      - tomli==1.2.3
      - tox==2.9.1
      - tqdm==4.64.1
      - twine==1.9.1
      - typing-extensions==4.1.1
      - unicodecsv==0.14.1
      - unidecode==0.04.21
      - urllib3==1.22
      - vcrpy==1.11.1
      - virtualenv==20.17.1
      - watchdog==0.8.3
      - wrapt==1.16.0
      - yarl==1.7.2
      - zipp==3.6.0
prefix: /opt/conda/envs/pydatajson

tests/test_ckan_utils.py::DatasetConversionTestCase::test_catalog_id_is_prepended_to_dataset_id_if_passed tests/test_ckan_utils.py::DatasetConversionTestCase::test_dataset_array_attributes_are_correct tests/test_ckan_utils.py::DatasetConversionTestCase::test_dataset_extra_attributes_are_complete tests/test_ckan_utils.py::DatasetConversionTestCase::test_dataset_extra_attributes_are_correct tests/test_ckan_utils.py::DatasetConversionTestCase::test_dataset_id_is_preserved_if_catlog_id_is_not_passed tests/test_ckan_utils.py::DatasetConversionTestCase::test_dataset_nested_replicated_attributes_stay_the_same tests/test_ckan_utils.py::DatasetConversionTestCase::test_preserve_themes_and_superThemes tests/test_ckan_utils.py::DatasetConversionTestCase::test_replicated_plain_attributes_are_corrext tests/test_ckan_utils.py::DatasetConversionTestCase::test_superThemes_dont_impact_groups_if_not_demoted tests/test_ckan_utils.py::DatasetConversionTestCase::test_themes_are_preserved_if_not_demoted
tests/test_ckan_utils.py::DatasetConversionTestCase::test_catalog_id_is_prefixed_in_resource_id_if_passed tests/test_ckan_utils.py::DatasetConversionTestCase::test_resource_id_is_preserved_if_catalog_id_is_not_passed tests/test_ckan_utils.py::DatasetConversionTestCase::test_resources_extra_attributes_are_created_correctly tests/test_ckan_utils.py::DatasetConversionTestCase::test_resources_replicated_attributes_stay_the_same tests/test_ckan_utils.py::DatasetConversionTestCase::test_resources_transformed_attributes_are_correct tests/test_ckan_utils.py::DatetimeConversionTests::test_dates_change_correctly tests/test_ckan_utils.py::DatetimeConversionTests::test_dates_stay_the_same tests/test_ckan_utils.py::DatetimeConversionTests::test_datetimes_without_microseconds_are_handled_correctly tests/test_ckan_utils.py::DatetimeConversionTests::test_datetimes_without_seconds_are_handled_correctly tests/test_ckan_utils.py::DatetimeConversionTests::test_datetimes_without_timezones_stay_the_same tests/test_ckan_utils.py::DatetimeConversionTests::test_timezones_are_handled_correctly tests/test_federation.py::PushDatasetTestCase::test_dataset_id_is_preserved_if_catalog_id_is_not_passed tests/test_federation.py::PushDatasetTestCase::test_dataset_without_license_sets_notspecified tests/test_federation.py::PushDatasetTestCase::test_id_is_created_correctly tests/test_federation.py::PushDatasetTestCase::test_id_is_updated_correctly tests/test_federation.py::PushDatasetTestCase::test_licenses_are_interpreted_correctly tests/test_federation.py::PushDatasetTestCase::test_tags_are_passed_correctly tests/test_federation.py::RemoveDatasetTestCase::test_empty_search_doesnt_call_purge tests/test_federation.py::RemoveDatasetTestCase::test_filter_in_datasets tests/test_federation.py::RemoveDatasetTestCase::test_filter_in_out_datasets tests/test_federation.py::RemoveDatasetTestCase::test_query_one_dataset tests/test_federation.py::RemoveDatasetTestCase::test_query_over_500_datasets tests/test_federation.py::RemoveDatasetTestCase::test_remove_through_filters_and_organization
pydatajson/search.py pydatajson/ckan_utils.py pydatajson/federation.py
pydatajson/search.py pydatajson/ckan_utils.py pydatajson/federation.py