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,
// ... 177 entries
])Cada área es un módulo en src-tauri/src/commands/ (mod.rs los lista): actualizar, cards, carpetas, decklist, diseno, export, fuentes, identify, imagen, layout, library, mesa, 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. 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. |
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. |
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 y cómo se instaló la app. |
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). |
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[]. |
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. |
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). |
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 (varios ficheros si el mazo se divide). Eventos export://progress, export://done. |
export_estimate |
Tamaño aproximado del PDF (bytes, images, pages). |
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. |
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. |
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. |
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_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. |
Enlaces de invitación (src-tauri/src/enlace.rs)
| Comando | Para qué |
|---|---|
enlace_pendiente |
El código del enlace proxybuilder://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 |
scryfall/client.rs (red(), 5xx tras los reintentos, parámetro estado), diseno/letras_libres.rs, diseno/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
|
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) |
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 |
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 |
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, update_install, red_crear, red_unirse, 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: las frases en español de src/lib/errores.ts no contienen {placeholders}, así que estos parámetros hoy no se muestran al usuario; son para registros, tests y textos futuros.
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;Unsupportedtieneversion;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 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_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_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 |
salas://enlace |
string (el código) |
enlace.rs, cuando un enlace proxybuilder://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. |
recordar(tipo, ruta) |
carpeta_recordar sin esperar respuesta; también se usa cuando se arrastran ficheros sobre la app. |
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 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.
- 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
Frontend · Frontend
- Frontend-Architecture · Arquitectura del frontend
- State-Stores · Stores de estado
- Internationalization · Internacionalización
- Themes-and-Skins · Temas y skins
- 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
- Set-Browser · Explorador de sets
Reference · Referencia