Repository navigation
Panel de la lista del mazo
English · Español
El panel izquierdo de la pantalla del mazo (src/features/decklist/). Contiene el cuadro de texto donde el usuario pega una lista de cartas, los botones que convierten esa lista en cartas (reemplazando o añadiendo), el diálogo «Detectar fichas», la importación por URL de Archidekt, la subida de imágenes propias y las opciones de resolución (idioma, política de imágenes, calidad de la vista previa). Todo lo pesado (parseo, Scryfall, descargas) se ejecuta en Rust; el panel solo lo dirige y pinta el estado.
Para el parser en sí véase Parseo de la lista del mazo; para la parte de Scryfall al resolver, véase Integración con Scryfall.

| Fichero | Responsabilidad |
|---|---|
src/features/decklist/decklist-panel.tsx |
DecklistPanel (maquetación, botones, opciones, confirmaciones) y PiePanel (pie: trabajo en curso o resumen) |
src/features/decklist/editor-lista.tsx |
EditorLista: el cuadro de texto con números de línea y marcas rojas en las líneas que Rust no pudo leer |
src/features/decklist/acciones.ts |
Funciones simples detrás de los botones: resolver, cargarLista, reaplicarIdioma, anadirPropias, subirCartasPropias, subirReverso, reversoGenerico, elegirImagenes, cancelarTrabajo
|
src/features/decklist/fichas-dialog.tsx |
FichasDialog: diálogo de revisión de «Detectar fichas» |
src/features/decklist/ejemplo.ts |
EJEMPLO: la lista de muestra detrás del botón «Ejemplo» |
src/features/decklist/__tests__/acciones.test.ts |
Cobertura de Vitest de acciones.ts
|
src-tauri/src/commands/decklist.rs |
parse_decklist, deck_list_text, deck_list_save
|
src-tauri/src/commands/cards.rs |
resolve_decklist, job_cancel, deck_tokens_detect, import_archidekt
|
Decisión: la lógica de los botones vive en acciones.ts, fuera de los componentes. Motivo: el menú contextual de una carta y el arrastre de ficheros sobre la vista previa reutilizan las mismas funciones (src/features/preview/arrastrar-ficheros.ts importa de ahí anadirPropias y EXTENSIONES_IMAGEN).
DecklistPanel es un ResizablePanelGroup vertical (react-resizable-panels, con la disposición guardada en localStorage bajo proxybuilder.panel-lista) con dos paneles:
-
lista: el cuadro de textoEditorLista. El usuario puede ajustar su altura con el tirador. -
opciones: una franja fija (contador de líneas, «Cargar lista», «Añadir», casilla «Añadir tokens automáticamente», «Detectar fichas») y, debajo, unScrollAreacompartido con tresSecciones plegables: «Importar desde URL», «Imágenes propias», «Opciones».
Botones de la cabecera: «Sets» (abre el Explorador de sets con useUi.getState().abrir({ tipo: "sets" })), «Ejemplo» (setSinHistorial({ decklistText: EJEMPLO })), «Limpiar» (vacía solo el cuadro de texto; las cartas del mazo no se tocan).
El pie PiePanel muestra o bien el trabajo en curso (useTrabajo -> trabajo.fase, hechas/total, una barra Progress y un botón Cancelar, deshabilitado hasta que se conoce trabajo.jobId) o bien «N cartas · P hojas» (recuento de cartas de useDeck y plan.sheets de usePlan), además de un recuento «N marcadas» de las cartas con DeckCard.marca y el último mensaje de acción (useUi.ultimaAccion, que se borra solo a los 8 s).
EditorLista es un <textarea> simple que crece con su contenido dentro del ScrollArea compartido (así los números y el texto se desplazan juntos y se cumplen las reglas de scroll del proyecto). La altura de línea es una constante LINEA = 20 px compartida por el margen de números y el textarea.
- 250 ms después de la última pulsación llama a
call("parse_decklist", { text }). Una banderavigentedescarta las respuestas obsoletas; un fallo se ignora porque la siguiente pulsación vuelve a parsear. - El resultado (
ParsedDecklist:lines,cardCount,invalidCount) sube poronLeidahasta el panel, que usaleida.cardCountpara habilitar los botones de carga y muestra «N cartas en la lista» / «N líneas sin entender» en rojo. - Las líneas con
kind === "invalid"tienen el número del margen en rojo con un tooltip (MOTIVOS: un mensaje por cadaLineError:zeroQuantity,badQuantity,tooMany,emptyName) y una banda roja detrás del texto.
parse_decklist (commands/decklist.rs) es un #[tauri::command] síncrono y barato que se limita a llamar a proxy_core::decklist::parse. El texto en sí es estado del mazo: useDeck -> deck.decklistText, que se escribe con setSinHistorial (un cambio que no entra en el historial de deshacer pero sí se autoguarda; véase Stores de estado).
sequenceDiagram
participant UI as DecklistPanel
participant A as acciones.ts
participant R as Rust commands/cards.rs
participant S as resolve::resolve
UI->>A: cargarLista(texto, "reemplazar" | "anadir", t)
A->>R: parse_decklist(text)
A->>A: resolver(lista, t)
A->>R: lanzarTrabajo: resolve_decklist(list, options)
R-->>A: jobId (spawned task)
R->>S: resolve(api, EnLaApp, list, options, cancelado, progress)
S-->>A: event resolve://progress {jobId, progress}
S-->>A: event resolve://done {jobId, result | error}
A->>A: deck.aplicar / setCartas(insertar)
Paso a paso:
-
pulsarCargarcompruebahayCartas && ui.pedirConfirmacionAlCargar; si ambas son verdaderas abre el diálogoConfirmar(«¿Reemplazar las cartas del deck?» con una casilla «No volver a preguntar» que cambia la preferenciapedirConfirmacionAlCargar). «Añadir» nunca pregunta. -
cargarListavuelve a parsear (parse_decklist) para no depender nunca de unleidaobsoleto. Una lista concardCount === 0anuncia «La lista no tiene cartas» y se detiene. -
resolverse niega a empezar siuseTrabajo.getState().actualestá definido (lanza{ code: "Busy", params: {} }), llama auseTrabajo.empezar("resolver", ...)y lanza el trabajo conlanzarTrabajo("resolve://progress", "resolve://done", ...)desrc/lib/trabajos.ts. Las opciones enviadas son{ lang: ui.lang, imagePolicy: ui.imagePolicy, addTokens: ui.addTokens }(ResolveOptionsensrc-tauri/src/scryfall/resolve.rs). -
lanzarTrabajose suscribe a ambos eventos antes de invocar el comando y guarda en un búfer los eventos que llegan antes de conocerse el id del trabajo. Motivo: un trabajo corto puede terminar antes de queinvokedevuelva su id. - Los eventos de progreso llevan una
ResolvePhase(lookup,language,tokens,images);FASESasigna a cada una una etiqueta traducida («Buscando cartas…», «Buscando el idioma…», «Buscando tokens…», «Descargando imágenes…»). - En
resolve://done: conerror.code === "Cancelled",resolverdevuelvenullycargarListaanuncia «Cancelado: no se ha cambiado nada»; cualquier otro error se lanza (toast mediantemensaje(e)); en caso contrario se aplica elResolveResult(cards: DeckCard[],unresolved: Unresolved[]). - Aplicar: reemplazar ->
deck.aplicar(() => ({ cards: r.cards }))y se limpia la selección (se puede deshacer con Ctrl+Z); añadir ->deck.setCartas((c) => insertar(c, r.cards))(src/lib/deck-ops.ts, inserta al final). -
avisarNoResueltasconstruye el aviso «Sin encontrar: A, B, C, D, E (+N)» (los cinco primeros nombres y luego un recuento) a partir der.unresolved; el envoltoriointentardel panel muestra comotoast.warningcualquier cadena devuelta. - El
finallyderesolversiempre llama auseTrabajo.terminar().
Lado de Rust (resolve_decklist en src-tauri/src/commands/cards.rs): reserva un trabajo (state.jobs.alta() -> (job_id, cancelado)), lanza una tarea asíncrona que mantiene la marca de ocupado (estado.trabajo()), ejecuta resolve::resolve con EnLaApp (una implementación del trait Guardar que escribe las imágenes en el almacén de blobs y registra los datos de las cartas en la caché de Scryfall), emite los dos eventos y da de baja el trabajo (jobs.baja). EnLaApp también ofrece la búsqueda de nombres en inglés para las líneas que no están en inglés (fuentes::nombres_ingleses) y los respaldos del catálogo de fichas de Cockatrice (externa_por_nombre, externas_creadas_por; véase Fuentes externas).
Correspondencia de secciones dentro del resolutor: una sección Sideboard llega como MarcaCarta::Banquillo y una sección Maybeboard como MarcaCarta::Opcional (src-tauri/src/scryfall/resolve.rs, Section::Sideboard => Some(MarcaCarta::Banquillo)). Las cartas marcadas se imprimen pero no cuentan para el tamaño del mazo, las copias ni las estadísticas (véanse Modelo del mazo y Estadísticas).
Cancelar: cancelarTrabajo llama a job_cancel { jobId } (commands/cards.rs, devuelve bool), o a export_cancel cuando el trabajo en curso es una exportación. Cancelar deja el mazo intacto.
| Control | Campo del store (useUi) |
Notas |
|---|---|---|
Idioma de las cartas (IDIOMAS, hecho con IDIOMAS_SCRYFALL de src/lib/idiomas.ts: en, es, fr, de, it, pt, ja, ko, ru, zhs, zht, cada uno con su nombre propio, sin traducir) |
lang |
Se envía como ResolveOptions.lang. Si el idioma no existe para una carta, el resolutor recurre al inglés y marca meta.langFallback (insignia «↩ EN»). |
| Imágenes |
imagePolicy (ImagePolicy: withImage, bestAvailable, highresOnly) |
Qué impresión se elige cuando la lista no indica el set. |
| Calidad de la vista previa |
calidadVista (244, 488, 672, 745 px) |
Solo en pantalla; el PDF siempre usa la resolución de exportación. |
| Preguntar antes de reemplazar las cartas | pedirConfirmacionAlCargar |
Activado por defecto. |
| Añadir tokens automáticamente | addTokens |
Desactivado por defecto. Las fichas salen del all_parts de la impresión en inglés. |
«Reaplicar idioma» -> reaplicarIdioma(t):
-
listaParaReaplicar(deck.cards)(src/lib/cartas.ts) construye unaParsedDecklistsintética con una línea por cada copia de cada carta que no esté fijada (c.locked), tengasource.kind === "scryfall"y tenga nombre, usando su set y su número de coleccionista, y devuelve la lista paralela de ids de carta. -
resolver(lista, t, { addTokens: false })ejecuta el trabajo normal (con las fichas desactivadas para que el mazo no crezca). -
aplicarReaplicado(cartas, ids, r.cards, noResueltas)empareja por orden las cartas nuevas con las antiguas, saltándose las líneas cuyolineNoestá enunresolved; la carta nueva toma elid, loseditsy ellockedde la antigua, y conserva un reverso propio si la antigua lo tenía. - Resultado: «Idioma reaplicado a N cartas».
Si no hay ninguna que cumpla: «No hay cartas de Scryfall sin fijar» y no se llama a Rust.
Se abre con useUi.abrir({ tipo: "fichas" }) (botón «Detectar fichas», deshabilitado si hay un trabajo en curso o el mazo está vacío). Encuentra las fichas que crean las cartas que ya están en el mazo y que el mazo aún no tiene, incluidas las cartas propias vinculadas a Scryfall (véase Identificación de cartas y OCR).
- Al abrirse llama a
deck_tokens_detect { cards }con las cartas actuales del mazo. DevuelveFichaPropuesta[]({ carta: CardSummary, creadaPor: string }). - Rust (
deck_tokens_detectencommands/cards.rs):scryfall::fichas_auto::faltan_en_el_deckrecoge los ids de Scryfall del mazo, lee cada carta de la caché local (cache::leer) y sigue elall_partsde las impresiones en inglés para encontrar fichas; excluye las fichas ya presentes por id, por la misma ficha de otra colección o por nombre (también las cartas propias hechas a mano). Las fichas encontradas se escriben en la caché de Scryfall. Después añade las fichas que Scryfall no tiene a partir del catálogo de Cockatrice (fuentes::cockatrice::catalogo,creadas_por(name)), salvo que ya haya una carta con ese nombre en el mazo o en las propuestas. Los errores de red del catálogo de Cockatrice se tragan (if let Ok(cat)), de modo que los resultados de Scryfall llegan igualmente. - El diálogo muestra las propuestas en una
RejillaCartascon todas preseleccionadas, más una lista «{ficha}: creada por {carta}». - «Añadir N fichas» llama a
traerCarta(p.carta)por cada propuesta seleccionada (véase Añadir carta y variantes) y las añade coninsertar(cs, nuevas). Anuncia «N fichas añadidas».
FichasDialog pasa cambiosSinGuardar={false} y se niega a cerrarse mientras añade (anadiendo).
La sección «Importar desde URL» llama a import_archidekt { url, includeSideboard } (commands/cards.rs). Extrae el id del mazo con archidekt::id_de_url, hace un GET a https://archidekt.com/api/decks/{id}/ con un user agent ProxyBuilder/<version>, asigna 404/403 a NotFound y los demás estados no exitosos a Network, y luego archidekt::desde_json(text, include_sideboard) devuelve ArchidektImport { text, list }. El panel pone r.text en el cuadro de texto (setSinHistorial) y anuncia «Lista importada de Archidekt (N cartas)»; no carga las cartas: el usuario aún tiene que pulsar «Cargar lista». Una URL incorrecta es InvalidInput con motivo = "bad_url".
Trampa conocida: este es el único sitio donde Rust habla con un host directamente con reqwest::Client en lugar de pasar por fuentes::Externo (véase Fuentes externas). El texto del panel indica que Moxfield no se puede importar (hay que copiar y pegar su lista).
- «Subir cartas»:
elegirImagenesabre el diálogo del sistema medianteabrirParaImportar(src/lib/dialogos.ts, que recuerda la última carpeta de importación), filtra porEXTENSIONES_IMAGEN(png,jpg,jpeg,webp), copia los ficheros al almacén de blobs conblob_import_files { paths }(devuelveFaceImage[];commands/library.rs) y crea una carta propia por imagen concartaPropia(img, nombreDeFichero(ruta)). El nombre de la carta se deriva del nombre del fichero connombreDeFichero(quita la carpeta, la extensión, la numeración inicial como0001 -,T003 -,12., y los(1)/~2finales). -
anadirPropias(rutas, t, en?)hace lo mismo con rutas que no vienen del diálogo (arrastrar y soltar), recordando la carpeta conrecordar("importar", ...)e insertando en la posiciónen. - «Subir reverso»: la primera imagen pasa a ser
deck.defaultBack; «Volver al reverso genérico» restablece el reverso generado por la app (blob_generic_back). - «Vincular con una carta» abre el diálogo de vinculación en bloque (
tipo: "vincularLote"); con más de una carta seleccionada se limita por defecto a la selección (soloSeleccion). Detalles en Añadir carta y variantes y Identificación de cartas y OCR.
Las imágenes siempre van al almacén de blobs (app_data_dir/blobs/), nunca a la caché; véase Almacenamiento y blobs.
«Limpiar caché de miniaturas» llama primero a cache_stats y muestra un Confirmar con el tamaño y el número de ficheros (megas() da formato a los bytes); al confirmar, cache_clear borra app_cache_dir/renders/ (commands/library.rs, vaciar). Nunca puede tocar el almacén de blobs, así que las imágenes de los mazos sobreviven.
La lista como texto la produce deck_list_text { deck, format } y la guarda deck_list_save { deck, format, path } (commands/decklist.rs; FormatoLista, proxy_core::decklist::exportar). Se manejan desde el menú de tres puntos del botón de exportar, véase Exportación. deck_list_save exige una ruta absoluta que acabe en .txt (si no, InvalidInput) y escribe de forma atómica (escribir_atomico).
Todos los manejadores están envueltos por intentar, que muestra mensaje(e) en un toast (véase IPC y manejo de errores). Códigos que el panel puede mostrar: Busy (ya hay un trabajo en curso), Network / RateLimited (Scryfall, Archidekt), NotFound (mazo de Archidekt privado o desconocido), InvalidInput (URL incorrecta), Cancelled (se trata en silencio como una cancelación).
-
src/features/decklist/__tests__/acciones.test.ts:resolver(opciones pasadas, fases, Busy, cancelación, error, fallo de Rust al lanzar),cargarLista(reemplazar, añadir, aviso de no resueltas de hasta cinco nombres, cancelación),reaplicarIdioma,cancelarTrabajo(por tipo de trabajo), imágenes propias y reversos. - Rust: los tests de
src-tauri/src/commands/cards.rs(consultas, registro de miniaturas, casos detraer_carta) y los tests del resolutor ensrc-tauri/src/scryfall/resolve.rs.
- Un botón nuevo que añade cartas: escribe la función en
acciones.ts(para que los menús y el arrastre de ficheros puedan reutilizarla) y llama auseDeck.getState().setCartas(...). - Una opción de resolución nueva: añade el campo a
ResolveOptionsen Rust, ejecutapnpm bindings, añade la preferencia asrc/stores/ui-store.tsy pásala enresolver. - Otro trabajo largo: reutiliza
lanzarTrabajo(escuchar antes de lanzar) yuseTrabajo.
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
- 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
- Set-Browser · Explorador de sets
Reference · Referencia