Skip to content

Ajustes y shell

Pablo Martínez López edited this page Oct 27, 2026 · 14 revisions

Ajustes y shell

English · Español

El shell es el marco de la app: la barra superior (selector de vista, nombre del mazo, indicador de guardado, botón de actualización, botón de ayuda, botón de Ajustes), los tres paneles redimensionables de la vista de mazo y los hooks de arranque que abren el último mazo y arrancan el autoguardado. El diálogo de Ajustes contiene el idioma de la interfaz, las actualizaciones dentro de la app, el índice opcional de nombres multilingües y todo el sistema de apariencia. Esta página documenta ambos, qué hace cada ajuste y dónde se persiste.

Código: src/features/shell/ y src/features/settings/.

Shell (src/features/shell/)

Fichero Responsabilidad
app-shell.tsx AppShell: cabecera + área principal; NombreDeck (input en línea del nombre del mazo); monta <Tutorial /> y llama a useTutorialInicial() (Tutoriales); monta <ConfirmarCierre />; cambiarVista pregunta antes de salir de Diseñar con la carta sin guardar (más abajo); useEnlacesEntrantes (src/features/salas/enlaces-entrantes.ts) pasa a Jugar cuando llega y se acepta un enlace de invitación proxybuilder://unirse/<código> (con una partida en marcha se rechaza con un toast; Juego en línea).
scroll-por-vista.ts useScrollPorVista(vista, raiz): recuerda el scroll de cada vista y lo devuelve al volver a ella (más abajo).
confirmar-cierre.tsx ConfirmarCierre: la guarda que pregunta antes de cerrar la ventana cuando se perdería algo (más abajo).
use-deck-inicial.ts useDeckInicial(): arranque del mazo, el autoguardado, el plan y las teclas de deshacer/rehacer.
save-indicator.tsx SaveIndicator: «Guardado / Guardando… / Sin guardar / Error al guardar».
actualizacion-automatica.tsx Bucle de actualización automática, toasts de actualización, BotonActualizar, PuntoActualizacion, useAvisoAjustes.

Barra superior (cabecera de AppShell, 48 px de alto)

De izquierda a derecha:

  1. Logo y título («Proxy Builder», subtítulo «Scryfall · listo para imprimir»).
  2. Un ToggleGroup de selección única ligado a useUi().vista: Deck, Diseñar, Biblioteca, Jugar (icono Swords, la mesa de juego; lleva una pequeña etiqueta «Beta», t("Beta") en shell.json, mientras la mesa siga en beta). onValueChange solo acepta estos cuatro valores (pulsar el elemento activo devuelve un valor vacío que se ignora, así que siempre hay uno seleccionado).
  3. NombreDeck (oculto en las vistas Biblioteca y Jugar): un Input que edita deck.name. Confirma al perder el foco o con Enter, Escape revierte; un valor vacío revierte; los cambios pasan por useDeck.setSinHistorial({ name }) (se guarda, no se puede deshacer).
  4. A la derecha: SaveIndicator, BotonActualizar, el botón de ayuda (botón de icono CircleHelp, con tooltip y aria-label «Tutorial de esta pantalla»; llama a useUi().abrirTutorial(vista) con la pantalla actual, véase Tutoriales) y el botón de Ajustes.

Varios de estos elementos llevan un atributo data-tutorial (pestanas, nombre-deck, ayuda, ajustes): son las anclas que rodea el recorrido del deck.

SaveIndicator lee useDeck().guardado / errorGuardado; no renderiza nada hasta que hay un mazo abierto y, si hay error, envuelve la etiqueta en un tooltip con mensaje(error).

Área principal

vista === "deck": ResizablePanelGroup horizontal con izquierda (lista, 22 % por defecto, mín. 260 px), centro (vista previa, mín. 360 px), derecha (exportar, 22 % por defecto, mín. 260 px) y dos asas con agarraderas. Los tamaños se persisten en localStorage["proxybuilder.paneles"] mediante useDefaultLayout. Las demás vistas son un simple cambio según vista: disenar, mesa y, para cualquier otro valor, biblioteca pintan la pantalla que les pasa App. mesa es MesaScreen (src/features/mesa/mesa-screen.tsx, Mesa de juego): pregunta una vez a Rust con mesa_estado y, hasta saberlo, no pinta nada; sin partida abierta enseña la entrada EntradaMesa y, si la hay, la mesa. Salir de la pestaña deja la partida abierta en Rust y la guarda (guardar() al desmontarse); al volver se retoma tal cual.

