Skip to content

Almacenamiento y blobs

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

Almacenamiento y blobs

English · Español

Todo lo que la app guarda en local se reparte en dos sitios: app_data_dir (la biblioteca SQLite library.db, el almacén de imágenes direccionado por contenido blobs/, los recursos descargados, la partida en curso y la identidad para jugar en red) y app_cache_dir (ficheros derivados que se pueden borrar en cualquier momento). Los mazos dependen de app_data_dir; nada depende de app_cache_dir.

Código: src-tauri/src/store/ (db.rs, decks.rs, blobs.rs, etiquetas.rs, colecciones.rs, presets.rs, settings.rs, listas.rs, partidas.rs, migrations/*.sql), src-tauri/src/mesa/guardada.rs (la partida en curso), src-tauri/src/state.rs (Paths, AppState::open), src-tauri/src/render.rs (caché de renderizado) y src/stores/deck-store.ts (el lado de la pantalla).

Regla de propiedad de los directorios

Decisión: las imágenes de los mazos viven en el almacén de blobs bajo app_data_dir/blobs/, no en la caché. Motivo: los mazos referencian las imágenes por SHA-256, así que las imágenes son datos del usuario; el sistema operativo o el usuario pueden vaciar un directorio de caché cuando quieran, y eso nunca debe romper un mazo. La caché (app_cache_dir/renders/) solo guarda renderizados derivados (miniaturas, vistas previas de diseños) que se recalculan a partir de los blobs cuando hacen falta.

AppState::open(paths) (state.rs) crea los dos directorios, abre library.db, ejecuta store::decks::rellenar_refs una sola vez, construye BlobStore::new(data/blobs) y Recursos::abrir(data), y carga (o crea) la clave de identidad para jugar en red con red::identidad::cargar_o_crear(data). Paths { data, cache } se rellena en src-tauri/src/lib.rs con app_data_dir() / app_cache_dir() de Tauri, pasando por red::perfil::carpeta (con un perfil de debug, perfiles/<nombre>/ dentro de cada una).

SQLite: library.db

Db (store/db.rs) envuelve una única rusqlite::Connection en un Mutex (rusqlite no es Sync; los trabajos largos sueltan el cerrojo entre pasos). Al abrir fija journal_mode=WAL, foreign_keys=ON, synchronous=NORMAL y después ejecuta las migraciones. Db::in_memory() se usa en los tests.

Migraciones

Las migraciones son ficheros SQL con include_str! listados en orden en MIGRACIONES (store/db.rs) y registrados en schema_migrations (version, applied_at) (que se crea con CREATE TABLE IF NOT EXISTS antes del bucle). Cada una se ejecuta en su propia transacción, junto con su fila de schema_migrations. Regla: las migraciones solo se añaden; una publicada nunca se edita.

# Fichero Añade
1 0001_init.sql decks, deck_cards (+ índices deck_cards_orden, deck_cards_front, deck_cards_back), blobs, presets (+ presets_kind), app_settings, scryfall_cards (+ scryfall_nombre, scryfall_impresion).
2 0002_blob_refs.sql blob_refs (owner_kind, owner_id, blob) + blob_refs_blob: referencias a imágenes que no son ni el anverso ni el reverso de una carta (arte del diseñador, emblemas, imágenes «antes»).
3 0003_biblioteca.sql Organización de la biblioteca: columnas decks.pinned, decks.notes, decks.opened_at, decks.cover_card_id; tablas tags (+ único tags_name sobre name COLLATE NOCASE) y deck_tags (+ deck_tags_tag).
4 0004_idioma_deck.sql Columna decks.lang_override (idioma del mazo elegido a mano; NULL significa «calculado a partir de las cartas»).
5 0005_listas_juego.sql Tabla deck_listas (deck_id, id, nombre, comandantes, cartas, updated_at), clave primaria (deck_id, id), deck_id referencia a decks(id) con ON DELETE CASCADE: las listas para jugar de la mesa de juego (Mesa de juego).
6 0006_partidas.sql Tablas partidas (una fila por partida terminada: hora de inicio y de fin, formato y modo como valores JSON, jugadores y ganadores como JSON, cómo acabó, una nota, rondas, duración, versión del catálogo y registro, el registro comprimido o NULL) y partida_decks (partida_id, jugador, deck_id, puesto), clave primaria (partida_id, jugador), con partida_id referenciando partidas(id) ON DELETE CASCADE; índices partidas_terminada y partida_decks_deck: el historial de partidas (Mesa de juego).

Test: abrir_dos_veces_aplica_la_migracion_una_sola_vez (db.rs) también ejecuta PRAGMA integrity_check.

Tablas

erDiagram
    decks ||--o{ deck_cards : "ON DELETE CASCADE"
    decks ||--o{ deck_tags : "ON DELETE CASCADE"
    tags ||--o{ deck_tags : "ON DELETE CASCADE"
    decks ||--o{ deck_listas : "ON DELETE CASCADE"
    decks }o--o{ blobs : "default_back_blob"
    deck_cards }o--o{ blobs : "front_blob / back_blob"
    blob_refs }o--|| blobs : "blob"
    decks ||--o{ blob_refs : "owner_kind=deck"
    presets ||--o{ blob_refs : "owner_kind=preset"
    decks ||--o{ blob_refs : "owner_kind=mesa (tapete de la mesa)"
    partidas ||--o{ partida_decks : "ON DELETE CASCADE"
    decks {
        text id PK
        text name
        text decklist_text
        text settings_json
        text default_back_json
        text default_back_blob
        int schema_version
        int created_at
        int updated_at
        int pinned
        text notes
        int opened_at
        text cover_card_id
        text lang_override
    }
    deck_cards {
        text id
        text deck_id FK
        int position
        text front_blob
        text back_blob
        text card_json
    }
    blobs {
        text hash PK
        text ext
        text mime
        int width
        int height
        int bytes
        text origin
        int created_at
    }
    blob_refs {
        text owner_kind
        text owner_id
        text blob
    }
    presets {
        text id PK
        text kind
        text name
        text json
        int updated_at
    }
    tags {
        text id PK
        text name
        text color
        int created_at
    }
    deck_tags {
        text deck_id
        text tag_id
    }
    deck_listas {
        text deck_id
        text id
        text nombre
        text comandantes
        text cartas
        int updated_at
    }
    app_settings {
        text key PK
        text value
    }
    scryfall_cards {
        text id PK
        text oracle_id
        text name
        text lang
        text set_code
        text collector_number
        text json
        int fetched_at
    }
    partidas {
        text id PK
        int empezada
        int terminada
        text formato
        text modo
        text jugadores
        text ganadores
        text como
        text nota
        int rondas
        int duracion_ms
        int version_catalogo
        blob registro
    }
    partida_decks {
        text partida_id FK
        int jugador
        text deck_id
        int puesto
    }
Loading

Notas sobre las tablas:

  • La clave primaria de deck_cards es (deck_id, id); front_blob / back_blob son copias desnormalizadas de los hashes de imagen de la carta, para que el recolector de basura y la biblioteca encuentren las imágenes en uso sin interpretar card_json. El DeckCard completo es card_json. Un id de carta duplicado dentro de un mismo mazo se convierte en InvalidInput con motivo = duplicate_card_id.
  • decks.default_back_json es el FaceImage serializado; default_back_blob es su hash (por la misma razón).
  • presets.kind es uno de frontOffsets, backOffsets, print (store/presets.rs, se guardan por nombre: mismo tipo y nombre sustituye) o collection (store/colecciones.rs, se guarda por id para que renombrar no cree una segunda colección; una colección que ya no se deserializa, por ejemplo de una versión futura, la omite list).
  • scryfall_cards es una caché de metadatos (TTL de una semana, scryfall/cache.rs; los precios se refrescan a las 24 h). Ten en cuenta que, aunque es una caché, vive dentro de library.db en app_data_dir, porque las estadísticas y la biblioteca funcionan sin conexión a partir de ella (commands/stats.rs, biblioteca.rs).
  • deck_listas (store/listas.rs) guarda las listas para jugar de la mesa de juego: comandantes y cartas son arrays JSON de ids de copias del deck (DeckCard.id). Viven aparte del deck, como las etiquetas, porque decks::save reescribe el deck en cada autoguardado y las pisaría; los ids que ya no están en el deck solo se avisan al comprobar la lista. guardar inserta o sustituye por (deck_id, id) (un id nuevo si viene vacío) con el nombre limpio y cortado a 60 caracteres.
  • partidas y partida_decks (store/partidas.rs) son el historial de la mesa de juego. registro es la PartidaGuardada de la partida (el mismo JSON comprimido que la partida guardada, mesa/guardada.rs) o NULL cuando quien la guardó nunca tuvo la partida entera (un invitado en red). partida_decks no tiene clave foránea a decks a propósito: borrar un deck deja sus partidas, con el nombre del deck escrito dentro del JSON de los jugadores. Guardar una partida con un id que ya existe la reemplaza (y a sus partida_decks); jugar nunca toca decks.updated_at. Cada jugador del JSON lleva también la lista para jugar usada (listaId, lista, cartasLista), escrita desde deck_listas al guardar la partida; las filas guardadas antes se leen sin ellos.
  • tags.color es uno de COLORES (gray red orange amber green teal sky blue violet pink); un color desconocido se queda en gris; los nombres de etiqueta se recortan, se limitan a 40 caracteres, son únicos sin distinguir mayúsculas y create devuelve la etiqueta existente si el nombre ya existe.
  • Las fechas son milisegundos desde 1970 (store::ahora_ms).

Qué toca updated_at

decks::save y decks::rename fijan updated_at. Fijar como favorito, las notas (máx. 4000 caracteres), la carta de portada, el idioma a mano, las etiquetas, las listas para jugar y «abierto en» deliberadamente no lo tocan (etiquetas.rs, decks::marcar_abierto), para que organizar la biblioteca no la reordene por fecha de modificación. Test: favorito_notas_y_portada_no_cambian_la_fecha.

Operaciones principales (store/decks.rs)

Función Comportamiento
create(db, name, default_back) Uuid nuevo, PrintSettings por defecto, DECK_SCHEMA_VERSION, y después save. La capa de comandos (commands/library.rs crear_deck) le pasa el reverso genérico.
open(db, id) Lee la fila del mazo y todos los card_json ordenados por position; NotFound si no existe, Db si el JSON ya no se deserializa. deck_open también llama a marcar_abierto; deck_peek no.
save(db, deck) Una transacción. Primero valida que toda imagen referenciada existe en blobs (anversos, reversos, reverso del mazo e imágenes del diseñador de refs_del_deck); si falta alguna, InvalidInput, motivo = blob_missing. Después hace upsert de la fila de decks (conserva created_at y las columnas de la biblioteca), borra y vuelve a insertar todos los deck_cards en orden, y reescribe los blob_refs del mazo. Devuelve la hora de guardado.
list(db) Un DeckSummary por mazo para la biblioteca: número de cartas, portada (la carta elegida si sigue presente y con imagen; si no, la primera carta con imagen), etiquetas. Ordenado por updated_at DESC.
duplicate(db, id, name) Lo abre, asigna uuids nuevos al mazo y a cada carta, lo guarda y después copia notas, portada (reasignada al id nuevo de la carta), idioma a mano y etiquetas (no el favorito). Las imágenes se comparten, no se copian.
delete(db, id) Borra el mazo (la cascada elimina cartas, etiquetas y listas para jugar), sus blob_refs (owner_kind = 'deck'), la referencia de su tapete de la mesa de juego (owner_kind = 'mesa') y el ajuste de ese tapete (mesa.tapete.<id>). Los ficheros de imagen no se eliminan; lo hace después la recolección de basura.
rellenar_refs(db) Relleno único de blob_refs a partir de los mazos existentes, protegido por la clave blob_refs_v1 de app_settings (una tabla vacía no es un marcador válido porque un mazo sin diseños la deja vacía).

Tests: guardar_y_reabrir_135_cartas_da_lo_mismo, no_se_guarda_una_carta_con_una_imagen_que_no_existe, dos_cartas_con_el_mismo_id_es_un_error_claro, duplicar_renombrar_y_borrar, la_limpieza_respeta_el_arte_de_los_disenos, el_relleno_de_referencias_se_hace_una_vez.

El almacén de blobs

BlobStore (store/blobs.rs) es un almacén de imágenes direccionado por contenido.

  • Estructura: app_data_dir/blobs/<primeros dos caracteres hex>/<sha256>.<ext> (BlobStore::path_of). ext es jpg, png o webp (proxy_core::imaging::probe::Kind).
  • La identidad es el SHA-256 de los bytes del fichero, en hexadecimal minúscula, 64 caracteres (es_huella lo valida y se reutiliza para validar rutas que vienen de paquetes y del protocolo de imágenes). Dos mazos con la misma carta comparten un único fichero.
  • Fila de metadatos en blobs: ext, mime, width, height, bytes, origin, created_at.

API

Función Comportamiento
put_bytes(db, bytes, origin) -> FaceImage Detecta el formato (ErrorCode::Unsupported si no es una imagen admitida; si está corrupta, ErrorCode::Image con motivo = corrupt), rechaza cualquier lado superior a LADO_MAXIMO = 12000 px (Image, motivo = too_large), calcula el hash, escribe de forma atómica si no existe y hace INSERT OR IGNORE de la fila. Si el blob ya existía, el origin devuelto es con el que entró la primera vez.
import_files(db, paths) Lee ficheros del usuario y les aplica put_bytes como Upload. Copia, nunca enlaza, así que borrar el original no puede romper un mazo.
generic_back(db) Inserta el reverso genérico incluido (assets/reverso-generico.png, 745x1040, origen Generated) y guarda su hash en app_settings bajo generic_back_blob.
locate(db, hash) Ruta y Kind de un blob conocido (busca ext en blobs); None para hashes mal formados.
gc(db, ocupado) Véase más abajo.
escribir_atomico(dest, bytes) Escribe en un temporal .part en la misma carpeta (su nombre es único en cada escritura: id del proceso y un contador, porque la preparación de la mesa escribe en paralelo y dos copias de la misma carta, o dos decks que la comparten, escriben el mismo destino a la vez; con un nombre fijo uno renombraba el temporal del otro y fallaba con «no existe el fichero», que se veía como «no se puede escribir en disco»; test escribir_a_la_vez_el_mismo_destino_no_falla), hace sync_all y renombra; un fallo nunca deja un fichero a medias con el nombre final. Se reutiliza en casi todas las descargas de la app.

Recolección de basura

BlobStore::gc borra los blobs que nada usa y que tienen más de 24 horas (GRACIA_MS). Un blob se conserva si se cumple cualquiera de estas condiciones:

  1. deck_cards.front_blob o back_blob es igual a su hash.
  2. decks.default_back_blob es igual a él.
  3. Una fila de blob_refs apunta a él (arte del diseñador, emblema del diseñador, imágenes «antes», emblemas de colecciones, tapetes de la mesa de juego, la imagen de los dados, el avatar para jugar en red).
  4. Es el reverso genérico (app_settings.generic_back_blob).
  5. Se creó hace menos de 24 horas.

Por qué el periodo de gracia: una imagen puede pertenecer a una descarga cuyo mazo aún no se ha guardado. Por qué ocupado: gc devuelve ErrorCode::Busy mientras hay un trabajo de resolución o exportación en marcha (AppState::ocupado()), porque el trabajo puede tener hashes que ninguna fila de mazo referencia todavía. ocupado() también es verdadero mientras hay una partida abierta en la mesa (Mesa de juego). El único que la llama es blob_gc (commands/library.rs), lanzado por la acción «Liberar espacio» de la pantalla de la biblioteca (src/features/library/library-screen.tsx); no hay recolección automática. Primero se elimina el fichero (si falta no pasa nada) y después la fila de blobs.

Los propietarios de blob_refs: owner_kind = 'deck' (se reescribe en cada decks::save a partir de CardDesign::blobs()), 'preset' (el emblema de una colección, escrito en colecciones::save y eliminado en colecciones::delete) y 'mesa' (el tapete de la mesa de juego: owner_id es el id del deck o general; lo escribe y lo quita mesa/tapete.rs::guardar, y se borra con su deck en decks::delete; con owner_id = 'dados', la imagen de los dados del jugador, que escribe mesa/dados.rs::guardar) y 'red' (owner_id = 'avatar', el avatar para jugar en red, que escribe y quita red/identidad.rs::guardar). El tapete no puede usar 'deck' porque decks::save reescribe las referencias 'deck' de un deck en cada guardado y lo borraría. Test: la_limpieza_solo_borra_lo_huerfano_y_viejo.

Trampa conocida: cualquier lugar nuevo que guarde un hash de blob debe quedar reflejado en una de las cuatro protecciones anteriores, o la recolección acabará borrando la imagen y el mazo fallará al guardarse o imprimirse (save se niega a guardar una referencia a un blob inexistente, así que normalmente lo notarás en el siguiente guardado).

Caché de renderizado (app_cache_dir/renders/)

AppState::carpeta_renders() (render.rs) es el único directorio de caché que gestiona la interfaz de la app. Contenido, todo derivado y regenerable bajo demanda:

Ruta bajo renders/ Contenido Lo escribe
<key>.jpg Miniaturas de vista previa procesadas (clave de RenderSpec::key()) AppState::miniatura (render.rs)
t/<hash>.jpg Miniaturas de resultados de búsqueda obtenidas de Scryfall u otras fuentes protocol.rs
d/<hash>-<width>.png Vistas previas de cartas del diseñador diseno/vista.rs imagen
c/<hash>-<width>.png, c/<hash>.json «Capa sin ilustración» de la carta del diseñador y la información del hueco de la ilustración diseno/vista.rs capa
b/<blob>-<side>.jpg o .png Ilustración reducida para el arrastre en vivo diseno/vista.rs arte_reducido
m/<sha256 o s-<id>-<cara>>-<ancho>.jpg Imágenes de la mesa de juego reducidas al ancho que pide la pantalla mesa/imagenes.rs servir

El renderizado del PDF no tiene caché en disco («pesa demasiado», render_para_pdf). Los comandos cache_stats / cache_clear (commands/library.rs) miden y hacen remove_dir_all exactamente de esta carpeta; se exponen en el panel de la lista (src/features/decklist/decklist-panel.tsx) con un texto de confirmación. Test: vaciar_la_cache_no_toca_las_imagenes_de_los_decks.

app_settings (clave/valor en SQLite)

Se accede mediante store/settings.rs (get, set, upsert) y, desde la pantalla, los comandos genéricos app_setting_get / app_setting_set (src/lib/ipc.ts). Claves vistas en el código:

Clave La escribe Significado
last_deck_id src/stores/deck-store.ts Mazo que se abre al arrancar.
generic_back_blob BlobStore::generic_back Hash del reverso genérico; lo protege de la recolección.
blob_refs_v1 decks::rellenar_refs Marca que blob_refs ya se rellenó.
carpeta_importar, carpeta_exportar commands/carpetas.rs Última carpeta usada por los diálogos de fichero de importar / exportar (se guardan por separado).
mesa.tapete.<id del deck>, mesa.tapete mesa/tapete.rs El tapete de la mesa de juego de un deck, o el general: JSON { blob, velo, desenfoque }. La clave del deck se borra en decks::delete.
mesa.dados mesa/dados.rs El aspecto de los dados y la moneda del jugador (AspectoDados, JSON).
red.apodo, red.avatar red/identidad.rs El apodo y la huella del blob del avatar para jugar en red.

Otras preferencias de la interfaz (tema, skin, idioma, tamaños de paneles, vista de la biblioteca) no están aquí; son claves de localStorage con el prefijo proxybuilder. (por ejemplo proxybuilder.lang, proxybuilder.theme, proxybuilder.preferencias, proxybuilder.biblioteca), véanse Ajustes y shell y Temas y skins.

Autoguardado y deshacer por mazo (src/stores/deck-store.ts)

El mazo abierto vive en el store de zustand useDeck (deck, guardado, errorGuardado, version, pasado, futuro). Véase también Stores de estado.

  • Un único punto de entrada. Todo cambio del usuario pasa por aplicar(cambio): empuja una instantánea (cards, settings, defaultBack; no el nombre ni el texto de la lista) a pasado (con tope MAX_HISTORIAL = 100), vacía futuro, pone guardado = "dirty" y sube version. setCartas y setAjustes son envoltorios. Ninguna pantalla llama a deck_save por su cuenta.
  • Cambios que no se pueden deshacer. setSinHistorial (nombre, decklistText) marca como sucio sin historial. refrescarCartas(antes, nuevas) sustituye cartas (por ejemplo miniaturas de diseños regeneradas) sin historial y solo para las cartas que siguen siendo el mismo objeto que en antes, de modo que una edición concurrente del usuario gana.
  • Deshacer/rehacer. deshacer / rehacer intercambian instantáneas entre pasado y futuro y marcan como sucio (así que deshacer también se guarda). cargar(deck) reinicia el historial.
  • Autoguardado. iniciarAutoguardado(retardoMs = 800) (se inicia en src/features/shell/use-deck-inicial.ts) se suscribe a los cambios de version mientras está sucio y retarda un temporizador de 800 ms. Solo hay un guardado en curso a la vez: si llega otro cambio durante el guardado, encadena otro guardado después con el último mazo. Tras guardar, el estado pasa a "saved" solo si version no se movió entretanto; si no, sigue en "dirty". Si falla, el estado es "error" (con errorGuardado fijado); el siguiente cambio vuelve a armar el guardado.
  • Volcado inmediato. guardarAhora() guarda al instante si no está ya guardado; se llama antes de abrir otro mazo, de duplicar o exportar desde la biblioteca y antes de que se instale una actualización de la app.
  • Arranque. abrirDeckInicial(nombre) lee last_deck_id, prueba deck_open y, si falla, recurre a deck_create; después escribe last_deck_id.
  • Lado de Rust. deck_save -> decks::save reescribe el mazo entero en una transacción (véase más arriba), de modo que un guardado es atómico: o queda el mazo nuevo entero o el anterior.

Tests: src/stores/__tests__/deck-store.test.ts (cinco cambios rápidos guardan una sola vez, deshacer x3 restaura y rehacer repite, error y reintento, cambios sin historial), deck-store-abrir.test.ts.

Estructura del directorio de datos

Todo lo que sigue es relativo al app_data_dir o app_cache_dir de la plataforma (los resuelve Tauri; las rutas no están fijadas en el código). «Se puede borrar» significa que no se pierden datos de mazos; la entrada se recrea o se vuelve a descargar cuando hace falta.

app_data_dir

Ruta Contenido Lo crea ¿Se puede borrar?
library.db (+ -wal, -shm) Biblioteca SQLite: mazos, cartas, etiquetas, presets (incluidas las colecciones), ajustes, scryfall_cards store/db.rs No. Borrarla borra todos los mazos. Cierra antes la app; copia los tres ficheros juntos.
blobs/<xx>/<sha256>.<ext> Imágenes de los mazos (direccionadas por contenido) store/blobs.rs No. Los mazos quedarían referenciando imágenes inexistentes. Usa en su lugar «Liberar espacio» de la app.
modelos/ Dos modelos de OCR (text-detection.rten, text-recognition.rten, ~12 MB), descargados la primera vez que se usa el OCR ocr.rs Sí (se vuelven a descargar cuando se usa el OCR otra vez).
temas/<id>.json, temas/fuentes/<hash>.woff2 Temas de tweakcn instalados (convertidos a CSS) y sus Google Fonts temas.rs Sí, pero esos temas desaparecen hasta que se reinstalen.
simbolos/ (indice.json + SVG) Símbolos de maná y de sets de Scryfall, renovados cuando el índice tiene más de 30 días diseno/recursos.rs Sí (se vuelven a descargar).
fuentes/<sha256>.<ttf|otf>, fuentes/licencias/ Letras importadas por el usuario o del catálogo de letras libres, con sus licencias diseno/recursos.rs, diseno/letras_libres.rs Solo si aceptas que los diseños que las usan recurran a otra letra. Las letras importadas por el usuario no se pueden recuperar.
marcos/<id>/ Packs de marcos: original (marcos de Card Conjurer, descargado), packs descargables como Balatro y packs importados por el usuario diseno/original.rs, diseno/descargables.rs, diseno/recursos.rs Los descargados: sí (se vuelven a descargar y se comprueba su SHA-256). Los packs importados por el usuario: no hay copia en ningún otro sitio.
packs-extras/<id>/ Contenido extra de los packs descargables (reversos, fichas, cartas hechas), guardado aparte del pack diseno/descargables.rs Sí (se vuelve a descargar).
partidas/actual.json.gz La partida en curso de la mesa (PartidaGuardada): versión del catálogo, configuración, decks preparados, partida inicial, eventos, hora del guardado y el marcador del «al mejor de», como JSON en gzip escrito de forma atómica. Se escribe al abrir la partida, cada 50 eventos (CADA), a los 20 s con eventos sin guardar (CADA_MS) y cuando se fuerza (mesa_guardar, que la pantalla llama al salir de la pestaña o cerrar la ventana; mesa_salir con guardar; mesa_siguiente_partida). No lo escribe un invitado en red (la partida la guarda el anfitrión) ni durante una repetición. Lo borra mesa_salir sin guardar (no al salir de una repetición). Las partidas terminadas van a la tabla partidas mesa/guardada.rs, commands/mesa.rs Sí, pero se pierde la partida en curso. Un fichero de un catálogo más nuevo se rechaza con Unsupported (motivo: version).
identidad/clave La clave de iroh de la instalación (32 bytes, ed25519, permisos 0600, escrita de forma atómica): la identidad del jugador en red y en el servidor de salas. Al importar una identidad, la clave anterior se queda en identidad/clave.anterior red/identidad.rs Solo si aceptas ser un jugador nuevo (los playgroups pedirían otra invitación). Expórtala antes (Ajustes > Mesa de juego) para conservarla.
perfiles/<nombre>/ Otra carpeta de datos entera para una instancia de debug (PB_PERFIL; nunca en release) red/perfil.rs Sí.
letras-cjk/<código>.ttf, letras-cjk/Noto-CJK-OFL.txt Noto Sans JP/KR/SC/TC para las cartas diseñadas en japonés, coreano y chino (ja, ko, zhs, zht; unos 10–18 MB cada una) y su licencia OFL, bajadas la primera vez que un deck usa uno de esos idiomas de diseño diseno/letras_cjk.rs, diseno/recursos.rs (guardar_cjk, leer_cjk) Sí (se vuelven a bajar y se comprueba su SHA-256 al elegir otra vez el idioma; mientras, esas cartas enseñan el aviso de que faltan letras).

app_cache_dir

Ruta Contenido Lo crea ¿Se puede borrar?
renders/ (+ t/, d/, c/, b/, m/) Miniaturas derivadas, vistas previas del diseñador e imágenes reducidas de la mesa de juego render.rs, protocol.rs, diseno/vista.rs, mesa/imagenes.rs Sí (botón en el panel de la lista).
mesa/ (<xx>/<sha256>.jpg, s/<id>-<cara>.jpg, i/<clave>.json) Caras de las cartas de la mesa de juego preparadas a 745 px (ANCHO_MESA, JPEG 85), impresiones de Scryfall, tapetes reducidos a 1920 px de ancho (ANCHO_TAPETE, clave del índice t-<blob>; el tapete en sí sigue en blobs/) y el índice de clave de preparación a imagen mesa/imagenes.rs, mesa/preparar.rs, mesa/tapete.rs Sí (se vuelven a preparar al empezar o seguir una partida).
mesa/s/<id de Scryfall>-<cara>.jpg Las caras de Scryfall que enseña «Originales en mi idioma» (y las que una partida en red baja de Scryfall), tal como se bajaron mesa/imagenes.rs, mesa/originales.rs Sí (se vuelven a bajar al enseñarlas).
mesa/r/<xx>/<sha256>.jpg En una partida en red, los bytes de las imágenes propias de los demás tal como llegaron (los que dan la SHA-256, para pasárselos a otro); la copia saneada que se ve va a mesa/<xx>/ red/imagenes.rs Sí (se vuelven a pedir al enseñarlas).
scryfall-card-names.json, scryfall-set-codes.json Catálogo de nombres de cartas y códigos de sets de Scryfall para el reconocimiento local del OCR, renovado cada semana scryfall/nombres.rs Sí (se vuelve a descargar cuando hace falta; requiere red).
cockatrice-tokens.xml Base de datos de fichas de Cockatrice (solo se usan las fichas que Scryfall no tiene), renovada cada semana fuentes/cockatrice.rs Sí.
mtgjson/nombres.sqlite Nombres de cartas sin conexión en otros idiomas (opcional), construidos a partir de MTGJSON fuentes/mtgjson.rs Sí (una acción de Ajustes también lo elimina).
perfiles/<nombre>/ (+ webview/) La caché de una instancia de debug (PB_PERFIL), con su propia carpeta de datos de la vista web para que las instancias no compartan localStorage red/perfil.rs, lib.rs (crear_ventana) Sí.

Otras ubicaciones

  • Fichero de registro proxy-builder.log en el app_log_dir de Tauri (registro.rs): ahí se registra cada petición de red, para poder comprobar que abrir un mazo guardado no descarga nada.
  • La geometría de la ventana la guarda tauri-plugin-window-state (registrado en lib.rs, con el indicador de visibilidad excluido). Deducido: escribe en el directorio de configuración de la app; no se lee en este repo.
  • La gestión del paquete descargado por el actualizador y de su firma se describe en Versiones y actualizaciones automáticas.

Juego en línea

  • app_settings: red.apodo (apodo) y red.avatar (la huella de un blob, protegida con una fila de blob_refs de tipo red/avatar). El aspecto de los dados es mesa.dados (su imagen, mesa/dados).
  • El servidor de salas guarda su propio SQLite (jugadores, playgroups, invitaciones) en el servidor; las salas viven en su memoria. La app no guarda nada de ellas: tras un reinicio vuelve a registrar su sala. Ver Juego en línea.

Modos de fallo

Situación Resultado
Guardar un mazo cuya carta referencia una imagen que no está en blobs InvalidInput (motivo = blob_missing, blob = <hash>); el estado del autoguardado pasa a "error".
Dos cartas con el mismo id en un mazo InvalidInput (motivo = duplicate_card_id).
Bytes de imagen no admitidos o corruptos Unsupported / Image (motivo = corrupt).
Imagen de más de 12000 px por lado Image (motivo = too_large).
blob_gc mientras corre un trabajo o hay una partida abierta Busy.
JSON guardado que ya no se deserializa Db (con detalle).
Mutex de la base de datos envenenado Internal.

Los códigos de error se definen en src-tauri/src/error.rs; véase IPC y manejo de errores.

Por dónde empezar para ampliarlo

  • Columna persistida nueva: añade 000N_*.sql, añádelo a MIGRACIONES y no edites nunca los ficheros antiguos. Recuerda que el valor por defecto de ALTER TABLE ... ADD COLUMN debe valer para las filas existentes.
  • Datos nuevos que referencian una imagen: guarda su hash en un lugar protegido (véase la lista de la recolección) y añádelo a refs_del_deck / CardDesign::blobs().
  • Recurso descargado nuevo: elige app_data_dir si las funciones dependen de que siga ahí, app_cache_dir si se puede recrear por completo; escribe con escribir_atomico.

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