Repository navigation
IPC y manejo de errores
English · Español
Todo lo que la pantalla le pide a Rust pasa por una sola función tipada, call() en src/lib/ipc.ts; todo lo que Rust le devuelve pasa por eventos tipados que se escuchan con on(). Los fallos llegan siempre como un AppError { code, params } que la pantalla convierte en una frase en el idioma actual. Esta página lista los 177 comandos, el modelo de errores, los bindings de TypeScript generados, el protocolo de trabajos largos y los diálogos de ficheros.
src/lib/ipc.ts exporta:
| Export | Función |
|---|---|
interface Commands |
Una entrada por cada comando de Rust: name: { args: {...}; result: T }. El compilador comprueba cada call() contra ella. |
call<C extends keyof Commands>(cmd, args?) |
Envoltorio fino sobre invoke de @tauri-apps/api/core. Es el único sitio donde se importa invoke (comprobado con grep: ningún otro fichero de src/ llama a invoke). |
interface Events |
Nombre de evento a tipo de payload para los eventos que la pantalla escucha con on() (19 entradas). No están los avisos del juego en red (red://aviso, salas://…) ni mesa://originales: se escuchan con listen directamente (véase Eventos). |
on(evento, f) |
Importa listen de @tauri-apps/api/event de forma dinámica; devuelve la función para dejar de escuchar (como promesa). |
Convenciones que dan problemas:
-
Los nombres de los argumentos van en camelCase en el lado de JS. Los parámetros de Rust van en snake_case; Tauri los traduce:
job_idesjobId,scryfall_idesscryfallId,type_filterestypeFilter,highres_onlyeshighresOnly. Los campos de struct dentro de los payloads siguen el#[serde(rename_all = "camelCase")]de cada struct. -
Los resultados unitarios pasan a ser
null(result: nullenCommands), yOption<T>pasa a serT | null. -
Commandsse mantiene a mano. Los tipos de argumentos y resultado a los que hace referencia están generados (véase Bindings de ts-rs), pero la lista en sí no. Añadir un comando de Rust sin entrada enCommandscompila sin problema y simplemente no se puede llamar desde código tipado. Hoy los 177 nombres degenerate_handler!y las 177 claves deCommandscoinciden exactamente (comprobado comparando ambas listas). -
Las importaciones directas de Tauri fuera de
ipc.tsexisten solo para APIs que no son comandos:@tauri-apps/plugin-dialog(src/lib/dialogos.ts),getCurrentWindow().show()(src/main.tsx),getCurrentWebview()para soltar ficheros (src/features/preview/arrastrar-ficheros.ts),getVersion()(src/features/settings/settings-dialog.tsx),convertFileSrc(src/lib/image-url.ts),getCurrentWindow()para la guarda de cierre (src/features/shell/confirmar-cierre.tsx) y la pantalla completa (src/features/mesa/barra-mesa.tsx), la claseChannelde@tauri-apps/api/core(src/features/mesa/conexion.ts, véase Canales) ylistende@tauri-apps/api/eventpara los eventos que no están enEvents(src/features/mesa/red/conexion-red.ts,src/features/salas/conexion-salas.ts,src/features/mesa/originales.ts).
// src-tauri/src/lib.rs
.invoke_handler(tauri::generate_handler![
commands::sistema::ping,
commands::carpetas::carpeta_recordar,
// ... 216 entries
])Cada área es un módulo en src-tauri/src/commands/ (mod.rs los lista): actualizar, cards, carpetas, decklist, diseno, export, fichero, fuentes, iconos, identify, imagen, layout, library, mesa, pasar, red, salas, sistema, stats, temas. Los dos comandos de enlaces (enlace_pendiente, enlace_invitacion) viven fuera de esa carpeta, en src-tauri/src/enlace.rs, igual que pbdeck_pendiente (src-tauri/src/abrir.rs). Los comandos reciben State<'_, AppState> (véase Capa de aplicación Rust), a veces un AppHandle (para emitir eventos o usar plugins), y devuelven AppResult<T> (Result<T, AppError>). Algunos devuelven T directamente porque no pueden fallar: consultas baratas (ping, parse_decklist, deck_list_text, arrange_cards, cache_stats, themes_installed, ocr_ready, names_index_info, design_structure, design_remove, update_status, red_estado, salas_estado, enlace_pendiente, enlace_invitacion), los de cancelar (job_cancel, export_cancel), los datagramas del movimiento en vivo (mesa_fantasma, mesa_senal, resultado unitario) y los trabajos que solo devuelven un id y cuentan los fallos en su evento done (resolve_decklist, export_pdf, identify_batch, mesa_empezar, mesa_retomar). Los trabajos red_unirse, red_empezar y salas_deck devuelven AppResult<u32> porque pueden negarse antes de empezar (Busy, un código que no se entiende, sin sala); design_complete_lang también devuelve AppResult<u32>, pero nunca falla antes del id.
Los ficheros están bajo src-tauri/src/commands/. «Trabajo» significa que el comando devuelve al instante un id de trabajo y comunica el progreso y la finalización mediante eventos (véase Trabajos largos).
Sistema (sistema.rs)
| Comando | Función |
|---|---|
ping |
Devuelve "pong"; demuestra que el puente funciona. |
ping_error |
Falla siempre con InvalidInput; demuestra que los errores cruzan el puente y se traducen. |
app_plataforma |
Plataforma (escritorio, android o ios, de cfg!(target_os)): la pantalla lo pregunta una vez (src/lib/plataforma.ts) para decidir el comportamiento táctil y qué esconder en un móvil («Mostrar en carpeta», por ejemplo). Es el mismo enum que lleva el Hola del servidor de salas para que no se elija un móvil de anfitrión. |
app_salir |
app.exit(0): el botón «atrás» de Android en la biblioteca no tiene «cerrar», así que la pantalla pregunta dos veces («Pulsa otra vez para salir») y luego llama a esto, tras guardar lo suyo. |
app_pantalla_completa(si) |
Esconde o enseña las barras del sistema de Android. La pantalla ya no la llama: la actividad tiene la app a pantalla completa todo el rato (Móvil). En escritorio no hace nada (sin_barras_es_ok toma el Unsupported de android.rs como «nada que hacer»; cualquier otro error se devuelve). |
app_compartir_texto(texto) |
Comparte un texto (un código de partida o un enlace de invitación) con otra app del móvil. El plugin solo comparte ficheros, así que el texto va en invitacion.txt de la caché y se comparte como text/plain. Unsupported fuera de Android: la pantalla solo lo ofrece allí. |
Carpetas para los diálogos (carpetas.rs)
| Comando | Función |
|---|---|
carpeta_recordar |
Recuerda la carpeta de una ruta elegida bajo la clave de app_settings carpeta_importar o carpeta_exportar (TipoCarpeta). |
carpeta_inicial |
Devuelve la ruta en la que abrir un diálogo: la última carpeta de ese tipo (si todavía existe), unida a un nombre de fichero sugerido. |
Ficheros en el móvil (fichero.rs, y pbdeck_pendiente en src-tauri/src/abrir.rs; véase Rutas y URI content://)
| Comando | Función |
|---|---|
fichero_temporal(nombre) |
Una ruta nueva dentro de la caché (app_cache_dir/fichero/<uuid>/<nombre>, nombre saneado) donde se escribe una exportación antes de compartirla. La copia se conserva: Copia::conservar. |
compartir_fichero(ruta) |
Comparte un fichero con otra app (ACTION_SEND). Solo un content:// o un fichero dentro de app_cache_dir (canonicalizado, así que ni .. ni un enlace sacan de ella); cualquier otra ruta es InvalidInput, motivo: compartir_fuera, para que la vista web no pueda dar a otra app la base de datos ni la clave del nodo (fichero::para_compartir). Corre en spawn_blocking. Unsupported fuera de Android. |
pbdeck_pendiente |
El content:// de un .pbdeck que otra app abrió con esta (o con el que arrancó la app), una sola vez (luego null). Los siguientes llegan como el evento pbdeck://abierto. Rust no importa nada en ese momento: la pantalla lo ofrece, con las opciones del importador, y la persona confirma. |
Actualizador (actualizar.rs; la lógica está en src-tauri/src/actualizar.rs, véase Versiones y actualizaciones automáticas)
| Comando | Función |
|---|---|
update_status |
Versión actual, si esta instalación puede actualizarse (soportada) e instalaElSistema (Android: el instalador del sistema pide confirmar y relanza la app; no hay «reiniciar»). |
update_check |
Consulta latest.json; devuelve ActualizacionInfo o null. |
update_download |
Descarga la versión encontrada; emite update://progress. |
update_install |
Instala la versión descargada y reinicia. Se niega con Busy mientras AppState::ocupado(). |
Texto de la lista (decklist.rs; véanse Parseo de la lista del mazo, Exportación)
| Comando | Función |
|---|---|
parse_decklist |
Parsea una lista pegada a ParsedDecklist (proxy_core::decklist::parse). Síncrono y barato; se llama mientras se escribe. |
deck_list_text |
Escribe el mazo como lista en un FormatoLista (Moxfield, Arena, MTGO, texto plano) para copiarla. |
deck_list_save |
Guarda esa lista en una ruta .txt absoluta (rechaza cualquier otra con InvalidInput). |
Layout (layout.rs; véase Motor de maquetación)
| Comando | Función |
|---|---|
compute_page_plan |
layout::plan(&deck) a PagePlan. Errores: InvalidInput con motivo card_does_not_fit o invalid_settings. |
arrange_cards |
«Auto ordenar»: devuelve el nuevo orden de los ids de las cartas. |
Biblioteca: mazos, etiquetas, blobs, ajustes, presets, paquetes (library.rs; véanse Biblioteca de mazos, Almacenamiento y blobs, Formato del paquete de mazo)
| Comando | Función |
|---|---|
deck_list |
Resúmenes de todos los mazos. |
deck_create |
Mazo nuevo vacío con el reverso genérico de la app. |
deck_open |
Carga un mazo (y lo registra como abierto). |
deck_peek |
Carga un mazo sin registrarlo como abierto (selector de portada). |
deck_details |
Colores, clase, precio, idiomas y formatos de los mazos (todos si ids está vacío), a partir de la caché de Scryfall, sin red. |
deck_save |
Persiste el mazo; devuelve SaveResult { updatedAt }. Lo llama el autoguardado. |
deck_rename / deck_duplicate / deck_delete
|
Renombrar un mazo, copiarlo con un nombre nuevo, borrarlo. |
tag_list / tag_create / tag_update / tag_delete
|
Etiquetas del usuario (create devuelve la etiqueta existente si el nombre ya existe). |
tag_icon_set |
Pone o quita (null) el icono de una etiqueta, un id de Iconify (InvalidInput, motivo: icono, si no lo es). |
deck_tags_set |
Añade y quita etiquetas en varios mazos a la vez. |
deck_pin |
Marca de favorito para varios mazos. |
deck_notes_set |
Texto de las notas de un mazo. |
deck_cover_set |
Carta de portada elegida (o null). |
deck_lang_set |
Idioma del mazo fijado a mano (o null para calcularlo). |
blob_import_files |
Copia al almacén de blobs los ficheros de imagen que elige el usuario; devuelve FaceImage[]. Los nombres pueden ser URI content://. |
blob_generic_back |
El reverso genérico de carta de la app como FaceImage. |
blob_gc |
Borra los blobs sin referencias con más de 24 h; Busy mientras corre un trabajo. |
optimizar_decks, optimizar_estimar(ids), optimizar_lanzar(ids)
|
Los decks con imágenes que optimizar, lo que se liberaría y el trabajo (optimizar://progress, optimizar://done; se cancela con job_cancel); Busy con un trabajo o una partida en marcha. |
app_setting_get / app_setting_set
|
Ajustes clave/valor en SQLite (app_settings). |
cache_stats / cache_clear
|
Tamaño de app_cache_dir/renders / lo borra. Nunca toca los blobs. |
deck_export_package |
Escribe un mazo con todas sus imágenes en un .pbdeck; devuelve bytes. |
deck_import_package |
Importa un .pbdeck (ids nuevos, imágenes verificadas); devuelve el Deck. |
preset_list / preset_save / preset_delete
|
Presets con nombre por PresetKind (ajustes de impresión, etc.). |
Cartas: Scryfall y listas (cards.rs; véanse Integración con Scryfall, Añadir carta y variantes, Explorador de sets)
| Comando | Función |
|---|---|
resolve_decklist |
Trabajo. Resuelve un ParsedDecklist a cartas con sus imágenes guardadas. Eventos resolve://progress, resolve://done. |
job_cancel |
Cancela cualquier trabajo iniciado desde AppState::jobs (misma implementación que export_cancel). Devuelve si el trabajo todavía existía. |
card_autocomplete |
Nombres del autocompletado de Scryfall. |
card_search |
Búsqueda con la sintaxis de Scryfall, como máximo 50 resultados (CardSummary[]); idioma y solo alta resolución opcionales. |
card_prints |
Todas las impresiones del nombre de una carta (selector de variantes). |
cartas_buscar |
Una página de la pantalla Buscar (FiltrosCartas, página desde 1) → PaginaCartas (cartas, total, hayMas). Sin consulta o sin resultados es una página vacía, no un error. Véase Búsqueda de cartas. |
carta_ficha / carta_aclaraciones
|
Los detalles de una impresión (FichaCarta); sus aclaraciones (AclaracionCarta[]). |
card_fetch |
Descarga una impresión y la devuelve como DeckCard. |
card_fetch_externa |
Lo mismo para una carta de una fuente que no es Scryfall (FuenteCarta, hoy las fichas de Cockatrice). |
deck_tokens_detect |
Propone las fichas que faltan en un mazo (FichaPropuesta[]); no añade nada. |
sets_list / set_cards
|
Catálogo de sets; cartas de un set, opcionalmente por tipo. |
import_archidekt |
Importa un mazo público de Archidekt por URL. |
Exportar (export.rs; véanse Exportación, Imagen y PDF)
| Comando | Función |
|---|---|
export_pdf |
Trabajo. Exporta la instantánea de mazo dada a una ruta de PDF o a un content:// (varios ficheros si el mazo se divide; en Android, un solo ZIP). Eventos export://progress, export://done. |
export_estimate |
Tamaño aproximado del PDF (bytes, images, pages), cuántos PDF saldrán (files) y el aviso de memoria (memory, lowMemory); véase Exportación. |
export_calibration |
Escribe la hoja de calibración a doble cara; los textos vienen de la pantalla. |
export_cancel |
Cancela el trabajo de exportación. |
export_open / export_reveal
|
Abren el fichero con el visor del sistema / lo muestran en su carpeta. Solo se aceptan ficheros .pdf o .pbdeck que existan (exportado()); si no, InvalidInput. En Android export_open comparte el fichero en su lugar y export_reveal es Unsupported. |
Imagen de una carta suelta (imagen.rs)
| Comando | Función |
|---|---|
card_export_image |
Guarda una cara de una carta (frente o dorso; deckBack es el dorso del mazo) como imagen, la misma que imprimiría el PDF (retoques, oscurecido, carta diseñada), a los PPP de la calidad del PDF y sin sangrado. El formato sale de la extensión del fichero (PNG si no tiene; una desconocida es InvalidInput con motivo: formato). Devuelve la ruta escrita. Corre en spawn_blocking. |
Estadísticas (stats.rs; véase Estadísticas)
| Comando | Función |
|---|---|
deck_stats |
DeckStats para unas cartas y un DeckKind opcional; pide a /cards/collection de Scryfall los datos que faltan o están caducados (más de 24 h). |
Identificar y vincular cartas propias (identify.rs; véase Identificación de cartas y OCR)
| Comando | Función |
|---|---|
ocr_ready |
¿Están descargados los modelos de OCR? |
card_identify |
Propone cartas de Scryfall para una imagen guardada (texto de pista y OCR opcional). |
identify_batch |
Trabajo. Identifica muchas imágenes; el resultado de cada una va en identify://progress y el final en identify://done. Se cancela con job_cancel. |
card_link |
Información de carta para un id de Scryfall elegido (primero la caché, luego la red, luego lo guardado para las estadísticas sin conexión). |
card_link_externa |
Lo mismo para una carta de una fuente externa. |
Índice de nombres y temas (fuentes.rs, temas.rs; véanse Fuentes externas, Temas y skins)
| Comando | Función |
|---|---|
names_index_info / names_index_download / names_index_remove
|
Estado, descarga (emite names://progress) y eliminación del índice de nombres en varios idiomas de MTGJSON. |
themes_catalog |
El registro de tweakcn. |
theme_fetch |
Un tema por id o enlace, para previsualizarlo. |
theme_install / themes_installed / theme_remove
|
Instalar (CSS + letras), listar y quitar temas de tweakcn. |
themes_open_community |
Abre la galería de la comunidad de tweakcn en el navegador. |
Iconos (iconos.rs; véase Iconos)
| Comando | Función |
|---|---|
iconos_colecciones |
Todas las colecciones visibles de Iconify (ColeccionIconos { prefijo, nombre, total, categoria }), en caché una semana. |
iconos_nombres(prefijo) |
Los nombres de una colección, en caché una semana. |
iconos_buscar(texto) |
Busca por nombre en todas las colecciones (hasta 999 ids). |
iconos_datos(ids) |
{ id: DatosIcono { body, vb } } de los iconos pedidos (memoria, elegidos en disco o la API); los cuerpos SVG peligrosos se descartan. |
iconos_elegir(id) |
Guarda un icono elegido en app_data_dir/iconos/elegidos.json para verlo sin red; NotFound si no existe. |
Diseñador de cartas (diseno.rs; véanse Capa de aplicación del diseñador, Motor del diseñador, Packs de marcos)
| Comando | Función |
|---|---|
design_draft |
Borrador de diseño para una carta del mazo (instantánea mecánica EN/ES, nombres originales, siguiente número libre del pie). |
collection_list / collection_save / collection_delete
|
Colecciones de diseño. |
fonts_list / font_import / font_remove
|
Letras propias. |
packs_list / pack_import / pack_remove
|
Packs de marcos. |
fonts_free_list / font_free_download
|
Catálogo de letras libres (OFL) y su descarga. |
original_info / original_download
|
Estado y descarga de los marcos «Original» (emite original://progress). |
packs_downloadable_list / pack_downloadable_download
|
Packs descargables y su descarga (emite packs://progress). |
pack_extra_use |
Copia un extra de un pack (reverso, ficha, carta terminada) al almacén de blobs. |
symbols_status |
Estado de los símbolos de maná; los descarga si faltan o están caducados (refresh). |
design_register |
Registra el diseño que se está editando para pbimg://…/d/<hash>/<width>; devuelve el hash. |
design_overlay |
La carta sin ilustración más la ventana de la ilustración (pbimg://…/c/…), para mover la ilustración en vivo; null para cartas compuestas o grandes. |
design_fit_report |
Avisos: texto que no cabe, símbolos o letras que faltan. |
design_structure |
Cómo está maquetada una cara (layout y texto dividido) para el editor. |
design_suggest_translation |
Sugerencia experimental de traducción para una cara. |
design_drafts_for_deck |
Trabajo. Borradores para todas las cartas sin diseñar. Eventos design://progress, design://done. |
design_complete_lang |
Trabajo. Completa un idioma de diseño en las cartas diseñadas del mazo: baja las letras CJK si es japonés, coreano o chino y la impresión que le falte a cada carta. Cada carta hecha llega por designlang://progress y el final por designlang://done; se cancela con job_cancel. |
design_export_png |
Trabajo. Exporta las cartas diseñadas como PNG a una carpeta. Eventos designpng://progress, designpng://done. |
design_save / design_remove
|
Aplicar un diseño a una carta (miniatura al almacén de blobs) / revertir la carta. |
design_rerender / design_renumber
|
Refrescar miniaturas caducadas / renumerar los diseños según el orden del mazo; devuelven las cartas que han cambiado. |
art_from_clipboard / art_from_blob / art_from_scryfall / art_from_card
|
Obtener la ilustración: portapapeles del sistema, fichero guardado, art_crop de Scryfall, recorte de una carta existente (fracciones 0–1). |
Mesa de juego (mesa.rs; la lógica en src-tauri/src/mesa/, véase Mesa de juego)
| Comando | Para qué |
|---|---|
mesa_empezar |
Trabajo. Prepara los decks de una ConfigMesa y abre la partida. Eventos mesa://progress, mesa://done. |
mesa_retomar |
Trabajo. Sigue la partida guardada y vuelve a preparar las imágenes que falten en la caché. Mismos eventos. |
mesa_estado |
EstadoMesa { abierta, deckIds, guardada }: si hay una partida abierta y, si no, el resumen de la guardada. |
mesa_suscribir |
Recibe un Channel<MensajeMesa>; manda la vista entera y, desde ahí, un parche por evento. |
mesa_vista |
La VistaMesa entera (cuando un parche llega fuera de orden), sin el registro. |
mesa_accion |
Manda una Accion (opcionalmente como otro jugador local); el cambio llega por el canal y un rechazo es Rejected (espectador para un espectador). |
mesa_mirar_como |
En una partida local de varios, pasa a mirar (y a jugar como) otro jugador; Rejected red en red. |
mesa_encuentro, mesa_banquillo, mesa_banquillo_cambiar, mesa_siguiente_partida
|
El marcador del «al mejor de», el mazo y el banquillo de un jugador, el cambio de cartas entre partidas y abrir la siguiente. mesa_banquillo y mesa_banquillo_cambiar son async: en una partida en red cada jugador cambia solo su mazo, y un invitado se lo pide al anfitrión y espera hasta 10 s (si no, Network sin_respuesta). Rechazos: ajeno, invitado, sin_terminar, sin_mazos, cambio. |
mesa_resumen |
El ResumenPartida de la partida abierta (puestos, rondas, duración), contado por mesa::resumir a partir del registro. |
partidas_de_deck |
HistorialDeck de un deck: sus partidas terminadas (FilaPartida), las victorias y los ids de las partidas. |
partidas_todas |
HistorialGeneral: una página de todas las partidas terminadas de todos los decks (limite, desde), con el total y las victorias. |
partida_borrar |
Borra una partida del historial junto con su repetición. |
repeticion_abrir |
Abre una partida del historial como repetición en su propia sesión (VistaRepeticion); Rejected partida_abierta con una partida abierta, sin_registro sin registro, Unsupported con version si viene de un catálogo más nuevo. |
repeticion_actual, repeticion_ir, repeticion_salir
|
Repetir la partida que acaba de terminar, en su sitio (Rejected sin_terminar si no ha terminado); ir a un punto (pos, como); salir de la repetición (vuelve a la partida terminada o, si se abrió desde el historial, cierra la mesa). Mientras se repite, mesa_accion es Rejected repeticion salvo el chat y los avisos rápidos; en red, lo que hacen los demás jugadores se sigue aplicando a la partida de verdad. |
mesa_guardar |
Guarda la partida ya (al salir de la pestaña, al cerrar la ventana). |
mesa_salir |
Sale de la mesa: con guardar la partida queda guardada; sin él, se borra el fichero. |
mesa_info_carta |
El texto de reglas de una carta que quien mira puede ver («Inspeccionar»). |
mesa_origen_scryfall |
Un OrigenCarta para una carta o ficha de Scryfall que no está en el deck (baja sus imágenes). |
mesa_sparring_carta |
El OrigenCarta de una de las cartas del rival fantasma del sparring, por su nombre (solo nombres de su lista; si no, InvalidInput). |
mesa_originales |
Pone en marcha «Originales en mi idioma»: para las cartas con vínculo a Scryfall que se le dan (≤ 1.000) busca la impresión en el idioma de la interfaz y contesta cada una por mesa://originales. Solo pantalla. |
mesa_listas |
ListasDeDeck { listas, cuentan } de un deck: sus listas para jugar y cuántas cartas cuentan sin lista. |
mesa_lista_cartas |
Las copias elegibles del deck con sus datos (CartaDeLista, de Scryfall o del diseño) para el editor. |
mesa_lista_comprobar |
Los AvisoLista[] de una lista para un formato (avisos o que impiden, véase Mesa de juego). |
mesa_lista_guardar / mesa_lista_borrar
|
Guarda (crea o sustituye; la devuelve con su id) o borra una lista para jugar. mesa_empezar rechaza un deck que necesita lista y no la tiene (InvalidInput, motivo: falta_lista) o una lista que no vale (motivo: lista). |
mesa_tapete_get / mesa_tapete_set / mesa_tapete_ajustes
|
El tapete de un deck (deck: null = el general): leerlo, ponerlo o quitarlo, cambiar solo el velo y el desenfoque. |
mesa_dados_get / mesa_dados_set / mesa_dados_imagen
|
El aspecto de los dados y la moneda del jugador (VistaAspectoDados): leerlo, guardarlo (con los colores comprobados), poner o quitar la imagen propia. |
Juego en red con un código directo (red.rs; la lógica en src-tauri/src/red/, véase Juego en línea)
| Comando | Para qué |
|---|---|
red_identidad / red_identidad_set
|
La Identidad con la que se juega en red (clave pública, apodo, avatar); cambiar el apodo y el avatar (una huella de blob o ninguno). |
red_identidad_exportar / red_identidad_importar
|
Guardar la identidad de la instalación en un fichero / sustituirla por una exportada antes. Importar es Busy (motivo: partida) con una partida, una sala por código o una sala del servidor abiertas; un fichero que no es una identidad es InvalidInput identidad. |
red_estado |
EstadoRed { papel, codigo, sala, empezada, puedeCeder }: si esta app es anfitrión, jugador o espectador, el código para invitar (solo el anfitrión), quién hay en la sala (solo el anfitrión; el invitado lo recibe por red://aviso; cada fila lleva un id para elegir asientos y equipos) y, para el anfitrión, si al salir ahora la partida pasa a otro jugador (puedeCeder) en vez de cerrarse. |
red_crear |
Abre una partida en red como anfitrión: levanta el nodo, atiende a quien llegue con el código y lo devuelve (PB1-…). Busy partida si ya hay una partida o una sala abiertas. |
red_unirse |
Trabajo. Se une con una PeticionUnion (código, apodo, rol, deck, lista, formato): prepara el deck (si juega) y conecta. Eventos mesa://progress, mesa://done; un rechazo del anfitrión es Rejected con su motivo. |
red_apodo |
Cambia el nombre de esta app en la sala de espera: el anfitrión se lo dice a todos al momento, un invitado se lo pide al anfitrión (AlHost::Apodo). NotFound sin_sala sin sala. |
red_anfitrion_deck |
El anfitrión cambia el nombre del deck con el que se le ve en la sala de espera (None: ninguno). NotFound sin_sala si no hay sala o ya empezó. |
red_deck |
Trabajo. Un invitado cambia el deck (y la lista) con el que juega en la sala de espera: lo prepara y se lo manda al anfitrión. Eventos mesa://progress, mesa://done. |
red_vuelta |
La partida en red de la que este invitado se cayó y a la que puede volver (Option<VueltaRed>: apodo y hora), de red/vuelta.rs. |
red_vuelta_olvidar |
Olvida esa partida. |
red_volver |
Trabajo. Se vuelve a unir a esa partida con su código, como jugador sin deck (el anfitrión reconoce el nodo y le devuelve el asiento); también con la mesa abierta tras perder la conexión. NotFound sin_vuelta si no la hay; un Rejected del anfitrión la olvida. |
red_empezar |
Trabajo. El anfitrión empieza la partida con los de la sala que trajeron deck; config lleva primero al anfitrión (y su deck) y, tras él, lo que el anfitrión eligió de cada invitado (color y equipo) en el orden de invitados, los ids de la sala (EnSala.id) por asiento; si no son exactamente los jugadores con deck de la sala, Rejected sala_cambio. En red no hay sparring. Mismos eventos. NotFound sin_sala si no hay una sala sin empezar. |
red_salir |
Sale de la partida en red (en la sala o empezada): el anfitrión la cede a otro jugador si puede (Anfitrion::irse) y, si no, la cierra para todos; el invitado se despide. La mesa la cierra mesa_salir. |
mesa_fantasma, mesa_senal
|
Movimiento en vivo: mandan un fantasma (carta o cursor, coordenadas 0–10.000) o un aro de 2 s como datagramas; el anfitrión pone el remitente y limita el ritmo. Sin partida en red, o como espectador, no hacen nada. |
Servidor de salas y playgroups (salas.rs; el cliente en src-tauri/src/red/servidor/, véase Juego en línea)
Todo comando que necesita el servidor falla con Network sin_servidor si no hay cliente; las negativas del propio servidor llegan como Server (véase Modelo de errores).
| Comando | Para qué |
|---|---|
salas_conectar |
Arranca el cliente del servidor si no lo estaba (con el apodo) y devuelve EstadoSalas. |
salas_estado |
EstadoSalas: si hay servidor configurado, si está conectado, la clave de esta instalación (yo), la sala y el papel en ella, y el deck preparado para la sala. |
salas_playgroups / salas_pg_crear / salas_pg_salir / salas_pg_expulsar / salas_pg_rol
|
Listar, crear y dejar un playgroup, expulsar a un miembro (por clave), cambiar el RolPlaygroup de un miembro; cada uno devuelve los Playgroup[] nuevos. |
salas_invitar / salas_revocar
|
Una Invitacion { codigo, caduca } a un playgroup (o, con null, a la sala en la que se está) / revocar una. |
salas_usar |
Usa un código de invitación; devuelve Usada (playgroups o la sala en la que se entró). |
salas_sala_crear / salas_sala_unirse
|
Crear una sala (SalaNueva, de 2 a 10 jugadores) / unirse a una como jugador o espectador; devuelven EstadoSala. Los dos, y salas_usar, son Busy partida con una partida o una sala por código abiertas. |
salas_sala_salir |
Sale de la sala (y de su partida, si la hay). |
salas_sala_expulsar, salas_sala_expulsar_jugador
|
Expulsan a un miembro de la sala (por clave) o a un jugador de la mesa (por asiento): quien creó la sala, por el servidor de salas, o el anfitrión con un código directo (Rejected no_anfitrion para cualquier otro). |
salas_deck |
Trabajo. Prepara el deck con el que se juega en la sala y se lo dice al servidor. Eventos mesa://progress, mesa://done; NotFound sin_sala fuera de una sala. |
salas_empezar / salas_terminar
|
Dicen al servidor que la partida de la sala empieza / ha terminado. |
Pasar decks entre equipos (pasar.rs; la lógica en src-tauri/src/pasar/, véase Pasar decks)
| Comando | Función |
|---|---|
pasar_recibir_abrir / pasar_recibir_estado / pasar_recibir_cerrar / pasar_recibir_cancelar
|
«Recibir»: abrirlo (levanta el nodo de iroh compartido si hace falta; devuelve el EstadoReceptor con el código PBD1-, su QR y el código de seis cifras), leer su estado, cerrarlo o cancelar lo que se esté recibiendo. Los cambios llegan como el evento pasar://receptor. |
pasar_peticion(aceptar) / pasar_responder(decisiones)
|
Aceptar o rechazar a un equipo de la red local que quiere enviar; contestar, deck por deck, qué hacer con la oferta (Decision: cuánto coger y, si el mismo deck ya existe, Resolucion). |
pasar_cerca_buscar / pasar_cerca_parar
|
Buscar equipos cerca con «Recibir» abierto (mDNS; en Android toma el bloqueo de multicast de la Wi-Fi); la lista llega como pasar://cerca. |
pasar_compartir_codigo(texto) |
Ofrece el código de «Recibir» a otra app con el selector del sistema. El texto tiene que ser un código PBD1- válido. Solo en Android; fuera, Unsupported. |
pasar_codigo_comprobar(texto) |
Comprueba un código PBD1- pegado o leído antes de enviar. Uno de partida (PB1-) pegado aquí da InvalidInput con motivo: codigo_de_partida; y uno de deck pegado donde va el de partida (red_unirse) da codigo_de_deck, que tiene frase propia. Véase Modelo de errores. |
pasar_nombre / pasar_nombre_set(nombre)
|
El nombre de este equipo al pasar decks; vacío vuelve al del sistema. Asíncronos: en Android preguntan al plugin de Kotlin. |
pasar_enviar(destino, decks, modo) / pasar_enviar_cancelar / pasar_emisor_estado / pasar_emisor_olvidar
|
Enviar decks a un Destino (equipo cercano o código); vuelve al momento y el avance llega como pasar://emisor. |
pasar_completar(deckId) |
Trabajo (un id, con pasar://imagenes-progress y pasar://imagenes-done): baja las imágenes que le faltan a un deck recibido como «solo originales». |
deck_paquete_info, deck_paquete_guardar, deck_paquete_compartir
|
Tamaño del .pbdeck de un deck en cada ModoPaquete (original, optimizado, sinProxies); guardarlo (una ruta o un content://) o compartirlo con otra app en uno de ellos. |
paquete_leer, paquete_importar
|
Leer un .pbdeck sin importarlo (qué trae y si ya hay el mismo deck aquí) / importarlo con lo que eligió la persona; InvalidInput eleccion si no eligió nada y falta_resolucion si el deck ya existe y no se dio resolución. |
Enlaces de invitación (src-tauri/src/enlace.rs)
| Comando | Para qué |
|---|---|
enlace_pendiente |
El código del enlace proxybuilder://unirse/<código> (o tapete://unirse/<código>) con el que arrancó la app, una vez (después, null). Los enlaces que llegan más tarde vienen como el evento salas://enlace. |
enlace_invitacion |
El enlace de invitación de un código (el que se copia en el panel), o null. |
Los comandos fn normales son síncronos y deben ser baratos (consultas a SQLite, layout::plan). Los comandos async fn esperan la red o delegan el trabajo. El trabajo pesado de CPU se delega de forma explícita: export_pdf usa tauri::async_runtime::spawn_blocking; el diseñador envuelve los renders en un ayudante que se ejecuta en un hilo aparte (commands/diseno.rs, «Corre f en un hilo aparte»). Deducido: como Tauri ejecuta los comandos síncronos normales en el hilo principal, mantenerlos baratos importa para la capacidad de respuesta de la interfaz; consulta la documentación de Tauri al añadir un comando síncrono pesado.
Definido en src-tauri/src/error.rs:
pub struct AppError { pub code: ErrorCode, pub params: BTreeMap<String, String> }
impl AppError { pub fn new(code) -> Self; pub fn with(self, clave, valor: impl ToString) -> Self }
pub type AppResult<T> = Result<T, AppError>;Deriva Serialize y TS, así que un invoke fallido se rechaza con el objeto JSON { code: "InvalidInput", params: { motivo: "…" } }. Rust nunca escribe prosa para el usuario: params lleva datos para la frase, no un mensaje.
ErrorCode tiene 14 variantes:
| Código | Significado | Orígenes típicos |
|---|---|---|
Network |
Sin conexión o el servidor no responde; con el motivo de error::red / error::estado_http (HTTP) o error::red_sin_http (iroh, mDNS), una frase por causa |
error.rs (red, estado_http, red_sin_http; esta última la usa pasar/), que usan scryfall/client.rs, fuentes/mod.rs, commands/cards.rs (Archidekt), ocr.rs, diseno/{letras_libres,letras_cjk,original}.rs; actualizador (Reqwest, Network) |
RateLimited |
HTTP 429 tras los reintentos | scryfall/client.rs |
NotFound |
Falta una carta, mazo, blob, tema o versión de actualización |
render.rs leer_blob (parámetro blob), card_link, actualizar.rs
|
Io |
Lectura o escritura de disco |
From<std::io::Error>, fallos de export_open, un content:// que no se puede abrir (io_uri de fichero.rs) |
Db |
Error de SQLite |
From<rusqlite::Error>, un lock envenenado es Internal
|
InvalidInput |
Disparates enviados desde la pantalla |
From<LayoutError>, deck_list_save, exportado(), card_export_image (motivo: formato), listas para jugar (falta_lista, lista, lista_sin_nombre), en red (codigo, deck, identidad), un código de la clase equivocada (codigo_de_deck, codigo_de_partida, red/codigo.rs), pasar decks (eleccion, falta_resolucion, oferta_grande, sin_espacio, sin_oferta, huella, importar), leer_bytes de fichero.rs por encima de su tope (fichero_grande) y compartir_fichero con una ruta fuera de la caché (compartir_fuera) |
Cancelled |
El usuario canceló | comprobaciones del flag de jobs en resolver, exportar, identificar, PNG, lote de diseño; PdfError::Cancelled; AuthenticationFailed del actualizador |
Pdf |
No se pudo construir el PDF |
From<PdfError>, errores de imagen de krilla en exportar
|
Image |
Imagen ilegible o demasiado grande |
From<ImagingError>, From<ProbeError>
|
Unsupported |
Formato de fichero no aceptado, o algo que solo existe en otra plataforma |
ImagingError::Unsupported, ProbeError::Unsupported; mesa/guardada.rs con motivo: version para una partida guardada o una repetición de un catálogo más nuevo; android.rs fuera de Android (detalle: android), export_reveal sobre un content:// (detalle: content), elegirCarpetaParaExportar en un móvil (detalle: carpeta, lanzado en la pantalla) y, en Android, motivo: instalar_permiso cuando el sistema aún no deja a esta app instalar paquetes (véase Versiones y actualizaciones automáticas) |
Busy |
Un trabajo, o una partida abierta en la mesa, impide esto ahora (motivo: partida cuando es la partida o una sala en red) |
blob_gc, optimizar_lanzar, update_install, red_crear, red_unirse, red_volver, red_identidad_importar, salas_usar, salas_sala_crear, salas_sala_unirse y las guardas del lado de JS |
Internal |
Inesperado (error de programación, pool de hilos fallido, lock envenenado) | varios map_err
|
Rejected |
La mesa de juego no acepta esa acción. params.motivo es el Rechazo del motor (NoExiste, SinPermiso, ZonaVacia, FueraDeTurno, Limite, NoSePuede, escrito con {:?}) o un motivo de la capa de la app en snake_case: la sesión (repeticion, como, red, no_existe, no_anfitrion), el encuentro al mejor de (invitado, sin_encuentro, sin_terminar, encuentro_terminado, cambio), la repetición (partida_abierta, sin_registro, sin_terminar, no_es_repeticion), un espectador que actúa (espectador), un anfitrión de código directo sin la mayoría (sin_mayoria) y el rechazo del anfitrión a un invitado (codigo, version, llena, empezada, sin_deck, ritmo, sin_partida, expulsado, no_activo…) |
mesa/sesion.rs (impl From<Rechazo> for AppError), mesa/encuentro.rs, mesa/repeticion.rs, commands/mesa.rs (mesa_accion, repeticion_abrir, repeticion_actual, mesa_info_carta), red/invitado.rs, salas_sala_expulsar_jugador
|
Server |
El servidor de salas ha dicho que no; params.code es el código del protocolo (version, firma, limite, no_existe, caducada, llena, sin_permiso, ya_empezada, expulsado, mensaje), con los params del servidor |
red/servidor/cliente.rs (pedir, y el saludo inicial, que solo lleva code) |
Claves de parámetros usadas en toda la app (contadas con grep): detalle (detalle técnico libre, la más común), motivo (motivo para máquinas como card_does_not_fit, invalid_settings, too_large, corrupt), estado (estado HTTP), blob, url, fichero, deck, carta, ficha, además de las claves de la carga de packs (estilo, hueco, falta, color, version). Trampa conocida: casi ninguna frase de src/lib/errores.ts contiene {placeholders}, así que estos parámetros no suelen mostrarse al usuario; son para registros, tests y textos futuros. La excepción es estado en las frases de Network rechazo y estado.
proxy-core no conoce AppError. Cada módulo tiene un enum de thiserror y la app convierte con From:
| Error del core | Definido en | Convertido en | Se convierte en |
|---|---|---|---|
LayoutError (CardDoesNotFit, InvalidSettings) |
proxy-core/src/layout/mod.rs |
src-tauri/src/error.rs |
InvalidInput + motivo
|
ImagingError (Unsupported, Corrupt, TooLarge, Encode) |
proxy-core/src/imaging/mod.rs |
src-tauri/src/render.rs |
Unsupported; Image + motivo=too_large; Image + detalle
|
ProbeError (Unsupported, Corrupt) |
proxy-core/src/imaging/probe.rs |
src-tauri/src/store/blobs.rs |
Unsupported; Image + motivo=corrupt
|
PdfError (Geometry, Image, Write, Cancelled) |
proxy-core/src/pdf/mod.rs |
src-tauri/src/commands/export.rs |
Cancelled; en otro caso Pdf + detalle
|
PackError |
proxy-core/src/diseno/pack.rs |
src-tauri/src/diseno/recursos.rs → de_pack
|
claves del pack (falta, estilo, hueco, color, version) |
DisenoError |
proxy-core/src/diseno/mod.rs |
capa de aplicación del diseñador |
Deducido: se convierte en los puntos de llamada con map_err (no leído entero) |
LineError |
proxy-core/src/decklist/mod.rs |
no es un tipo de error en el puente | Se serializa dentro de ParsedLine; la pantalla muestra un texto por variante |
También en el crate de la app: From<std::io::Error> (Io) y From<rusqlite::Error> (Db) en error.rs; el actualizador convierte tauri_plugin_updater::Error en actualizar.rs → error().
Ambos crates prohíben unwrap()/expect() fuera de los tests (#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]), así que los fallos deben fluir hacia estos tipos.
src/lib/errores.ts:
-
TEXTOS: Record<ErrorCode, string>tiene una frase fuente en español por código, envuelta enparaTraducir(...)para que las herramientas de i18n (pnpm idiomas) la vean; el inglés sale desrc/i18n/en/. El tipoRecord<ErrorCode, …>hace que el compilador falle si Rust añade un código que la pantalla no cubre (traspnpm bindings). -
esAppError(e): objeto cuyocodees una clave deTEXTOS. Los rechazos deinvokesonunknown, y un objeto JS plano como{ code: "Busy", params: {} }cumple, que es como el frontend lanzaBusypor su cuenta. -
MOTIVOS: Partial<Record<ErrorCode, Record<string, string>>>: una frase propia para los motivos conocidos de un código, buscada porparams.motivoo, si no hay, porparams.code. HoyRejectedtiene una por variante deRechazo, además de los rechazos del anfitrión (véase más abajo) y departida_abierta,sin_registro,sin_terminaryrepeticion(historial y repetición de partidas);InvalidInputtiene los motivos de las listas para jugar (falta_lista,lista,lista_sin_nombre) ycodigo,deck,identidad;Unsupportedtieneversioneinstalar_permiso(Android: «Android necesita tu permiso para instalar la actualización: se ha abierto su ajuste…»);InvalidInputtiene ademáscodigo_de_deck(el código de «Recibir un deck»,PBD1-, pegado donde va el de una partida);Busytienepartida(«Hay una partida abierta: termínala o sal de la mesa para actualizar.», escrita para la actualización pero que sale también cuando se rechaza un comando de red por lo mismo); yServertiene una por código del protocolo. Los motivos sin entrada (como,red,no_existe,no_es_repeticion,invitado,sin_encuentro,encuentro_terminado,cambio) enseñan la frase general del código. Un motivo desconocido cae en la frase general del código, así un motor más nuevo nunca enseña un motivo en crudo. -
mensajeDeError(e, t):AppError→t(MOTIVOS[code][motivo] ?? TEXTOS[code], params);Error→ su mensaje; cualquier otra cosa →String(e). -
useLang().mensaje(e)(src/components/language-provider.tsx) esmensajeDeErrorligada altactivo. Las pantallas llaman amensaje(e)en los bloquescatchy lo muestran en un toast o en línea.
Lo vigilan src/lib/__tests__/errores.test.ts y el test de Rust el_error_viaja_como_codigo_y_datos en error.rs.
Un comando de trabajo nunca se rechaza por fallos del trabajo; su evento done lleva error: AppError | null junto a result. El frontend comprueba d.error y trata Cancelled como un resultado silencioso (src/features/decklist/acciones.ts → resolver devuelve null; src/features/export/acciones.ts → exportarA anuncia «Exportación cancelada»).
Los textos de src/lib/errores.ts::MOTIVOS cubren también el juego en red: Network con motivo sin_servidor, sin_respuesta o desconectado; NotFound sin_sala; InvalidInput codigo (un código directo que no se entiende), deck e identidad (un fichero importado que no es una identidad); Rejected del host (codigo, version, llena, empezada, sin_deck, ritmo, espectador, sin_partida, expulsado, no_anfitrion, además de los motivos del motor; el rechazo de una acción mandada después le llega a un invitado como MensajeMesa::Rechazo, no como el error de mesa_accion); y todos los códigos de Server, expulsado incluido. red y como (mirar o actuar como otro jugador en red) no tienen frase propia. Los avisos de la red van como eventos (red://aviso, salas://…), no como errores. Ver Juego en línea.
Los fallos de HTTP corriente (Scryfall, Archidekt, fuentes externas, descargas) montan su error Network con crate::error::red(reqwest::Error) o crate::error::estado_http(StatusCode), nunca a mano, para que la pantalla los distinga: conexion (no se pudo conectar: revisar la conexión, el cortafuegos o el antivirus), tiempo (se pasó el plazo), respuesta (una respuesta que la app no entiende), rechazo (401/403, con {estado}: suele ser una VPN, un proxy o la red bloqueada) y estado (cualquier otro estado de error, con {estado}). Las dos funciones escriben además una línea warn en proxy-builder.log con el host y la cadena entera de causas, que es lo que hay que pedir cuando alguien cuenta un problema de red. Los fallos de red que no son de HTTP (las conexiones de iroh y el mDNS de Pasar decks) usan en cambio crate::error::red_sin_http(motivo, Option<&dyn Error>): el mismo Network con motivo y, con un error de fondo, la misma cadena de causas en el registro y en detalle; sin él (un plazo, un rechazo del otro lado) solo se apunta el motivo. El código sigue siendo Network, así que los reintentos y los matches!(e.code, ErrorCode::Network | …) siguen funcionando. Ver Integración con Scryfall.
Los tipos compartidos derivan TS y llevan #[ts(export)]; algunos también #[serde(rename_all = "camelCase")]. Hay 295 ficheros generados en src/bindings/ (no se editan; la cabecera lo dice).
-
pnpm bindingsejecutacargo test --manifest-path src-tauri/Cargo.toml --workspace export_bindings. ts-rs genera un testexport_bindings_<Type>por cada tipo exportado, y al ejecutar los tests se escriben los ficheros. - El directorio de salida lo fija
.cargo/config.tomlen la raíz del repo:TS_RS_EXPORT_DIR = { value = "src/bindings", relative = true }. Tiene que estar en la raíz, no ensrc-tauri/.cargo/, porque Cargo lee la configuración del directorio desde el que se lanza, y los scripts de pnpm se lanzan desde la raíz. - Los campos
u64/i64usan#[ts(type = "number")](p. ej.ExportResult.bytes,SaveResult.updatedAt) para evitarbigint. - Los comentarios de doc de los tipos de Rust se copian a los ficheros
.ts(fíjate en el texto en español deAppError.ts). -
ErrorCodees una unión de cadenas;AppError.paramses{ [key in string]: string }. - El CI ejecuta
pnpm check(que incluyetsc --noEmit), así que un binding desactualizado que ya no coincide conCommandsrompe la compilación. Véase Tests y CI.
src-tauri/src/jobs.rs → Jobs (campo AppState.jobs):
| Método | Comportamiento |
|---|---|
alta() |
Reserva el siguiente id u32 (contador atómico) y un flag de cancelación Arc<AtomicBool>; devuelve ambos. |
cancelar(id) |
Activa el flag; false si el trabajo ya no existe. |
baja(id) |
Quita el trabajo; el trabajo la llama al terminar. |
Por separado, AppState::trabajo() devuelve una guarda TrabajoEnMarcha (RAII, contador trabajos); AppState::ocupado() es verdadero mientras viva alguna guarda. Sirve para que la GC de blobs y las instalaciones del actualizador nunca se ejecuten bajo un trabajo largo. Las guardas las mantienen resolve_decklist, export_pdf, identify_batch, design_drafts_for_deck, design_complete_lang, design_export_png, names_index_download, card_fetch, card_fetch_externa, deck_tokens_detect, mesa_empezar, mesa_retomar, red_unirse, red_volver, red_empezar, salas_deck (busca .trabajo() con grep). ocupado() también es verdadero mientras hay una partida abierta en la mesa (AppState::hay_partida()), porque una actualización reiniciaría la app en mitad de ella.
Forma canónica de un comando de trabajo (de resolve_decklist y export_pdf):
let (job_id, cancelado) = state.jobs.alta();- Lanzar (
tauri::async_runtime::spawnospawn_blocking) y mantenerlet _marca = estado.trabajo(); - Ejecutar, llamando a
app.emit("<name>://progress", XProgressEvent { job_id, … }), comprobandocancelado.load(...)entre pasos y devolviendoCancelled. -
estado.jobs.baja(job_id)y despuésapp.emit("<name>://done", XDoneEvent { job_id, result, error }). - Devolver
job_idal instante.
La cancelación es cooperativa: no se mata nada; el trabajador comprueba el flag. Tanto job_cancel como export_cancel llaman a jobs.cancelar.
| Evento | Payload (src/bindings/) |
Emitido por | Escuchado por |
|---|---|---|---|
resolve://progress / resolve://done
|
ResolveProgressEvent { jobId, progress } / ResolveDoneEvent { jobId, result, error }
|
resolve_decklist |
features/decklist/acciones.ts mediante lanzarTrabajo
|
export://progress / export://done
|
ExportProgressEvent { jobId, phase (prepare/compose/write), done, total } / ExportDoneEvent
|
export_pdf |
features/export/acciones.ts |
identify://progress / identify://done
|
IdentifyProgressEvent { jobId, done, total, blob, … } / IdentifyDoneEvent { jobId, error }
|
identify_batch |
features/variants/vincular-lote.tsx |
design://progress / design://done
|
DesignProgressEvent { jobId, done, total, … } / DesignDoneEvent { jobId, error }
|
design_drafts_for_deck (diseno/lote.rs) |
features/disenar/lote-dialog.tsx |
designlang://progress / designlang://done
|
El mismo DesignProgressEvent (con la card hecha) / DesignDoneEvent
|
design_complete_lang (diseno/idioma.rs) |
features/disenar/renumerar.ts (useTrabajo de tipo idioma) |
designpng://progress / designpng://done
|
DesignPngProgressEvent, DesignPngDoneEvent
|
design_export_png (diseno/png.rs) |
features/disenar/png-dialog.tsx |
mesa://progress / mesa://done
|
MesaProgressEvent { jobId, mazo, mazos, hechas, total } / MesaDoneEvent { jobId, error }
|
mesa_empezar, mesa_retomar, red_unirse, red_volver, red_empezar, salas_deck
|
features/mesa/conexion.ts (conProgreso, al que llaman también features/mesa/red/conexion-red.ts y features/salas/conexion-salas.ts) |
original://progress |
{ done, total } |
original_download |
features/disenar/marcos-originales.ts |
packs://progress |
{ id, done, total } |
pack_downloadable_download |
features/disenar/packs-descargables.ts |
names://progress |
{ fase, hecho, total } |
names_index_download |
features/settings/nombres-idiomas.tsx |
update://progress |
{ hecho, total } |
update_download |
stores/actualizacion-store.ts |
ciclo://pausa / ciclo://vuelta
|
ninguno |
ciclo.rs (solo Android): la app pasó a segundo plano / volvió |
lib/ciclo-de-vida.ts (guarda el deck y la carta que se diseña) / features/mesa/red/conexion-red.ts (la partida en red reconecta) |
pbdeck://abierto |
string (un content://) |
abrir.rs, cuando otra app abre un .pbdeck con esta |
features/library/abrir-pbdeck.ts |
pasar://receptor, pasar://emisor, pasar://cerca
|
EstadoReceptor, EstadoEmisor, EquipoCerca[]
|
pasar/ |
features/pasar/estado.ts |
pasar://imagenes-progress / pasar://imagenes-done
|
ImagenesProgreso / ImagenesFin
|
pasar_completar |
features/pasar/imagenes.ts |
salas://enlace |
string (el código) |
enlace.rs, cuando un enlace proxybuilder://unirse/… o tapete://unirse/… llega a la app ya abierta |
features/salas/enlaces-entrantes.ts |
original://, packs://, names:// y update://progress no tienen id de trabajo: pertenecen a un único comando que se espera (el propio invoke se resuelve cuando termina la descarga), no se pueden cancelar y se escuchan directamente con on().
Algunos eventos no están en Events y se escuchan con listen de @tauri-apps/api/event, con el tipo puesto en la llamada:
| Evento | Payload | Emitido por | Escuchado por |
|---|---|---|---|
red://aviso |
AvisoRed |
red/mesa.rs (avisos de la partida en red: sala, inicio, conexión, enlaces) |
features/mesa/red/conexion-red.ts |
salas://conexion, salas://playgroups, salas://sala, salas://enlaces, salas://host, salas://fuera
|
boolean, Playgroup[], EstadoSala, EnlaceSala[], HostSala, MotivoFuera
|
red/servidor/sala.rs |
features/salas/conexion-salas.ts |
mesa://originales |
OriginalMesa (uno por carta) |
mesa_originales (commands/mesa.rs) |
features/mesa/originales.ts |
La mesa de juego no usa eventos para su estado: mesa_suscribir recibe un tauri::ipc::Channel<MensajeMesa> creado en la pantalla (new Channel() en src/features/mesa/conexion.ts, con el tipo Channel<MensajeMesa> en Commands). Un canal es de un solo suscriptor y entrega en orden, que es lo que necesita una ristra de parches (un parche perdido o desordenado desincronizaría la vista). La sesión lo guarda y manda Vista { vista, registro } (con los últimos 500 eventos que siguen contando, Registro::vigentes(), REGISTRO_EN_VISTA en mesa/sesion.rs), Parche { parche, evento }, Cerrada o, en red, Rechazo { motivo } cuando el anfitrión no aceptó una intención de esta pantalla (llega después, no como respuesta de mesa_accion); un parche cuya versión no sigue a la anterior hace que la pantalla pida mesa_vista. Volver a suscribirse (tras cambiar de pestaña) sustituye el canal y empieza con la vista entera. Un espectador, que no tiene sesión propia, se suscribe a la vista que mira (AppState.mirada, red/mirada.rs). Véase Mesa de juego.
src/lib/trabajos.ts:
lanzarTrabajo(progreso, final, lanzar: () => Promise<number>, alProgreso)
: Promise<{ jobId: number; fin: Promise<DoneEvent> }>Invariantes:
-
Escuchar antes de lanzar. Un trabajo corto puede terminar antes de que
invokedevuelva el id; su evento final se perdería. Por eso se registran primeroon(progreso)yon(final). - Los eventos que llegan antes de conocer el id se almacenan en un búfer (
pendientesP,pendientesD) y se reproducen cuando llega el id; los posteriores se filtran porjobId. -
finse resuelve con el payload dedone(incluso cuando lleva un error); ambos listeners se quitan cuando eso ocurre. Silanzar()lanza una excepción, se quitan los listeners y se relanza el error.
Probado en src/lib/__tests__/trabajos.test.ts.
src/stores/trabajo-store.ts → useTrabajo guarda el resolver, el exportar, el trabajo de idioma de diseño o la preparación de la mesa en marcha (TipoTrabajo = "resolver" | "exportar" | "idioma" | "mesa"): { tipo, jobId, fase, hechas, total } con empezar, setId, avanzar, terminar. El pie del panel izquierdo y el botón de exportar lo leen. resolver() y exportarA() lanzan { code: "Busy", params: {} } si actual está definido: la regla de un solo trabajo a la vez se hace cumplir en el frontend (Rust solo cuenta los trabajos en marcha para ocupado(); resolve_decklist por sí mismo no rechaza una segunda llamada). cancelarTrabajo() en acciones.ts llama a export_cancel para las exportaciones y a job_cancel en los demás casos. El lote del diseñador, la exportación a PNG y la vinculación en bloque usan lanzarTrabajo desde sus propios diálogos sin useTrabajo.
Los diálogos vienen de @tauri-apps/plugin-dialog (capability dialog:default), envueltos solo por src/lib/dialogos.ts:
| Función | Comportamiento |
|---|---|
abrirParaImportar(opciones) |
open() empezando en la última carpeta de importar; recuerda la carpeta de la primera ruta elegida. Devuelve string[] | null. |
guardarParaExportar(opciones, nombre) |
save() empezando en la última carpeta de exportar con el nombre sugerido; recuerda la elección. |
elegirCarpetaParaExportar() |
Selector de carpeta para exportar imágenes sueltas. No en un móvil (Unsupported, lanzado en la pantalla): Android no tiene selector de carpetas. |
elegirDestinoDeImagenes({ titulo, nombre }) |
Dónde poner imágenes que van en carpetas por idioma: una carpeta en escritorio, un ZIP en un móvil. |
recordar(tipo, ruta) |
carpeta_recordar sin esperar respuesta; también se usa cuando se arrastran ficheros sobre la app. |
En Android los selectores del sistema devuelven una URI content:// en vez de una ruta, así que nada de esto se aplica allí: no se recuerda nada, defaultPath es solo el nombre propuesto, los filtros pasan a tipos MIME (filtrosParaAbrirEnMovil, filtrosParaGuardarEnMovil: Android no conoce .pbdeck ni .ttf, así que un filtro de abrir con una extensión desconocida se descarta y se puede elegir cualquier fichero, y un filtro de guardar sin tipo conocido cae en application/octet-stream, con el que Android respeta el nombre tal cual) y la pregunta es esMovil() de src/lib/plataforma.ts. Los caminos del móvil están en Rutas y URI content://.
Rust recuerda por separado las carpetas de importar y de exportar (commands/carpetas.rs, claves de app_settings carpeta_importar y carpeta_exportar) para que, por ejemplo, exportar un PDF no cambie dónde se abre «importar imágenes». carpeta_inicial devuelve solo el nombre sugerido si no hay nada guardado o la carpeta guardada ha desaparecido; si la consulta falla, inicial() recurre al nombre a secas. CLAUDE.md exige que todos los diálogos de ficheros pasen por este módulo; lo cubre src/lib/__tests__/dialogos.test.ts.
Las rutas vuelven a la pantalla como cadenas simples (en Android, URI) y van a los comandos de Rust (export_pdf, deck_export_package, font_import…). Rust vuelve a validar lo que abre (exportado() en export.rs, comprobación de .txt en deck_list_save), porque no se confía en que el WebView nombre ficheros arbitrarios. El fichero de capabilities no concede ningún permiso de fs.
En escritorio la pantalla le da a Rust una ruta y Rust la usa. En Android un diálogo devuelve en cambio una URI content://: un permiso que el sistema da a ese único fichero, sin carpeta, a menudo sin extensión en la URI y a veces con un proveedor que solo se puede leer como tubería. La regla es que todo comando que lee o escribe un fichero que elige la persona pasa por src-tauri/src/fichero.rs, nunca std::fs sobre una ruta de diálogo, y que en escritorio el resultado es exactamente el de antes (una ruta es una ruta). El formato y el nombre llegan como parámetro o del nombre que da Android (DISPLAY_NAME), nunca de la URI.
| Función | Qué hace |
|---|---|
es_uri(ruta) |
true para una cadena que empieza por content://. |
leer(app, ruta, nombre?) |
Pone el fichero al alcance de std::fs: una ruta tal cual; una URI copiada a la caché (app_cache_dir/fichero/<uuid>/), porque el .pbdeck es un ZIP y necesita Seek, que la tubería de un proveedor en la nube no da. La Copia devuelta borra la carpeta al soltarse. |
leer_bytes(app, ruta, tope) |
El contenido entero; una URI por encima de tope bytes es InvalidInput con motivo: fichero_grande. |
escribir(app, ruta, nombre?, f) |
Da a f una ruta local donde escribir: la real, o un fichero temporal de la caché que luego se copia a la URI. Una URI no se puede renombrar, así que escribir_atomico (el .part y renombrar) no existe allí. |
escribir_bytes, copiar_a
|
Escribir bytes / copiar un fichero local a una ruta (a trozos, a un nombre provisional, y luego renombrado) o a una URI. |
temporal(app, nombre) |
Una ruta vacía nueva en la caché, que se borra al soltarse (Copia::conservar la mantiene). Sirve para exportar y para compartir. |
para_compartir(app, ruta) |
Deja el fichero donde el FileProvider del proyecto de Android lo ve (la caché) y devuelve la ruta y el tipo MIME (mime_de); la copia se queda hasta el siguiente barrido. Solo acepta una URI o un fichero que ya esté dentro de la caché (si no, compartir_fuera). |
zip_de_carpeta, zip_de_lista
|
Empaquetan ficheros en un ZIP (imágenes, PDF y ZIP se guardan tal cual; el resto, con deflate). |
nombre_seguro, nombre_destino
|
Un nombre que se puede crear en cualquier carpeta (sin separadores ni caracteres de control, no vacío, 120 caracteres) / el nombre que tendrá lo que se escriba, el de Android para una URI. |
Por qué una copia a la caché y no leer en flujo: los importadores (.pbdeck, packs ZIP, letras) quieren un fichero con Seek, y una copia reduce los tres tipos de origen (ruta, URI local, URI en la nube) a uno. Por qué Acceso: la lógica corre contra el trait Acceso { abrir, nombre }, implementado sobre tauri-plugin-fs en la app (DeLaApp, que abre una URI con app.fs().open(..) y obtiene un std::fs::File de verdad del descriptor que da el sistema) y sobre ficheros locales en los tests. tauri-plugin-fs se usa solo desde Rust: al WebView no se le concede ningún permiso fs:. La carpeta de la caché se barre al usarla (lo que lleve más de 24 horas en fichero/ se borra).
Abrir un fichero desde fuera (otra app, ACTION_VIEW) es src-tauri/src/abrir.rs: el plugin de Android entrega una URI, que se guarda (PENDIENTE) y se anuncia con pbdeck://abierto; la pantalla pide pbdeck_pendiente una vez si aún no escuchaba. Solo valen cadenas content:// de 2048 caracteres como máximo (valida). Rust no importa nada en ese momento.
- Escribe
#[tauri::command] pub fn|async fn name(state: State<'_, AppState>, …) -> AppResult<T>ensrc-tauri/src/commands/<área>.rs; pon la lógica real en una función que no sea comando para que los tests puedan llamarla con unAppStateconstruido sobre directorios detempfile. - Regístralo en
generate_handler!ensrc-tauri/src/lib.rs. - Añade su entrada a
Commandsensrc/lib/ipc.ts(nombres de argumentos en camelCase). - Deriva
Serialize, TScon#[ts(export)]en cualquier tipo de payload nuevo; ejecutapnpm bindings. - Devuelve los errores como
AppErrorcon unErrorCodeexistente; añade un código solo si la pantalla necesita una frase distinta (entonces añade su texto aTEXTOSy asrc/i18n/en/). - Para un trabajo largo: sigue la forma de cinco pasos de arriba, añade ambos eventos a
Eventsy llama alanzarTrabajodesde la pantalla. - Ejecuta
pnpm check,pnpm check:clippy,pnpm test:rust.
Use and build · Uso y desarrollo
- User-Guide · Guía de usuario
- Development-Setup · Preparación del entorno de desarrollo
- Testing-and-CI · Tests y CI
- Releases-and-Auto-Updates · Versiones y actualizaciones automáticas
- Conventions · Convenciones
Architecture · Arquitectura
- Architecture · Arquitectura
- IPC-and-Error-Handling · IPC y manejo de errores
- Rust-App-Layer · Capa de aplicación Rust
Data · Datos
- Deck-Model · Modelo del mazo
- Storage-and-Blobs · Almacenamiento y blobs
- Deck-Package-Format · Formato del paquete de mazo
- Deck-Library · Biblioteca de mazos
Getting cards · Conseguir cartas
- Decklist-Parsing · Parseo de la lista del mazo
- Scryfall-Integration · Integración con Scryfall
- External-Sources · Fuentes externas
- Card-Identification-and-OCR · Identificación de cartas y OCR
Layout, images, PDF · Maquetación, imagen y PDF
- Layout-Engine · Motor de maquetación
- Imaging-and-PDF · Imagen y PDF
- Export · Exportación
- Live-Preview · Vista previa en vivo
- Statistics · Estadísticas
Card designer · Diseñador de cartas
- Designer-Overview · Visión general del diseñador
- Designer-Engine · Motor del diseñador
- Designer-Composition · Composición del diseñador
- Frame-Packs · Packs de marcos
- Designer-App-Layer · Capa de aplicación del diseñador
- Designer-UI · Interfaz del diseñador
- Card-Editor-Dialog · Diálogo Editar carta
Game table · Mesa de juego
Mobile · Móvil
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- Icons · Iconos
- Settings-and-Shell · Ajustes y shell
- Tutorials · Tutoriales
- Decklist-Panel · Panel de la lista del mazo
- Add-Card-and-Variants · Añadir carta y variantes
- Card-Search · Búsqueda de cartas
- Set-Browser · Explorador de sets
Reference · Referencia