La entrada (src/features/mesa/entrada.tsx) tiene la lista de decks de la biblioteca (buscador, «Historial» de las partidas terminadas, el deck abierto más recientemente ya elegido), la tarjeta «Partida a medias» cuando hay una partida guardada («Seguir la partida» / «Descartar»), el nombre del jugador, el formato, la lista para jugar (obligatoria en Commander si el deck pasa de 100 cartas), las vidas, el mulligan, las ayudas del turno, la intensidad del sparring y «Tapete de este deck…», y cuatro formas de empezar: «Solitario» (pregunta antes si sustituiría la partida guardada), «Con amigos…» (servidor de salas), «Partida local…» y «En red…» («Con amigos…» y «En red…» pasan a «Volver a la sala» mientras haya una sala suya abierta). La ventana tiene un mínimo de 960 x 640 y un tamaño por defecto de 1440 x 900 (src-tauri/tauri.conf.json).

Scroll por vista (scroll-por-vista.ts)

Cambiar de pestaña desmonta la vista anterior, así que antes se perdía su posición de scroll. AppShell llama a useScrollPorVista(vista, principal) con una referencia a <main>:

  • Apuntar. Un único listener de scroll en fase de captura sobre <main> (scroll no sube por el DOM) guarda el scrollTop de cada viewport de ScrollArea ([data-slot="scroll-area-viewport"]) en un Map<Vista, Map<índice, top>> dentro de una referencia. La clave de cada zona de scroll es su orden entre los viewports de <main>; los diálogos van por portal fuera de <main>, así que no cuentan.
  • Reponer. Al activarse una vista, un bucle de requestAnimationFrame pone cada scrollTop guardado en cuanto ese viewport es lo bastante alto (scrollHeight - clientHeight >= top), durante un máximo de 2 s (ESPERA_MS). Las listas y la vista previa se llenan después del primer pintado, así que ponerlo una sola vez al montar se quedaría en 0. Una wheel, un pointerdown o un keydown dentro de <main> detiene el bucle, para no pelearse nunca con el usuario.
  • Por qué no se mantienen montadas y ocultas. display: none también reinicia el scroll, y la vista previa y el diseñador seguirían trabajando por debajo (Vista previa en vivo, Interfaz del diseñador).
  • Límites. Solo en memoria (se pierde al cerrar la app). Como la clave es el orden, una zona de scroll que aparece o desaparece entre visitas (por ejemplo el formulario de Diseñar según haya o no carta elegida) puede cambiar qué posición recibe cada una.

Salir de «Diseñar» con la carta sin guardar

AppShell pasa el cambio de pestaña por cambiarVista(v). Si la vista actual es disenar y useEdicion.getState().pendiente está puesto (la carta que se diseña tiene cambios sin guardar), el movimiento se aparca en salirDeDisenar y un segundo Confirmar («¿Descartar los cambios?» / «Hay cambios sin guardar en esta carta.») pregunta con tres salidas: Cancelar, Descartar (destructiva, ejecuta el movimiento aparcado) y Guardar y continuar (guarda con pendiente.guardar() y solo se mueve si devolvió true). Por qué existe: DisenarScreen se desmonta al mostrar otra vista, así que antes de esta guarda el borrador se perdía en silencio. Por qué el shell no sabe cómo se guarda una carta: DisenarScreen registra un callback guardar en useEdicion mientras tiene cambios (Stores de estado, Interfaz del diseñador) y el shell solo lo llama.

Cerrar la app (confirmar-cierre.tsx)

ConfirmarCierre registra una sola vez getCurrentWindow().onCloseRequested(...). Flujo de una petición de cierre:

flowchart TD
    A["Petición de cierre"] --> B{"¿Diálogo ya abierto?"}
    B -->|"sí"| C["preventDefault, nada más"]
    B -->|"no"| D["guardarAhora(): vuelca el mazo"]
    D --> E["pendientes(deckSinGuardar) en lib/cierre.ts"]
    E --> F{"¿hayPendientes?"}
    F -->|"no"| G["return sin preventDefault: el envoltorio de Tauri llama a destroy(), la ventana se cierra"]
    F -->|"sí"| H["preventDefault + Confirmar: ¿Cerrar Proxy Builder?"]
    H --> I["Cancelar: se queda, vuelve a preguntar en el siguiente intento"]
    H --> J["Cerrar igualmente: cancelarTrabajo si hay uno, guarda el mazo, destroy()"]
    H --> K["Guardar y cerrar (solo con la carta sin guardar): useEdicion.pendiente.guardar() y cierra; si guardar falla, el diálogo y la ventana se quedan"]
