Tutorial de Tauri – Capítulo 9: Acceso al sistema de archivos

Una de las razones principales para usar una aplicación de escritorio es el acceso al sistema de archivos. Las aplicaciones web no pueden leer ni escribir archivos en tu ordenador, pero con Tauri sí puedes.

En este capítulo vamos a crear un proyecto nuevo para construir un editor de notas sencillo que guarde y cargue archivos de verdad.

Crear el proyecto

Si tienes la aplicación anterior ejecutándose, párala con Ctrl+C en la terminal. Vamos a crear un proyecto nuevo para empezar limpio.

Abre la terminal y ejecuta:

npm create tauri-app@latest

Responde a las preguntas:

  • Project name: tauri-notas
  • Identifier: com.tutorial.taurinotas
  • Frontend language: TypeScript / JavaScript
  • Package manager: npm
  • UI template: React
  • UI flavor: JavaScript

Entra en el proyecto e instala las dependencias:

cd tauri-notas
npm install

Instalar el plugin de sistema de archivos

Tauri 2.0 usa un sistema de plugins para funcionalidades extra. El acceso al sistema de archivos no viene incluido por defecto — necesitas instalar el plugin fs. Ejecuta este comando desde la raíz del proyecto:

npm run tauri add fs

Este comando hace todo automáticamente:

  • Instala el paquete npm @tauri-apps/plugin-fs
  • Añade tauri-plugin-fs a las dependencias de src-tauri/Cargo.toml
  • Registra el plugin en src-tauri/src/lib.rs

Si quieres verificar que todo se ha instalado, abre src-tauri/src/lib.rs. Deberías ver algo como:

pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_fs::init())
        .plugin(tauri_plugin_opener::init())
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

La línea .plugin(tauri_plugin_fs::init()) es la que activa el plugin. Si no aparece, añádela manualmente.

Configurar permisos

Tauri tiene un sistema de seguridad que impide que tu aplicación acceda a archivos sin permiso explícito. Esto es una ventaja: el usuario sabe exactamente qué puede hacer tu app.

Los permisos se configuran en el archivo src-tauri/capabilities/default.json. Ábrelo y reemplaza su contenido por:

{
  "identifier": "default",
  "windows": ["main"],
  "permissions": [
    "core:default",
    "opener:default",
    {
      "identifier": "fs:allow-read-text-file",
      "allow": [{ "path": "$APPDATA/**" }]
    },
    {
      "identifier": "fs:allow-write-text-file",
      "allow": [{ "path": "$APPDATA/**" }]
    },
    {
      "identifier": "fs:allow-exists",
      "allow": [{ "path": "$APPDATA/**" }]
    },
    {
      "identifier": "fs:allow-mkdir",
      "allow": [{ "path": "$APPDATA" }, { "path": "$APPDATA/**" }]
    }
  ]
}

Veamos qué significa cada permiso:

  • core:default: permisos básicos de Tauri (ventana, commands, etc.)
  • opener:default: permite abrir enlaces externos (viene por defecto en proyectos nuevos)
  • fs:allow-read-text-file: permite leer archivos de texto en las carpetas especificadas en allow
  • fs:allow-write-text-file: permite escribir archivos de texto
  • fs:allow-exists: permite comprobar si un archivo existe
  • fs:allow-mkdir: permite crear carpetas
  • "allow": [{ "path": "$APPDATA/**" }]: es el scope, que limita cada permiso a la carpeta de datos de la app. Sin el scope, Tauri bloquea el acceso por seguridad. El ** significa «esta carpeta y todas las subcarpetas».

Directorios seguros (BaseDirectory)

Tu aplicación no puede acceder a cualquier carpeta del sistema. Tauri limita el acceso a directorios específicos y seguros. Cada uno tiene un nombre que usarás en tu código:

BaseDirectoryDescripciónEjemplo en macOS
AppDataDatos de tu aplicación~/Library/Application Support/com.tutorial.taurinotas
AppConfigConfiguración de tu app~/Library/Application Support/com.tutorial.taurinotas
DocumentCarpeta Documentos del usuario~/Documents
DownloadCarpeta Descargas~/Downloads
DesktopEscritorio~/Desktop
TempCarpeta temporal/tmp

Para nuestro editor de notas usaremos AppData, que es la carpeta propia de tu aplicación. Es el lugar correcto para guardar datos que la app necesita recordar.

Construir el editor de notas

Vamos a crear un editor de notas sencillo que:

  • Cargue la nota guardada al abrir la app
  • Guarde el contenido en un archivo cuando pulses «Guardar»
  • Muestre un mensaje de confirmación al guardar

Primero, borra los archivos que no necesitamos. Elimina src/App.css y el contenido por defecto de src/assets/ si quieres empezar limpio (es opcional).

Ahora crea el componente del editor. Crea el archivo src/EditorNotas.jsx:

import { useState, useEffect } from "react";
import {
  readTextFile,
  writeTextFile,
  exists,
  mkdir,
  BaseDirectory,
} from "@tauri-apps/plugin-fs";

