Tecnología · GUÍA PRÁCTICA
Comparte plantillas de mazos filtrados de Anki sin el historial de estudio
Exporta las búsquedas y preferencias existentes a un archivo JSON independiente y recréalas después en otra colección de Anki. Un programa auxiliar local completo comprueba el lote antes de crear nuevos mazos filtrados.

Si tienes un conjunto de mazos filtrados de Anki que quieres que otra persona reutilice, comparte sus definiciones por separado de las tarjetas. El programa auxiliar que aparece a continuación exporta a JSON sus nombres, búsquedas, límites, orden y preferencias. La persona que lo recibe puede revisar ese archivo y recrear el lote con las tarjetas de su propia colección que cumplan los requisitos, sin recibir tus registros de tarjetas ni tu registro de repasos a través del JSON.
Este es un procedimiento avanzado para Anki Desktop, verificado con el backend real de Python/Rust de Anki 26.09.3 en colecciones desechables. Importar los ajustes con apply=True construye inmediatamente nuevos mazos filtrados y mueve a ellos las tarjetas locales que coinciden. Lee primero el informe de comprobación previa, utiliza el perfil correcto y conserva una copia de seguridad actualizada antes de aplicar cambios. El programa auxiliar se incluye completo a continuación; no requiere instalar Python por separado.
1. Elige la transferencia que realmente necesitas
Un paquete de colección, .colpkg, es una opción existente adecuada cuando la otra persona quiere tu colección completa en un perfil independiente, incluidos sus datos de programación. Al importarlo, se reemplazan las tarjetas de la colección actual. No sirve para enviar únicamente las definiciones de los filtros ni para dejar fuera de la entrega tu historial de estudio acumulado.
Un paquete de mazo normal, .apkg, puede añadir tarjetas a otra colección, y Anki ofrece una opción para excluir la programación. Ese paquete es útil para las tarjetas, pero no debe considerarse un medio fiable para transferir las definiciones de los mazos filtrados. CrowdAnki también excluye expresamente los mazos filtrados de su exportación a JSON. Las herramientas que crean un filtro a partir de una etiqueta o reconstruyen filtros existentes resuelven partes útiles del procedimiento; ninguna de esas dos operaciones transfiere un lote seleccionado de tus definiciones completas existentes.
Utiliza el programa auxiliar proporcionado cuando lo que quieras reutilizar sea el mazo filtrado ya configurado. Conserva los siete campos de configuración actuales, incluidos los dos términos de búsqueda, los valores explícitos false y cero, las demoras actuales de la vista previa y los campos de demora guardados por versiones anteriores. Copiar esos campos antiguos no activa un planificador antiguo. Los ajustes preestablecidos de los mazos normales, la configuración de FSRS, los ajustes de los complementos, los archivos multimedia y el contenido de las tarjetas quedan fuera de este formato JSON.
El archivo sigue conteniendo los nombres de mazos que elegiste y el texto literal de las consultas, incluidos los ID que hayas introducido en una búsqueda. Léelo antes de compartirlo y elimina nombres o términos privados si es necesario. La ausencia de tarjetas e historial de repasos no convierte esas cadenas de texto en anónimas.
2. Guarda y carga el programa auxiliar completo en Anki Desktop
Guarda la siguiente línea exec(...) completa como un archivo de texto sin formato UTF-8 llamado anki_filtered_templates.py. Contiene todo el programa; los saltos de línea y espacios escapados conservan la sangría de Python al copiarlo desde esta página. Mantén la línea intacta y no sustituyas sus comillas rectas por comillas tipográficas. También puedes conservar el código fuente complementario en su forma normal, con sangría.
exec("\"\"\"Filtered-deck\x20configuration\x20only;\x20tested\x20with\x20Anki\x2026.9.3's\x20real\x20backend.\n\nLoad\x20this\x20file\x20in\x20Anki\x20Desktop's\x20debug\x20console,\x20then\x20use\x20mw.col:\n\x20\x20\x20\x20export_filtered_templates(mw.col,\x20'/absolute/path/templates.json')\n\x20\x20\x20\x20import_filtered_templates(mw.col,\x20'/absolute/path/templates.json')\n\x20\x20\x20\x20import_filtered_templates(mw.col,\x20'/absolute/path/templates.json',\x20apply=True)\nUse\x20names=['Exact\x20name',\x20...]\x20to\x20export\x20a\x20selection.\x20None\x20exports\x20all\x20filtered\ndecks\x20in\x20name\x20order.\x20prefix\x20changes\x20destination\x20deck\x20names,\x20never\x20searches.\napply=True\x20immediately\x20builds\x20using\x20destination-local\x20cards,\x20in\x20file\x20order.\nEarlier\x20builds\x20take\x20precedence\x20for\x20overlapping\x20cards.\x20This\x20is\x20not\x20one\x20atomic\nbatch\x20or\x20one\x20promised\x20Undo.\x20Existing\x20filtered\x20decks\x20are\x20never\x20emptied\x20here.\nNo\x20source\x20deck-ID\x20metadata,\x20card/note\x20records\x20or\x20review\x20logs\x20are\x20exported.\nSearch\x20text\x20is\x20preserved\x20and\x20may\x20contain\x20source-specific\x20IDs.\n\"\"\"\nimport\x20json\nimport\x20math\nimport\x20unicodedata\nfrom\x20pathlib\x20import\x20Path\nfrom\x20anki.collection\x20import\x20Collection\nfrom\x20anki.decks\x20import\x20DeckId,\x20FilteredDeckConfig\nfrom\x20google.protobuf.json_format\x20import\x20ParseDict\n\nSCHEMA\x20=\x20'anki-filtered-templates'\nVERSION\x20=\x201\nCONFIG_KEYS\x20=\x20{'reschedule',\x20'search_terms',\x20'delays',\x20'preview_delay',\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'preview_again_secs',\x20'preview_hard_secs',\x20'preview_good_secs'}\nDELAY_KEYS\x20=\x20('preview_delay',\x20'preview_again_secs',\x20'preview_hard_secs',\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'preview_good_secs')\nORDER\x20=\x20FilteredDeckConfig.SearchTerm.Order\n\n\nclass\x20PartialImportError(RuntimeError):\n\x20\x20\x20\x20\"\"\"Inspect\x20.partial_report;\x20successful\x20earlier\x20creations\x20remain\x20present.\"\"\"\n\x20\x20\x20\x20def\x20__init__(self,\x20message,\x20report):\n\x20\x20\x20\x20\x20\x20\x20\x20self.partial_report\x20=\x20report\n\x20\x20\x20\x20\x20\x20\x20\x20ids\x20=\x20[row['id']\x20for\x20row\x20in\x20report['created']]\n\x20\x20\x20\x20\x20\x20\x20\x20super().__init__(f'{message}.\x20New\x20filtered\x20IDs\x20so\x20far:\x20{ids}.\x20'\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'Inspect\x20exception.partial_report;\x20no\x20rollback\x20was\x20attempted.')\n\n\ndef\x20_object(value,\x20allowed,\x20required,\x20label):\n\x20\x20\x20\x20if\x20type(value)\x20is\x20not\x20dict:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{label}:\x20expected\x20an\x20object')\n\x20\x20\x20\x20unknown,\x20missing\x20=\x20set(value)\x20-\x20allowed,\x20required\x20-\x20set(value)\n\x20\x20\x20\x20if\x20unknown\x20or\x20missing:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{label}:\x20unknown\x20keys\x20{sorted(unknown)},\x20missing\x20keys\x20{sorted(missing)}')\n\n\ndef\x20_name(value):\n\x20\x20\x20\x20if\x20not\x20isinstance(value,\x20str)\x20or\x20not\x20value:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError('Deck\x20name\x20must\x20be\x20a\x20nonempty\x20string')\n\x20\x20\x20\x20if\x20(any(ord(c)\x20\u003c\x2032\x20or\x20ord(c)\x20==\x20127\x20for\x20c\x20in\x20value)\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20or\x20any(not\x20p\x20or\x20p\x20!=\x20p.strip()\x20for\x20p\x20in\x20value.split('::'))\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20or\x20unicodedata.normalize('NFC',\x20value)\x20!=\x20value):\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'Name\x20needs\x20nonempty,\x20unpadded,\x20NFC-normalized\x20components:\x20{value!r}')\n\x20\x20\x20\x20return\x20value\n\n\ndef\x20_uint(value,\x20label):\n\x20\x20\x20\x20if\x20type(value)\x20is\x20not\x20int\x20or\x20not\x200\x20\u003c=\x20value\x20\u003c=\x204294967295:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{label}:\x20expected\x20an\x20integer\x20from\x200\x20to\x204294967295')\n\n\ndef\x20_config_json(config):\n\x20\x20\x20\x20#\x20Explicit\x20defaults\x20keep\x20false\x20and\x20zero\x20visible;\x20no\x20IDs\x20or\x20scheduling\x20data.\n\x20\x20\x20\x20result\x20=\x20{'reschedule':\x20config.reschedule,\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'search_terms':\x20[{'search':\x20t.search,\x20'limit':\x20t.limit,\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'order':\x20ORDER.Name(t.order)}\x20for\x20t\x20in\x20config.search_terms],\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'delays':\x20list(config.delays)}\n\x20\x20\x20\x20result.update({key:\x20getattr(config,\x20key)\x20for\x20key\x20in\x20DELAY_KEYS})\n\x20\x20\x20\x20return\x20result\n\n\ndef\x20_config(col,\x20value,\x20label):\n\x20\x20\x20\x20_object(value,\x20CONFIG_KEYS,\x20{'search_terms'},\x20label)\n\x20\x20\x20\x20if\x20'reschedule'\x20in\x20value\x20and\x20type(value['reschedule'])\x20is\x20not\x20bool:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{label}.reschedule:\x20expected\x20true\x20or\x20false')\n\x20\x20\x20\x20for\x20key\x20in\x20DELAY_KEYS:\n\x20\x20\x20\x20\x20\x20\x20\x20if\x20key\x20in\x20value:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20_uint(value[key],\x20f'{label}.{key}')\n\x20\x20\x20\x20delays\x20=\x20value.get('delays',\x20[])\n\x20\x20\x20\x20if\x20type(delays)\x20is\x20not\x20list\x20or\x20any(type(v)\x20not\x20in\x20(int,\x20float)\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20or\x20not\x20math.isfinite(v)\x20or\x20v\x20\u003c\x200\x20for\x20v\x20in\x20delays):\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{label}.delays:\x20expected\x20finite\x20nonnegative\x20numbers')\n\x20\x20\x20\x20terms\x20=\x20value['search_terms']\n\x20\x20\x20\x20if\x20type(terms)\x20is\x20not\x20list\x20or\x20not\x201\x20\u003c=\x20len(terms)\x20\u003c=\x202:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{label}:\x20requires\x20one\x20or\x20two\x20search\x20terms')\n\x20\x20\x20\x20normalized\x20=\x20[]\n\x20\x20\x20\x20for\x20i,\x20term\x20in\x20enumerate(terms):\n\x20\x20\x20\x20\x20\x20\x20\x20part\x20=\x20f'{label}.search_terms[{i}]'\n\x20\x20\x20\x20\x20\x20\x20\x20_object(term,\x20{'search',\x20'limit',\x20'order'},\x20{'search'},\x20part)\n\x20\x20\x20\x20\x20\x20\x20\x20search\x20=\x20term['search']\n\x20\x20\x20\x20\x20\x20\x20\x20if\x20not\x20isinstance(search,\x20str)\x20or\x20not\x20search.strip():\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{part}:\x20blank\x20search\x20rejected;\x20use\x20explicit\x20deck:*\x20for\x20all\x20decks')\n\x20\x20\x20\x20\x20\x20\x20\x20_uint(term.get('limit',\x200),\x20part\x20+\x20'.limit')\n\x20\x20\x20\x20\x20\x20\x20\x20order\x20=\x20term.get('order',\x200)\n\x20\x20\x20\x20\x20\x20\x20\x20try:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20type(order)\x20is\x20str:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ORDER.Value(order)\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20elif\x20type(order)\x20is\x20int:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20ORDER.Name(order)\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20else:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError('not\x20an\x20enum\x20name\x20or\x20integer')\n\x20\x20\x20\x20\x20\x20\x20\x20except\x20(ValueError,\x20KeyError)\x20as\x20error:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{part}:\x20unknown\x20order\x20{order!r}')\x20from\x20error\n\x20\x20\x20\x20\x20\x20\x20\x20try:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20normalized.append(col.build_search_string(search))\n\x20\x20\x20\x20\x20\x20\x20\x20except\x20Exception\x20as\x20error:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{part}:\x20invalid\x20search:\x20{error}')\x20from\x20error\n\x20\x20\x20\x20result\x20=\x20FilteredDeckConfig()\n\x20\x20\x20\x20result.Clear()\n\x20\x20\x20\x20try:\n\x20\x20\x20\x20\x20\x20\x20\x20ParseDict(value,\x20result,\x20ignore_unknown_fields=False)\n\x20\x20\x20\x20except\x20Exception\x20as\x20error:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'{label}:\x20invalid\x20configuration:\x20{error}')\x20from\x20error\n\x20\x20\x20\x20return\x20result,\x20normalized\n\n\ndef\x20_manifest(col,\x20value):\n\x20\x20\x20\x20_object(value,\x20{'schema',\x20'version',\x20'templates'},\x20{'schema',\x20'version',\x20'templates'},\x20'manifest')\n\x20\x20\x20\x20if\x20value['schema']\x20!=\x20SCHEMA\x20or\x20type(value['version'])\x20is\x20not\x20int\x20or\x20value['version']\x20!=\x20VERSION:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'Expected\x20schema\x20{SCHEMA!r},\x20version\x20{VERSION}')\n\x20\x20\x20\x20if\x20type(value['templates'])\x20is\x20not\x20list\x20or\x20not\x20value['templates']:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError('templates\x20must\x20be\x20a\x20nonempty\x20list')\n\x20\x20\x20\x20seen,\x20result\x20=\x20set(),\x20[]\n\x20\x20\x20\x20for\x20i,\x20item\x20in\x20enumerate(value['templates']):\n\x20\x20\x20\x20\x20\x20\x20\x20_object(item,\x20{'name',\x20'config'},\x20{'name',\x20'config'},\x20f'template\x20{i\x20+\x201}')\n\x20\x20\x20\x20\x20\x20\x20\x20name\x20=\x20_name(item['name'])\n\x20\x20\x20\x20\x20\x20\x20\x20if\x20name.casefold()\x20in\x20seen:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'Duplicate\x20template\x20name:\x20{name}')\n\x20\x20\x20\x20\x20\x20\x20\x20seen.add(name.casefold())\n\x20\x20\x20\x20\x20\x20\x20\x20config,\x20normalized\x20=\x20_config(col,\x20item['config'],\x20name)\n\x20\x20\x20\x20\x20\x20\x20\x20result.append((name,\x20config,\x20normalized))\n\x20\x20\x20\x20return\x20result\n\n\ndef\x20_pairs(pairs):\n\x20\x20\x20\x20result\x20=\x20{}\n\x20\x20\x20\x20for\x20key,\x20value\x20in\x20pairs:\n\x20\x20\x20\x20\x20\x20\x20\x20if\x20key\x20in\x20result:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'Duplicate\x20JSON\x20key:\x20{key}')\n\x20\x20\x20\x20\x20\x20\x20\x20result[key]\x20=\x20value\n\x20\x20\x20\x20return\x20result\n\n\ndef\x20_bad_constant(value):\n\x20\x20\x20\x20raise\x20ValueError(f'Nonfinite\x20JSON\x20constant:\x20{value}')\n\n\ndef\x20_counts(col):\n\x20\x20\x20\x20return\x20{'cards':\x20int(col.card_count()),\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'revlog':\x20int(col.db.scalar('select\x20count(*)\x20from\x20revlog'))}\n\n\ndef\x20export_filtered_templates(col,\x20path,\x20*,\x20names=None):\n\x20\x20\x20\x20\"\"\"Read\x20existing\x20filtered\x20configs,\x20validate\x20all,\x20then\x20exclusively\x20write\x20JSON.\n\n\x20\x20\x20\x20names=None\x20means\x20all\x20filtered\x20decks;\x20a\x20nonempty\x20list\x20gives\x20exact\x20selection\n\x20\x20\x20\x20and\x20order.\x20The\x20collection\x20is\x20not\x20written.\x20Existing\x20output\x20files\x20are\x20refused.\n\x20\x20\x20\x20Returns\x20the\x20manifest\x20that\x20was\x20written.\n\x20\x20\x20\x20\"\"\"\n\x20\x20\x20\x20available\x20=\x20{n.name:\x20n.id\x20for\x20n\x20in\x20col.decks.all_names_and_ids()\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20col.decks.get(n.id,\x20default=False)['dyn']}\n\x20\x20\x20\x20if\x20names\x20is\x20None:\n\x20\x20\x20\x20\x20\x20\x20\x20selected\x20=\x20list(available)\n\x20\x20\x20\x20elif\x20type(names)\x20is\x20list\x20and\x20names\x20and\x20all(isinstance(n,\x20str)\x20for\x20n\x20in\x20names):\n\x20\x20\x20\x20\x20\x20\x20\x20selected\x20=\x20names\n\x20\x20\x20\x20else:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError('names\x20must\x20be\x20None\x20or\x20a\x20nonempty\x20list\x20of\x20exact\x20deck\x20names')\n\x20\x20\x20\x20if\x20len(selected)\x20!=\x20len(set(selected)):\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError('Duplicate\x20names\x20in\x20selection')\n\x20\x20\x20\x20missing\x20=\x20[name\x20for\x20name\x20in\x20selected\x20if\x20name\x20not\x20in\x20available]\n\x20\x20\x20\x20if\x20missing:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'Not\x20existing\x20filtered\x20deck\x20names:\x20{missing}')\n\x20\x20\x20\x20manifest\x20=\x20{'schema':\x20SCHEMA,\x20'version':\x20VERSION,\x20'templates':\x20[]}\n\x20\x20\x20\x20for\x20name\x20in\x20selected:\n\x20\x20\x20\x20\x20\x20\x20\x20saved\x20=\x20col.sched.get_or_create_filtered_deck(DeckId(available[name]))\n\x20\x20\x20\x20\x20\x20\x20\x20manifest['templates'].append({'name':\x20saved.name,\x20'config':\x20_config_json(saved.config)})\n\x20\x20\x20\x20_manifest(col,\x20manifest)\x20\x20#\x20No\x20incomplete\x20output\x20when\x20one\x20selection\x20is\x20invalid.\n\x20\x20\x20\x20text\x20=\x20json.dumps(manifest,\x20ensure_ascii=False,\x20allow_nan=False,\x20indent=2)\x20+\x20'\\n'\n\x20\x20\x20\x20with\x20Path(path).open('x',\x20encoding='utf-8')\x20as\x20stream:\n\x20\x20\x20\x20\x20\x20\x20\x20stream.write(text)\n\x20\x20\x20\x20return\x20manifest\n\n\ndef\x20import_filtered_templates(col,\x20path,\x20*,\x20prefix='',\x20apply=False):\n\x20\x20\x20\x20\"\"\"Validate\x20the\x20whole\x20file\x20first;\x20apply=False\x20never\x20creates/builds\x20decks.\n\n\x20\x20\x20\x20apply=True\x20creates\x20NEW\x20IDs\x20in\x20manifest\x20order\x20and\x20immediately\x20builds\x20from\n\x20\x20\x20\x20local\x20cards.\x20Prefix\x20alters\x20deck\x20names\x20only.\x20Backend-normalized\x20queries\x20are\n\x20\x20\x20\x20shown\x20in\x20the\x20plan\x20and\x20checked\x20on\x20readback;\x20original\x20query\x20text\x20is\x20submitted.\n\x20\x20\x20\x20On\x20execution\x20failure,\x20PartialImportError.partial_report\x20lists\x20created\x20IDs.\n\x20\x20\x20\x20Earlier\x20successful\x20operations\x20remain;\x20no\x20automatic\x20Empty/delete\x20is\x20done.\n\x20\x20\x20\x20\"\"\"\n\x20\x20\x20\x20if\x20type(apply)\x20is\x20not\x20bool\x20or\x20not\x20isinstance(prefix,\x20str):\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError('apply\x20must\x20be\x20a\x20bool\x20and\x20prefix\x20must\x20be\x20a\x20string')\n\x20\x20\x20\x20with\x20Path(path).open(encoding='utf-8-sig')\x20as\x20stream:\n\x20\x20\x20\x20\x20\x20\x20\x20data\x20=\x20json.load(stream,\x20object_pairs_hook=_pairs,\x20parse_constant=_bad_constant)\n\x20\x20\x20\x20parsed\x20=\x20_manifest(col,\x20data)\n\x20\x20\x20\x20existing\x20=\x20{n.name.casefold():\x20n.id\x20for\x20n\x20in\x20col.decks.all_names_and_ids()}\n\x20\x20\x20\x20original_ids\x20=\x20set(existing.values())\n\x20\x20\x20\x20planned\x20=\x20[_name(prefix\x20+\x20name)\x20for\x20name,\x20_,\x20_\x20in\x20parsed]\n\x20\x20\x20\x20folded\x20=\x20{name.casefold()\x20for\x20name\x20in\x20planned}\n\x20\x20\x20\x20if\x20len(folded)\x20!=\x20len(planned):\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError('Destination\x20names\x20collide\x20after\x20adding\x20prefix')\n\x20\x20\x20\x20for\x20name\x20in\x20planned:\n\x20\x20\x20\x20\x20\x20\x20\x20if\x20col.decks.id_for_name(name)\x20is\x20not\x20None:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'Destination\x20name\x20already\x20exists:\x20{name}')\n\x20\x20\x20\x20\x20\x20\x20\x20components\x20=\x20name.split('::')\n\x20\x20\x20\x20\x20\x20\x20\x20for\x20length\x20in\x20range(1,\x20len(components)):\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20parent\x20=\x20'::'.join(components[:length])\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20pid\x20=\x20col.decks.id_for_name(parent)\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20parent.casefold()\x20in\x20folded\x20or\x20(pid\x20is\x20not\x20None\x20and\x20col.decks.get(pid)['dyn']):\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'Filtered\x20decks\x20cannot\x20be\x20parents:\x20{parent}')\n\x20\x20\x20\x20report\x20=\x20{'applied':\x20False,\x20'templates':\x20[],\x20'created':\x20[],\x20'new_parent_decks':\x20[],\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'before':\x20_counts(col),\x20'warnings':\x20[\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'Apply\x20immediately\x20builds\x20from\x20destination-local\x20cards\x20in\x20manifest\x20order.',\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'Earlier\x20filtered\x20builds\x20take\x20precedence\x20for\x20overlapping\x20cards.',\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'The\x20batch\x20is\x20not\x20atomic;\x20no\x20automatic\x20rollback\x20or\x20promised\x20single\x20Undo.',\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'Prefix\x20changes\x20filtered\x20names\x20only;\x20search\x20strings\x20keep\x20their\x20original\x20targets.'],\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'recovery':\x20[\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'Inspect\x20created\x20IDs\x20and\x20names\x20before\x20any\x20recovery\x20action.',\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'To\x20return\x20cards,\x20use\x20Empty\x20on\x20ONLY\x20those\x20newly\x20created\x20filtered\x20decks.',\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'Verify\x20each\x20is\x20empty,\x20then\x20delete\x20only\x20those\x20new\x20filtered\x20decks\x20if\x20unwanted.',\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'Do\x20not\x20delete\x20normal\x20parent\x20decks\x20that\x20contain\x20other\x20decks\x20or\x20cards.']}\n\x20\x20\x20\x20for\x20name,\x20(_,\x20config,\x20normalized)\x20in\x20zip(planned,\x20parsed):\n\x20\x20\x20\x20\x20\x20\x20\x20report['templates'].append({'name':\x20name,\x20'config':\x20_config_json(config),\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20'normalized_searches':\x20normalized})\n\x20\x20\x20\x20\x20\x20\x20\x20if\x20normalized\x20!=\x20[term.search\x20for\x20term\x20in\x20config.search_terms]:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20report['warnings'].append(f'{name}:\x20backend\x20will\x20normalize\x20search\x20spelling;\x20see\x20normalized_searches.')\n\x20\x20\x20\x20if\x20not\x20apply:\n\x20\x20\x20\x20\x20\x20\x20\x20report['after']\x20=\x20_counts(col)\n\x20\x20\x20\x20\x20\x20\x20\x20return\x20report\n\x20\x20\x20\x20report['applied']\x20=\x20True\n\x20\x20\x20\x20try:\n\x20\x20\x20\x20\x20\x20\x20\x20for\x20item,\x20(_,\x20config,\x20normalized)\x20in\x20zip(report['templates'],\x20parsed):\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20col.decks.id_for_name(item['name'])\x20is\x20not\x20None:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20ValueError(f'Name\x20appeared\x20after\x20preflight:\x20{item[\"name\"]}')\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20new\x20=\x20col.sched.get_or_create_filtered_deck(DeckId(0))\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20new.name,\x20new.allow_empty\x20=\x20item['name'],\x20True\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20new.config.Clear()\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20new.config.CopyFrom(config)\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20did\x20=\x20int(col.sched.add_or_update_filtered_deck(new).id)\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20did\x20in\x20original_ids:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20RuntimeError(f'Backend\x20unexpectedly\x20reused\x20existing\x20ID\x20{did}')\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20row\x20=\x20{'id':\x20did,\x20'name':\x20item['name']}\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20report['created'].append(row)\x20\x20#\x20Retain\x20the\x20ID\x20even\x20if\x20verification\x20fails.\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20actual\x20=\x20col.sched.get_or_create_filtered_deck(DeckId(did))\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20row.update(name=actual.name,\x20config=_config_json(actual.config),\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20card_count=int(col.db.scalar('select\x20count(*)\x20from\x20cards\x20where\x20did=?',\x20did)))\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20expected\x20=\x20FilteredDeckConfig()\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20expected.CopyFrom(config)\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20for\x20term,\x20search\x20in\x20zip(expected.search_terms,\x20normalized):\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20term.search\x20=\x20search\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20actual.name\x20!=\x20item['name']\x20or\x20actual.config\x20!=\x20expected:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20RuntimeError(f'Readback\x20mismatch\x20for\x20new\x20filtered\x20deck\x20{did};\x20inspect\x20partial_report')\n\x20\x20\x20\x20\x20\x20\x20\x20report['after']\x20=\x20_counts(col)\n\x20\x20\x20\x20\x20\x20\x20\x20if\x20report['before']\x20!=\x20report['after']:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20raise\x20RuntimeError('Card/revlog\x20totals\x20changed\x20during\x20import;\x20inspect\x20partial_report')\n\x20\x20\x20\x20except\x20Exception\x20as\x20error:\n\x20\x20\x20\x20\x20\x20\x20\x20raise\x20PartialImportError(str(error),\x20report)\x20from\x20error\n\x20\x20\x20\x20finally:\n\x20\x20\x20\x20\x20\x20\x20\x20try:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20created_ids\x20=\x20{row['id']\x20for\x20row\x20in\x20report['created']}\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20report['new_parent_decks']\x20=\x20[{'id':\x20n.id,\x20'name':\x20n.name}\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20for\x20n\x20in\x20col.decks.all_names_and_ids()\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if\x20n.id\x20not\x20in\x20original_ids\x20and\x20n.id\x20not\x20in\x20created_ids]\n\x20\x20\x20\x20\x20\x20\x20\x20except\x20Exception\x20as\x20error:\n\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20report['inspection_error']\x20=\x20str(error)\n\x20\x20\x20\x20return\x20report\n")
Abre el perfil de origen en Anki Desktop y vuelve a su ventana principal. Abre la consola de depuración con Ctrl+Shift+; en Windows/Linux o Command+Shift+; en macOS. Algunas distribuciones de teclado utilizan : o + en lugar de ;. Pega un comando en el panel superior y ejecútalo con Ctrl+Return o Command+Return, sin Shift. Estas son las instrucciones oficiales de la consola; la verificación descrita más adelante no utilizó la consola gráfica.
Carga el archivo con el siguiente comando y sustituye la ruta de ejemplo por su ruta completa. Las barras inclinadas también funcionan dentro de una ruta de Windows entre comillas, como C:/Users/YOU/Downloads/anki_filtered_templates.py. El objeto mw.col es la colección abierta en este perfil de Anki; no apuntes un proceso de Python instalado por separado a un archivo de colección que esté en uso.
from pathlib import Path; from aqt import mw; exec(Path("/FULL/PATH/anki_filtered_templates.py").read_text(encoding="utf-8"))
3. Exporta las definiciones antes de preparar las tarjetas
Elige un nombre nuevo para el archivo JSON dentro de una carpeta existente. El siguiente comando exporta todos los mazos filtrados actuales en orden de nombre e imprime cuántos se han exportado. La exportación lee la colección y se niega a sobrescribir un archivo de salida existente. Cambia la ruta por la correspondiente a tu equipo.
manifest = export_filtered_templates(mw.col, "/FULL/PATH/templates.json"); print(len(manifest["templates"]))
Para un conjunto seleccionado, indica los nombres exactos en el orden en que quieras que se construyan. Por ejemplo: manifest = export_filtered_templates(mw.col, "/FULL/PATH/selected-templates.json", names=["Morning", "Exam"]). Se rechazan los nombres de mazos normales, los nombres inexistentes, los duplicados y una selección vacía. Los nombres de este ejemplo son marcadores que debes sustituir por los de tus mazos filtrados reales.
Abre el JSON en un editor de texto. Sus campos de nivel superior son schema, version y templates; cada plantilla tiene name y config. En search_terms, cada búsqueda contiene su consulta, limit y order. Comprueba el segundo término, si existe, así como reschedule y los valores de vista previa. Un limit de 0 sigue siendo 0, y reschedule=false sigue siendo false. Conserva los campos generados en lugar de eliminar un valor porque parezca predeterminado.
Conserva una copia del archivo original. Reordenar templates cambia el orden de construcción; cambiar una consulta modifica qué tarjetas del destino pueden reunirse. Si una plantilla de origen tiene una consulta vacía, la exportación se detiene y pide una consulta explícita. Utiliza deck:* solo cuando realmente quieras incluir todos los mazos; no lo introduzcas simplemente para descartar un error.
4. Añade un paquete opcional de tarjetas sin tu programación
Omite este paso si la persona que recibe el archivo ya tiene tarjetas adecuadas. En caso contrario, después de exportar el JSON, utiliza Vaciar (Empty) en cada mazo filtrado de origen que contenga tarjetas pertenecientes al mazo normal de origen que quieres entregar. Vaciar devuelve esas tarjetas a su mazo de origen sin eliminarlas. Esto cambia deliberadamente su ubicación actual dentro de la colección de origen; el programa auxiliar no realiza este paso por ti.
Exporta el mazo normal de origen, incluidos los submazos que quieras entregar, como un Anki Deck Package (.apkg). Desactiva Include Scheduling. Elige si incluir archivos multimedia según lo que necesiten las tarjetas. No lo sustituyas por un .colpkg de toda la colección. La opción de ajustes preestablecidos de mazos normales de Anki es independiente de las definiciones de filtros proporcionadas en JSON.
Este orden es importante. En nuestra prueba real del backend, exportar el mazo de origen mientras sus tarjetas seguían prestadas a filtros situados fuera de esa jerarquía omitió una de las tres tarjetas previstas. Un filtro anidado que contenía tarjetas se incorporó al primer paquete como un mazo normal. Después de vaciar los filtros de origen, el nuevo paquete incluyó las tres tarjetas en su mazo normal de origen y omitió las definiciones de los filtros, que ya estaban vacíos. Al inspeccionar las bases de datos internas de sus colecciones, ambos paquetes contenían cero filas en el registro de repasos.
La persona que recibe los archivos importa el APKG antes que el JSON y después comprueba las tarjetas, etiquetas y nombres de mazos de origen esperados. Una importación que combina datos no garantiza que las notas existentes permanezcan intactas: en el caso probado con GUID coincidentes, los campos y las etiquetas de la nota del destino se actualizaron con los valores más recientes del origen, según los ajustes de importación, mientras que los registros de tarjetas y las filas del registro de repasos existentes en el destino permanecieron sin cambios. Revisa esos ajustes y sus resultados si la otra persona ya tiene tarjetas relacionadas. El JSON que contiene solo ajustes tiene un efecto más limitado que la importación independiente del paquete de tarjetas.
5. Comprueba previamente el archivo completo en el perfil de destino
Cambia al perfil de destino previsto, vuelve a abrir la consola y carga allí el programa auxiliar con el comando del paso 2. Elige un prefijo si te ayuda a distinguir las nuevas vistas. Shared:: crea nombres como Shared::Morning; Shared debe ser un mazo padre normal, y durante la importación efectiva puede crearse un nuevo mazo padre normal. La comprobación previa en sí no crea nada.
plan = import_filtered_templates(mw.col, "/FULL/PATH/templates.json", prefix="Shared::"); pp(plan)
Lee cada name y config previstos, además de normalized_searches y warnings. El programa auxiliar valida todas las entradas antes de crear ningún mazo: rechaza JSON no válido, claves duplicadas, campos o valores de order desconocidos, búsquedas mal formadas o vacías, nombres no válidos y conflictos con nombres de destino. Cada plantilla requiere una o dos búsquedas no vacías. Los nombres existentes se rechazan, en lugar de sobrescribirlos silenciosamente o aceptarlos con un sufijo + automático.
El prefijo no modifica deck:Course, tag:chapter1 ni ninguna otra parte de una consulta. Una búsqueda de ID de tarjeta con cid: o de ID de nota con nid: conserva el ID de origen, que podría no identificar el registro previsto en otra colección. Comprueba que esos destinos correspondan a los registros previstos en la colección de destino o edita deliberadamente una copia de trabajo del JSON y vuelve a ejecutar la comprobación previa. Anki puede normalizar la forma escrita de una búsqueda válida. El plan muestra ese resultado y, tras la creación, el importador comprueba que la consulta guardada coincida con él.
La comprobación previa revisa la configuración y la sintaxis de búsqueda, no el conjunto final de tarjetas ni todas las condiciones de construcción específicas del planificador. Una consulta válida puede no encontrar ninguna tarjeta que cumpla los requisitos. La configuración del planificador de quien recibe el archivo también puede diferir de la del remitente; más adelante se describe el límite probado para el orden por recuperabilidad. Resuelve los errores de nombre o sintaxis antes de utilizar apply=True.
6. Aplica una vez y verifica los mazos creados
Cuando el informe sea correcto, ejecuta el siguiente comando con la misma ruta JSON y el mismo prefijo. Este bloque imprime el resultado completo o el informe de error parcial y solicita una actualización de la interfaz de Anki. Aplicar los ajustes supone un cambio deliberado en la colección de destino. No lo ejecutes por error en el perfil de origen.
exec("try:\n\x20\x20\x20\x20result\x20=\x20import_filtered_templates(mw.col,\x20'/FULL/PATH/templates.json',\x20prefix='Shared::',\x20apply=True)\nexcept\x20PartialImportError\x20as\x20error:\n\x20\x20\x20\x20pp(error.partial_report)\n\x20\x20\x20\x20mw.reset()\n\x20\x20\x20\x20raise\nelse:\n\x20\x20\x20\x20pp(result)\n\x20\x20\x20\x20mw.reset()\n")
Para cada entrada de created, revisa id, name, card_count y config. El programa auxiliar siempre solicita un nuevo ID de mazo filtrado y vuelve a leer el resultado. Comprueba el nombre y la configuración guardados, permitiendo la normalización de la consulta que se muestra en el plan. Los totales de tarjetas y revlog de before y after deben coincidir: esta operación reorganiza tarjetas locales que cumplen los requisitos; no importa tarjetas de origen ni historial de repasos.
Abre los nuevos mazos filtrados y compara sus búsquedas, límites, orden de selección, opción de reprogramación y demoras de vista previa con el JSON. Comprueba una tarjeta que sepas que coincide y su mazo de origen. La igualdad de los recuentos globales, por sí sola, no demuestra que la consulta haya seleccionado las tarjetas que pretendías. Una vista filtrada puede estar vacía legítimamente, porque el programa auxiliar permite un resultado vacío.
Conserva juntos el JSON y el resultado impreso. Si más adelante quieres otro conjunto, elige nombres nuevos o un prefijo nuevo y vuelve a realizar la comprobación previa. Se rechaza repetir la misma importación con nombres de destino que no hayan cambiado; el programa auxiliar no actualiza los mazos existentes.
7. Entiende el solapamiento, Vaciar y los errores parciales
El primer mazo que se construye tiene prioridad sobre las tarjetas que coinciden con varias búsquedas. Si Morning reúne las tarjetas 1, 2 y 3, un filtro Exam construido después no puede reunir también las tarjetas 2 y 3 mientras sigan en Morning. En la ilustración, Exam recibe la tarjeta 4. Estos son rótulos de ejemplo para tarjetas sintéticas, no consultas ejecutables ni ID reales de tarjetas de Anki. Los mazos filtrados existentes también pueden contener tarjetas que, de otro modo, coincidirían con un filtro nuevo.
Entre las exclusiones habituales están las tarjetas suspendidas, las enterradas y las que ya están en otro mazo filtrado. Pueden aplicarse otras reglas de elegibilidad. El programa auxiliar no reactiva tarjetas suspendidas ni vacía filtros existentes. Comprueba la búsqueda real, los límites, la elegibilidad y el orden de construcción antes de interpretar un resultado bajo o nulo como pérdida de datos.
El ajuste reschedule controla las consecuencias de estudiar las tarjetas reunidas. Con la reprogramación activada, las respuestas pueden cambiar su programación normal; el modo de vista previa utiliza en cambio su propio comportamiento. Transferir una plantilla es una operación distinta de estudiar después sus tarjetas. Verifica esta opción antes de empezar los repasos.
Si la aplicación falla después de que otras plantillas se hayan creado correctamente, esos nuevos mazos permanecen. Lee created y new_parent_decks en el informe parcial impreso e inspecciona el destino. Para descartar las nuevas vistas, utiliza Vaciar (Empty) únicamente en los mazos filtrados recién creados que se identifican en ese informe. Confirma que sus tarjetas han vuelto a sus mazos de origen y que las vistas están vacías; después elimina esos nuevos mazos filtrados si no los quieres. No elimines un mazo padre normal que contenga otras tarjetas o mazos.
No hay reversión automática del lote ni se garantiza un único Deshacer. Vaciar un mazo filtrado nuevo devuelve sus tarjetas; no elimina los registros de los mazos recién creados, no borra el estudio realizado después, no revierte una actualización previa de notas por APKG ni restaura todos los ajustes de la colección. Corrige el problema de fondo y comprueba previamente el trabajo pendiente, en lugar de volver a ejecutar los mismos nombres a ciegas.
8. Qué se verificó y qué sigue dependiendo de la versión
El programa auxiliar en su forma normal superó 21 pruebas con el backend real de Python/Rust de Anki 26.9.3, cuya versión de aplicación indicada es 26.09.3, en colecciones temporales. El conjunto de pruebas incluyó una exportación/importación/reexportación de 20 plantillas, dos búsquedas, valores explícitos false y cero, solicitudes exactas de nuevos ID, errores de nombres y sintaxis, historiales de origen no vacíos y instantáneas lógicas completas de SQLite para las operaciones de exportación y comprobación previa, ambas de solo lectura. Generó datos reales de registro de repasos respondiendo tarjetas a través del backend.
Los casos de solapamiento y Vaciar utilizaron operaciones reales del backend. La prueba de error parcial introdujo deliberadamente un error en la segunda creación después de una primera creación real; verifica el informe y la conservación del trabajo anterior, no la frecuencia de los errores naturales. Un revisor independiente ejecutó otros cuatro casos del backend: un error natural en el segundo elemento con el orden de recuperabilidad 8, un conflicto posterior con un mazo padre filtrado, un valor de enumeración desconocido posterior y una consulta que contenía un ID real de tarjeta de la colección sintética de origen. El error natural conservó el primer mazo creado y lo incluyó correctamente en el informe; los dos errores de comprobación previa dejaron todas las tablas de SQLite sin cambios. El caso del ID de tarjeta conservó su consulta literal cid: y se importó correctamente en un destino vacío sin reunir ninguna tarjeta, lo que demuestra que una sintaxis válida no garantiza que se obtengan las tarjetas de destino previstas. Otras pruebas reales de APKG verificaron la entrega de tarjetas normales, una combinación con GUID coincidentes y la secuencia corregida de Vaciar antes de exportar, y después combinaron ese paquete con esta versión exacta del programa auxiliar.
Durante la preparación inicial de las pruebas apareció un límite concreto de compatibilidad: RETRIEVABILITY_ASCENDING, orden 8, falló dentro de Anki al construir los datos de prueba de origen en una colección nueva sin FSRS, antes de que se ejecutara el programa auxiliar. La prueba final de ida y vuelta con 20 plantillas utilizó los órdenes funcionales del 0 al 7. Los 11 nombres y números de orden definidos superaron la comprobación previa de solo lectura; eso no significa que los 11 permitan construir mazos correctamente con cualquier configuración del planificador. Si tu orden falla, revisa el error y las creaciones anteriores y, después, elige un orden compatible o ajusta deliberadamente la configuración del destino antes de volver a intentarlo.
El programa utiliza las API de configuración y del planificador de Anki, que pueden cambiar entre versiones. Copia los siete campos definidos por la versión probada; no afirma admitir opciones futuras desconocidas. No se probaron la interfaz gráfica de Anki, perfiles privados de usuarios, aplicaciones móviles, una matriz de plataformas ni una colección grande de producción. Conserva el código fuente, el JSON y el resultado, y repite las comprobaciones pertinentes cuando cambies de versión de Anki.