Loading

Decisiones e invariantes:

  • Primero se vuelca el mazo. El autoguardado espera 800 ms tras el último cambio (Stores de estado), así que un cambio hecho justo antes de cerrar se perdía. guardarAhora() corre en cada petición de cierre; si lanza, «No se ha podido guardar el deck: se perderán sus últimos cambios.» pasa a ser una razón más para preguntar (pendientes(true)).
  • Razones (src/lib/cierre.ts, pendientes() / hayPendientes(); cada una es un campo de Pendientes y el diálogo lista solo las que se cumplen): un trabajo largo (useTrabajo.actual, con el texto de su fase: resolver, exportar, idioma), una actualización bajando o instalando (useActualizacion.paso es bajando o instalando; una actualización solo disponible o ya lista no cuenta), un diálogo abierto con un proceso en marcha o texto sin guardar (lib/dialogos-abiertos.ts, más abajo), la carta que se diseña con cambios sin guardar (useEdicion.pendiente), el mazo que no se pudo guardar y una partida abierta en la mesa (useMesa.activa, motivo «Hay una partida en la mesa: se guarda y podrás seguirla al volver.»).
  • La partida se guarda en cada petición de cierre. Justo después de guardar el mazo, el manejador llama a guardar() de src/features/mesa/conexion.ts (mesa_guardar), y «Cerrar igualmente» lo vuelve a llamar antes de destroy(). Una partida abierta sigue contando como razón para que quien juega sepa que la tendrá esperando en «Jugar» (Mesa de juego).
  • Sin ninguna razón no sale diálogo. El manejador vuelve sin preventDefault() y la ventana se cierra como siempre.
  • «Cerrar igualmente» es destructiva: cancela el trabajo en marcha con cancelarTrabajo() (export_cancel o job_cancel), intenta guardar el mazo una vez más y llama a destroy(). Las exportaciones escriben de forma atómica, así que cerrar a mitad de una exportación nunca deja un PDF a medias con su nombre final. Si destroy() falla, un toast dice «No se ha podido cerrar la ventana.».
  • Un segundo intento de cierre con el diálogo abierto se ignora (ref abierto): se le hace preventDefault y no se apila otro diálogo.
  • Fuera de Tauri (la vista abierta en un navegador durante el desarrollo) getCurrentWindow() lanza y se captura: no hay ventana que guardar.
  • 🛑 destroy() necesita un permiso. Con un escuchante de JavaScript en onCloseRequested, Tauri ya no cierra la ventana por su cuenta; lo hace destroy() (el envoltorio o el manejador). Ese comando es core:window:allow-destroy, que no forma parte de core:default ni de core:window:default, así que src-tauri/capabilities/default.json lo lista expresamente (véase Capa de aplicación Rust). Sin él la ventana no se podría cerrar; la compilación valida el identificador.
  • Esta guarda no bloquea al actualizador. «Reiniciar y actualizar» guarda el mazo por su cuenta (guardarAhora()) y reinicia desde Rust (request_restart), que no pasa por la petición de cierre de la ventana. Deducido: del comportamiento de Tauri; no se ha probado de extremo a extremo.
  • No cubierto. Deducido: en macOS, Cmd+Q o «Salir» desde el Dock puede que no emitan una petición de cierre de ventana, y entonces la guarda no correría.

Diálogos que declaran su estado (src/lib/dialogos-abiertos.ts)

Algunos diálogos tienen un proceso largo o un estado sin guardar propio. Se lo dicen a la guarda de cierre con useEstadoParaCerrar({ enCurso, sinGuardar }), llamado con los mismos valores que el componente ya pasa a DialogContent como ocupado / cambiosSinGuardar. El hook guarda los últimos valores en un ref y registra un lector en un Set del módulo (registrarDialogo, que devuelve la función que lo quita); estadoDeLosDialogos() junta con OR todos los lectores. Como quien registra es el Contenido del diálogo, que solo existe mientras está abierto, el registro vive justo lo que el diálogo.