function EditorNotas() {
  const [nota, setNota] = useState("");
  const [guardando, setGuardando] = useState(false);
  const [mensaje, setMensaje] = useState("");

  // Al montar el componente, cargar la nota guardada
  useEffect(() => {
    cargarNota();
  }, []);

  async function cargarNota() {
    try {
      // Comprobar si el archivo existe antes de leerlo
      const hayArchivo = await exists("mi-nota.txt", {
        baseDir: BaseDirectory.AppData,
      });

      if (hayArchivo) {
        const contenido = await readTextFile("mi-nota.txt", {
          baseDir: BaseDirectory.AppData,
        });
        setNota(contenido);
        setMensaje("Nota cargada");
      } else {
        setMensaje("No hay nota guardada. Escribe algo y pulsa Guardar.");
      }
    } catch (error) {
      console.error("Error cargando la nota:", error);
      setMensaje("Error al cargar la nota");
    }
  }

  async function guardarNota() {
    setGuardando(true);
    try {
      // Crear la carpeta AppData si no existe
      await mkdir("", {
        baseDir: BaseDirectory.AppData,
        recursive: true,
      });

      // Escribir el archivo
      await writeTextFile("mi-nota.txt", nota, {
        baseDir: BaseDirectory.AppData,
      });

      setMensaje("Nota guardada correctamente");
    } catch (error) {
      console.error("Error guardando:", error);
      setMensaje("Error al guardar la nota");
    }
    setGuardando(false);
  }

  return (
    <div style={{ maxWidth: "600px", margin: "0 auto", padding: "2rem" }}>
      <h1>Editor de Notas</h1>

      <textarea
        value={nota}
        onChange={(e) => setNota(e.target.value)}
        placeholder="Escribe tu nota aquí..."
        rows={12}
        style={{
          width: "100%",
          padding: "1rem",
          fontSize: "1rem",
          borderRadius: "8px",
          border: "1px solid #444",
          background: "#2a2a2a",
          color: "white",
          resize: "vertical",
        }}
      />

      <div style={{ marginTop: "1rem", display: "flex", alignItems: "center", gap: "1rem" }}>
        <button
          onClick={guardarNota}
          disabled={guardando}
          style={{
            padding: "0.8rem 2rem",
            background: "#24c8db",
            color: "#1a1a1a",
            border: "none",
            borderRadius: "8px",
            fontWeight: "bold",
            cursor: "pointer",
          }}
        >
          {guardando ? "Guardando..." : "Guardar"}
        </button>
        <span>{mensaje}</span>
      </div>
    </div>
  );
}

export default EditorNotas;

Veamos qué hace cada parte del código:

  • import { readTextFile, writeTextFile, exists, mkdir, BaseDirectory } from "@tauri-apps/plugin-fs": importamos las funciones del plugin de archivos. Fíjate en que se importan de @tauri-apps/plugin-fs, no de @tauri-apps/api.
  • useEffect(() => { cargarNota(); }, []): al abrir la app, intenta cargar la nota guardada.
  • exists("mi-nota.txt", { baseDir: BaseDirectory.AppData }): comprueba si el archivo existe antes de intentar leerlo. Si no lo comprobáramos y el archivo no existe, daría error.
  • readTextFile("mi-nota.txt", { baseDir: ... }): lee el contenido del archivo de texto. El baseDir le dice a Tauri en qué carpeta buscar.
  • mkdir("", { baseDir: BaseDirectory.AppData, recursive: true }): crea la carpeta de datos de la app si no existe. La primera vez que ejecutas la app, esta carpeta puede no existir todavía.
  • writeTextFile("mi-nota.txt", nota, { baseDir: ... }): escribe el contenido del textarea en el archivo.

Ahora abre src/App.jsx y reemplaza todo su contenido por:

import EditorNotas from "./EditorNotas";

function App() {
  return <EditorNotas />;
}

export default App;

Ejecuta la aplicación:

npm run tauri dev

Escribe algo en el textarea y pulsa «Guardar». Cierra la aplicación y vuelve a abrirla — tu nota seguirá ahí, cargada desde el archivo.

Referencia: otras operaciones con archivos

El plugin de archivos ofrece más funciones además de leer y escribir. No las necesitas para el editor de notas, pero te serán útiles en proyectos futuros:

import {
  readDir,
  remove,
  rename,
  copyFile,
  BaseDirectory,
} from "@tauri-apps/plugin-fs";

// Listar archivos de una carpeta
const archivos = await readDir("", {
  baseDir: BaseDirectory.Document,
});
archivos.forEach((archivo) => {
  console.log(archivo.name); // nombre de cada archivo
});

// Eliminar un archivo
await remove("archivo-viejo.txt", {
  baseDir: BaseDirectory.AppData,
});

// Renombrar un archivo
await rename("nombre-viejo.txt", "nombre-nuevo.txt", {
  oldPathBaseDir: BaseDirectory.AppData,
  newPathBaseDir: BaseDirectory.AppData,
});

// Copiar un archivo
await copyFile("original.txt", "copia.txt", {
  fromPathBaseDir: BaseDirectory.AppData,
  toPathBaseDir: BaseDirectory.AppData,
});

Resumen

En este capítulo has aprendido a:

  • Instalar plugins de Tauri con npm run tauri add
  • Configurar permisos en capabilities/default.json
  • Usar BaseDirectory para acceder a carpetas seguras
  • Leer archivos con readTextFile
  • Escribir archivos con writeTextFile
  • Comprobar si un archivo existe con exists
  • Crear carpetas con mkdir

En el próximo capítulo trabajaremos con bases de datos SQLite para guardar datos estructurados.

Nos vemos en el Capítulo 10.


¿Te está gustando este tutorial?

Este tutorial forma parte del libro Tauri 2.0: Aplicaciones de Escritorio con React y Rust, disponible en Amazon en formato papel y ebook. El libro incluye todos los capítulos, tres proyectos completos paso a paso y contenido exclusivo que no encontrarás en el blog.

Un saludo, y si aún no lo has hecho no olvides suscribirte a mi blog para no perderte los próximos posts  :-),

También puedes seguirme en Twitter en ‎@revi_apps y no olvides que me ayudas mucho si compartes este post en las redes sociales.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Scroll al inicio