Por qué un hook por diálogo y no dentro de DialogContent: src/components/ui/ es de solo lectura (heredado del sistema de diseño en que se basa la app), así que dialog.tsx no se toca.

Diálogo Fichero enCurso sinGuardar
Diseñar el deck src/features/disenar/lote-dialog.tsx en marcha no
Exportar imágenes src/features/disenar/png-dialog.tsx en marcha no
Vincular en bloque src/features/variants/vincular-lote.tsx trabajando revisando filas (fase === "revisar" con filas)
Colecciones src/features/disenar/colecciones-dialog.tsx no cambiada

A los diálogos sin el hook no se les pregunta. Deducido: solo tienen la regla del texto escrito de DialogContent para cerrarse ellos, no para cerrar la app.

Tests: src/lib/__tests__/cierre.test.ts (Tests y CI).

Arranque (useDeckInicial, llamado desde App)

  1. iniciarAutoguardado() e iniciarPlan() (véase Stores de estado).
  2. Un useDeck.subscribe que, cada vez que cambia el id del mazo abierto, llama a rehacerMiniaturas() (src/features/disenar/renumerar.ts) para volver a renderizar las miniaturas de diseño que han quedado obsoletas por un cambio de colección, de letra o de motor.
  3. abrirDeckInicial(t("Deck sin título")): reabre last_deck_id o crea un mazo nuevo. El error devuelto (si lo hay) se guarda en el estado del hook.
  4. Por separado, un listener de keydown para Ctrl/Cmd+Z (deshacer) y su variante con Shift (rehacer), desactivado mientras se escribe en un input, textarea o contenteditable.

Arranque de la ventana

Decisión: la ventana se crea oculta con un color de fondo oscuro y se muestra lo antes posible, para evitar un destello blanco.

  • tauri.conf.json: "visible": false, "backgroundColor": "#1d1b19".
  • src-tauri/src/lib.rs: el plugin window-state guarda tamaño/posición/estado maximizado de todos los flags salvo VISIBLE (si no, el estado guardado podría mostrar la ventana antes de que se haya pintado). on_page_load muestra la ventana en PageLoadEvent::Finished; un hilo en segundo plano la muestra tras 3 s si sigue oculta (la página nunca cargó).
  • src/main.tsx también llama a getCurrentWindow().show() justo después de render (no espera a un fotograma: WebKitGTK no entrega requestAnimationFrame a las ventanas ocultas), y ponerTemaAntesDeMontar() ya había puesto los colores correctos, así que no hay destello claro. Véanse Arquitectura del frontend y Temas y skins.

Diálogo de Ajustes (src/features/settings/settings-dialog.tsx)

Lo abre el botón de Ajustes (AppShell guarda open en estado local). DialogContent es sm:max-w-3xl con cambiosSinGuardar={false} (no hay nada que perder al cerrar). La cabecera muestra la versión leída una vez con getVersion() de @tauri-apps/api/app («Proxy Builder v2.5.3») y, cuando hay una actualización descargada, el botón ReiniciarParaActualizar.

Cuerpo, de arriba abajo:

Sección Componente Efecto Persistencia
Idioma SelectorIdioma (src/components/selector-idioma.tsx): un Select con la bandera y el nombre propio de cada idioma, sobre los 15 idiomas de IDIOMAS (src/lib/idiomas.ts) Idioma de la interfaz, sin recarga. Primero se carga el diccionario nuevo y el cambio llega cuando está, así que la pantalla nunca enseña textos a medio traducir. localStorage["proxybuilder.lang"]; véase Internacionalización.
Actualizaciones Actualizaciones (actualizaciones.tsx) Buscar ahora, descargar, reiniciar; el interruptor de actualización automática. Interruptor: useUi().actualizarSola en proxybuilder.preferencias. La máquina de estados, en memoria.
Nombres en otros idiomas NombresIdiomas (nombres-idiomas.tsx) Descarga opcional del índice de nombres de MTGJSON. En disco, por Rust (véase abajo).
Apariencia Appearance (appearance.tsx) Modo, escala, scroll suave, skin, filigrana, fondo, material, efectos, tema de color, «Más temas…». Claves de localStorage listadas en Temas y skins.
Mesa de juego (cerrada al principio) AjustesMesa (mesa.tsx) Seis grupos, más abajo. useUi().mesa en proxybuilder.preferencias, salvo el aspecto de los dados (Rust) y la identidad en red (Rust); véase Mesa de juego.

Sección de actualizaciones (Actualizaciones)

  • Llama a useActualizacion.iniciar() al montar. Si soportada === false muestra «Solo en la app instalada: esta copia no viene de un instalador (por ejemplo, en desarrollo).» y nada más.
  • «Buscar ahora» (buscar) está desactivado mientras está ocupado; si se encuentra una versión y la actualización automática está activada, la descarga al instante (como al arrancar).
  • Cuando hay una versión disponible y la automática está desactivada: un botón «Descargar la {version}». Cuando está descargada (lista/instalando): botón «Reiniciar y actualizar» (descarta el toast "actualizacion", llama a instalar() y muestra el error en un toast si falla).
  • Aparece una barra Progress durante la descarga; el último error se imprime con mensaje(error); las notas de la release (texto Markdown, en inglés) aparecen en un ScrollArea cuando existen; «Tienes la última versión ({version}).» cuando está al día.
  • La línea de estado dice que, en las instalaciones .deb/.rpm, el sistema pedirá la contraseña al reiniciar (ActualizacionInfo.pidePermiso).

Los detalles de la máquina de estados están en Stores de estado; el actualizador de Rust, las comprobaciones de firma y el formato de las releases, en Versiones y actualizaciones automáticas. La instalación se rechaza con Busy mientras corre un trabajo largo (el mazo se guarda antes y el paso vuelve a lista). Con una partida abierta en la mesa la pantalla ni lo pide: la instalación queda en cola (alSalirDeLaMesa, toast «Se instalará al salir de la mesa.») y se hace al salir de la mesa; Rust la sigue rechazando con Busy motivo: partida como red de seguridad. Buscar y bajar no se bloquean.

El mismo SelectorIdioma elige el idioma de diseño en el panel de exportar y en la vista previa de Diseñar (src/features/export/export-panel.tsx, src/features/disenar/vista-previa.tsx): los idiomas de la interfaz, del diseño y del deck comparten los códigos de Scryfall de src/lib/idiomas.ts, así que un solo componente sirve para los tres. Las banderas son imágenes de Noto Color Emoji (Bandera en src/components/emoji.tsx), no la letra de emoji del sistema, porque la Segoe UI Emoji de Windows no tiene banderas; véase Arquitectura del frontend.

Nombres en otros idiomas (NombresIdiomas)

Descarga opcional (aproximadamente del tamaño que muestra el botón, «Descargar (unos {mb} MB)») del índice de nombres por idioma de MTGJSON, guardado como SQLite por Rust, de modo que las cartas escritas por su nombre impreso en español (u otro idioma) que Scryfall no tiene se puedan encontrar en local, tanto en la lista como al vincular. Sin él, la app pregunta en línea a magicthegathering.io, que es más lento. Comandos: names_index_info, names_index_download (evento de progreso names://progress con fases; durante la fase de «lectura» la barra está llena y el texto dice «Preparando el índice…»), names_index_remove. Muestra el número de nombres descargados. Véanse Fuentes externas e Identificación de cartas y OCR.

Sección de apariencia

Documentada control por control en Temas y skins (modo, tamaño de 70 a 150 %, scroll suave, Plano/Zeus/Hera, filigrana, fondo, material, desenfoque/grano/sombra, «Incluye los paneles», cuadrícula de temas, «Más temas…»).

Scroll suave (proxybuilder.smoothScroll, activado por defecto) monta SmoothScroll (src/lib/SmoothScroll.ts) mediante SmoothScrollClient: la rueda anima el scrollTop nativo del ancestro con scroll más cercano. Al desactivarlo se destruye la instancia.

Sección de la mesa de juego (AjustesMesa)

Cada control escribe un campo de useUi().mesa (PreferenciasMesa en src/stores/ui-store.ts, valores de fábrica en MESA_DE_FABRICA) con setMesa, salvo que se diga otra cosa. Al leerlas, las preferencias guardadas se funden sobre MESA_DE_FABRICA, así que un campo añadido después toma su valor de fábrica.

Grupo Controles (campo)
«Jugar» Ofrecer el bono de auras y equipos al adjuntarlos (ofrecerBono, sí); ver la vida del fantasma en solitario (fantasma, sí); en red, ver cómo mueven los demás y mostrarles mi cursor (movimientoEnVivo, sí); en partida local, pasar a mirar al jugador del turno (seguirTurnoLocal, no); intensidad del sparring al empezar en solitario (sparring, apagado; la entrada tiene el mismo selector); mantener pulsado abre el menú de la carta (clicSostenido, no).
«Tablero» Tamaño de las cartas del campo (tamanoCartas, de 25 a 200 %, 100); tamaño de la vista grande (vistaGrande, de 200 a 500 px, 320); panel de estado junto a la vista grande (panelEstado, sí).
«Dados y monedas» (aspecto-dados.tsx, AjustesDados) El aspecto de tus dados y tu moneda, con una muestra en CSS 3D que se tira con «Probar» (con sonido): color del dado (el del jugador o uno propio), color de los números, acabado (liso, nacarado, marmol, purpurina, translucido), una imagen propia en cada cara (sustituye al color y al acabado) y el metal de la moneda (oro, plata, cobre). No está en useUi: useAspectoDados (src/features/mesa/dados/aspecto.ts) lo lee y lo escribe con mesa_dados_get, mesa_dados_set y mesa_dados_imagen; Rust lo guarda en app_settings, clave mesa.dados, y la imagen en el almacén de blobs (src-tauri/src/mesa/dados.rs). Los cambios se mandan 250 ms después del último.
«Sonidos» Un interruptor por grupo de sonidos: dados y monedas (sonidoDados), cartas: barajar, robar, girar, jugar (sonidoCartas), pasar el turno y cuando es el tuyo (sonidoTurno), mensajes y fantasma del sparring (sonidoChat), todos encendidos; un volumen para todos (volumen, de 0 a 100 %, 60).
«Identidad en red» (identidad-red.tsx) Exportar la identidad en red de la instalación a un fichero JSON o importar otra (pregunta antes; la de ahora se queda como copia en la carpeta de datos de la app). Lo hace Rust (red_identidad_exportar, red_identidad_importar); importar se rechaza con Busy mientras haya una partida o una sala abiertas. No es una preferencia.
«Rendimiento» Calidad de las imágenes del campo (calidad: 146, 244 o 488 px de ancho, 244); dados y monedas (dados: auto, 3d con física, css 3D ligero, 2d; auto); reducir animaciones (reducirAnimaciones, no); sombras (sombras, sí); contador de fotogramas (fps, no); «Modo rendimiento (todo al mínimo)» aplica MODO_RENDIMIENTO (src/features/mesa/fps.tsx: calidad 146, sin sombras, animaciones reducidas, sin panel de estado).

PreferenciasMesa guarda además campos sin control aquí: sparringVisto (ya salió la tarjeta de «Cómo funciona» del sparring), trackers (los contadores de jugador que se ven junto a la vida) y disposicion (el reparto de los tableros, recordado por número de jugadores), que se cambian desde la propia mesa.

Botón, puntito y toasts de actualización (actualizacion-automatica.tsx)

Pieza Comportamiento
useActualizacionAutomatica() Solo cuando useUi().actualizarSola es verdadero (por defecto). Tras 8 s (PRIMERA_MS) y después cada 6 h (CADA_MS): iniciar(); si no está soportada, se detiene; buscar(); si existe una versión, bajar(). Los fallos de red o la ausencia de release son silenciosos. Los temporizadores se limpian al desactivar el interruptor.
useAvisosActualizacion() Un único useActualizacion.subscribe, montado una vez en App: un toast de carga «Descargando Proxy Builder {version}…» con el porcentaje como descripción mientras bajando; un toast de éxito persistente «Proxy Builder {version} está lista» con una acción Reiniciar cuando termina la descarga (una vez por versión); un toast de error «No se ha podido descargar la actualización.» con el error traducido si falla la descarga. Todos usan el id de toast "actualizacion" para sustituirse entre sí.
BotonActualizar Botón outline de la barra superior «Actualizar a {version}», visible solo en lista/instalando; spinner mientras se instala; el tooltip dice si el sistema pedirá una contraseña.
PuntoActualizacion Puntito sobre el botón de Ajustes: ámbar y pulsante mientras bajando; del color primario del tema en lista/instalando.
useAvisoAjustes() Texto de tooltip/aria para el botón de Ajustes mientras hay una actualización en curso («Descargando la {version}…» / «La {version} está lista: reinicia desde Ajustes.»).

Los textos de los toasts leen el idioma actual a través de una ref (textos.current = lang), así que un cambio de idioma durante la descarga se respeta sin volver a suscribirse.

Dónde vive cada preferencia (mapa de ajustes)

Ajuste Ubicación en la UI Guardado en
Idioma de la interfaz Ajustes localStorage proxybuilder.lang
Tema, modo, skin, fondo, material, desenfoque, grano, sombra, escala, flags, scroll suave Ajustes > Apariencia localStorage proxybuilder.theme / .mode / .skin / .bg / .material / .blur / .noise / .shadow / .scale / .fxSidebar / .heraFiligree / .smoothScroll
Temas de tweakcn instalados Ajustes > Más temas Ficheros de Rust en la carpeta de datos de la app temas/, con espejo en proxybuilder.temasInstalados
Actualización automática Ajustes > Actualizaciones useUi().actualizarSola (proxybuilder.preferencias)
Ajustes de la mesa de juego Ajustes > Mesa de juego useUi().mesa (proxybuilder.preferencias)
Aspecto de los dados y la moneda Ajustes > Mesa de juego > «Dados y monedas» app_settings de Rust, clave mesa.dados (la imagen propia, en el almacén de blobs)
Nombre del jugador en la mesa Entrada de la pestaña Jugar localStorage proxybuilder.mesa.nombre
Tapete de la mesa (por deck y general) Pestaña Jugar: «Tapete de este deck…» y la barra de la mesa app_settings de Rust, claves mesa.tapete.<id del deck> y mesa.tapete (blob protegido en blob_refs)
Pantallas cuyo tutorial ya salió automático (primera visita) useUi().tutorialesVistos (proxybuilder.preferencias)
Índice de nombres de MTGJSON Ajustes Rust (fichero descargado, índice SQLite)
Idioma de las cartas, política de imagen, añadir fichas, calidad de la vista previa, preguntar antes de sustituir Panel de la lista > Opciones useUi() (proxybuilder.preferencias)
Zoom de la vista previa, superposición de info de carta Barra de herramientas de la vista previa useUi() (proxybuilder.preferencias); «Ver reversos» no se persiste
Anchos de los paneles arrastrar las asas localStorage proxybuilder.paneles, .panel-lista, .paneles.disenar
Sección abierta/cerrada cabeceras de sección localStorage proxybuilder.seccion.<id>
Vista, orden, agrupación y tamaño de la biblioteca Barra de herramientas de la biblioteca localStorage proxybuilder.biblioteca (los filtros no se guardan)
Último mazo abierto automático Rust, clave de app_settings last_deck_id
Última carpeta de importar / última carpeta de exportar automático Rust, claves de app_settings carpeta_importar, carpeta_exportar
Tamaño, posición y maximizado de la ventana automático tauri-plugin-window-state (la visibilidad, excluida)
Ajustes de impresión (tamaño de página, tamaño de carta, márgenes, sangrado, guías de corte, etc.) y presets de exportación Panel de exportar Dentro del mazo (Deck.settings, se guarda con el mazo); presets en Rust (comandos preset_*). Véase Exportación.

Valores de fábrica que conviene conocer (son decisiones de producto, no accidentes): idioma de la interfaz inglés; tema Amber Minimal en modo oscuro con material mate; zoom de la vista previa 80 %; calidad de la vista previa 488 px; «preguntar antes de sustituir cartas» activado; actualización automática activada. Los valores por defecto de impresión (2 mm de separación, sangrado 0, guías de corte desactivadas, «Rellenar las esquinas» desactivado) viven en el código de layout/exportación, véase Motor de maquetación.

Carpetas recordadas (diálogos de ficheros)

Todos los diálogos de ficheros pasan por src/lib/dialogos.ts; nunca llames directamente a @tauri-apps/plugin-dialog.

Función Uso
abrirParaImportar(opciones) Diálogo de abrir que empieza en la última carpeta de importar; devuelve las rutas (array) o null; recuerda la carpeta de la primera ruta.
guardarParaExportar(opciones, nombre) Diálogo de guardar que empieza en la última carpeta de exportar con nombre como nombre de fichero propuesto.
elegirCarpetaParaExportar() Selector de directorio (exportación de imágenes sueltas); también recuerda la carpeta de exportar.
recordar(tipo, ruta) Recordar sin esperar al resultado; se usa cuando se arrastran ficheros sobre la app.

Mecánica: carpeta_inicial({ tipo, nombre }) (Rust, src-tauri/src/commands/carpetas.rs) devuelve la carpeta guardada si todavía existe (unida con nombre si se da); si no, nombre o null. carpeta_recordar({ tipo, ruta }) guarda el directorio de la ruta (la propia ruta si es un directorio; si no, su carpeta padre) bajo carpeta_importar o carpeta_exportar en app_settings. TipoCarpeta es "importar" | "exportar" (tipo generado). Las carpetas de importar y de exportar son separadas a propósito, para que exportar un PDF al Escritorio no redirija la siguiente importación de imágenes. Los fallos de cualquiera de los dos comandos se tragan (el diálogo simplemente se abre en su ubicación por defecto). Lo usan features/decklist/acciones.ts, features/export/acciones.ts, features/export/menu-exportar.tsx, features/library/library-screen.tsx y los diálogos del diseñador. Tests: src/lib/__tests__/dialogos.test.ts y el test unitario la_carpeta_de_una_ruta de commands/carpetas.rs.

Mantenimiento de la caché y del almacén de imágenes (no está en Ajustes)

No hay ninguna sección de «almacenamiento» en el diálogo de Ajustes. Dos acciones de mantenimiento viven donde son relevantes:

  • Limpiar la caché de miniaturas («Limpiar caché de miniaturas»): en el panel de la lista (decklist-panel.tsx), un diálogo de confirmación muestra el tamaño y el número de ficheros de cache_stats y luego cache_clear vacía solo la caché de renderizados derivados; las imágenes de los mazos nunca se tocan.
  • Liberar las imágenes sin usar («Liberar espacio»): en la pantalla de la biblioteca, blob_gc elimina los blobs que ningún mazo referencia (Rust conserva los recientes durante 24 h); el toast informa del número y de los megabytes.

Motivo: las imágenes de los mazos viven en el almacén de blobs (son datos), la caché solo guarda derivados y siempre se puede borrar. Véase Almacenamiento y blobs.

Modos de fallo

Situación Comportamiento
localStorage bloqueado o corrupto Toda lectura/escritura va en try/catch; se aplican los valores por defecto durante la sesión.
Falla getVersion() Simplemente no se muestra la etiqueta de versión.
Falla la comprobación de actualización (sin conexión, sin release, sin paquete para este instalador) Silencioso en segundo plano; el «Buscar ahora» manual muestra el error traducido (Network, NotFound...).
Falla la instalación (p. ej. se cancela la petición de contraseña) El estado vuelve a lista, toast de error; se puede reintentar sin volver a descargar.
Actualizar mientras se exporta un PDF o hay una partida abierta en la mesa update_install devuelve Busy (con una partida, motivo: partida); la pantalla no lo pide mientras la mesa está abierta: la instalación queda en cola y se hace al salir de la mesa.
Cerrar la ventana con un trabajo, una descarga, una carta sin guardar, un diálogo ocupado, el mazo sin poder guardarse o una partida abierta ConfirmarCierre pregunta; «Cerrar igualmente» cancela el trabajo, guarda la partida y cierra.
Cerrar la ventana sin nada pendiente Se vuelca el mazo y la ventana se cierra sin diálogo.
destroy() falla Toast «No se ha podido cerrar la ventana.».
El tema instalado seleccionado ya no existe SincronizarTemas restablece Amber Minimal.

Por dónde empezar para ampliarlo

  • Ajuste nuevo: si afecta al aspecto, añádelo solo en código adyacente a ThemeProvider mediante CSS nuevo/tema-inicial.ts (el propio proveedor es de solo lectura); si es una preferencia de lista/vista previa, añádela a Preferencias de useUi (Stores de estado); si el backend debe conocerla entre sesiones, usa app_setting_get/set o un comando dedicado.
  • Sección nueva de Ajustes: añade un componente de tarjeta en src/features/settings/ y renderízalo en SettingsDialog; el texto, por t(); el scroll ya lo aporta DialogCuerpo.

Véase también

Documentation · Documentación

Language: English · Español

Use and build · Uso y desarrollo

Architecture · Arquitectura

Data · Datos

Getting cards · Conseguir cartas

Layout, images, PDF · Maquetación, imagen y PDF

Card designer · Diseñador de cartas

Game table · Mesa de juego

Frontend · Frontend

Reference · Referencia

Clone this wiki locally