Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Davidmallega/Gastos-App/llms.txt

Use this file to discover all available pages before exploring further.

El estado global de GastosApp no usa Redux, Zustand ni React Context: vive en una variable de módulo (globalState) declarada a nivel de archivo en src/store/useStore.js. Este patrón singleton garantiza que todos los componentes que llamen a useStore() compartan exactamente el mismo estado en memoria, y que cada mutación persista de forma inmediata en localStorage y notifique a todos los consumidores registrados. La simplicidad intencional de este diseño refleja la naturaleza de GastosApp: una aplicación de escritorio de usuario único sin necesidad de concurrencia ni middleware.

Patrón singleton

El corazón del store son tres elementos a nivel de módulo:
let globalState = null   // estado en memoria (único para toda la app)
let listeners = []       // callbacks de los componentes suscritos
La función setState es el único punto de escritura del estado. Acepta tanto un objeto parcial como una función actualizadora (el mismo patrón de useState):
function setState(updater) {
  const prev = getState()
  const next = typeof updater === 'function' ? updater(prev) : { ...prev, ...updater }
  globalState = next
  saveToStorage(next)
  listeners.forEach(fn => fn(next))
}
  1. Se obtiene el estado previo (o se carga desde localStorage si es la primera llamada).
  2. Se calcula el estado siguiente aplicando el updater.
  3. Se actualiza globalState en memoria.
  4. Se persiste en localStorage de forma síncrona e inmediata.
  5. Se notifica a todos los componentes suscritos para que re-rendericen.
getState() implementa lazy initialization: si globalState es null, llama a loadFromStorage(), que lee localStorage, aplica migraciones y purga la papelera antes de retornar el estado inicial.

Hook useStore()

El hook useStore() conecta el singleton al ciclo de vida de React:
export function useStore() {
  const [state, setLocalState] = useState(getState)

  useEffect(() => {
    const listener = (s) => setLocalState({ ...s })
    listeners.push(listener)
    return () => { listeners = listeners.filter(l => l !== listener) }
  }, [])

  // ... funciones de mutación
  return { ...state, addGastoCaja, updateFactura, ... }
}
Al montar el componente, useState(getState) inicializa el estado local con el valor actual del singleton (sin causar una lectura extra de localStorage). El useEffect registra un listener en el array global y lo desregistra al desmontar. Cada vez que cualquier componente llama a una función de mutación (como addFactura o deleteGastoCaja), setState persiste el cambio y ejecuta todos los listeners, lo que desencadena un re-render en todos los componentes que tengan useStore() montado.

Estado inicial

Si no hay datos previos en localStorage, el store se inicializa con defaultState:
const defaultState = {
  gastosCaja: [],
  facturas: [],
  compromisos: [],
  categorias: [
    'Arriendo',
    'Luz / Electricidad',
    'Agua',
    'Gas',
    'Teléfono / Internet',
    'Personal / Remuneraciones',
    'Insumos / Materiales',
    'Transporte',
    'Mantención',
    'Servicios Externos',
    'Impuestos / Patentes',
    'Seguros',
    'Otros',
  ],
  proveedores: [],
  papelera: [],
}
Las 13 categorías por defecto cubren los rubros de gasto más comunes para una empresa chilena pequeña. El usuario puede agregar, renombrar o eliminar categorías desde el módulo de Categorías en la app.

Migraciones de esquema

La constante SCHEMA_VERSION = 4 define la versión actual del schema. Al cargar datos desde localStorage o al importar un backup, la función migrateData aplica secuencialmente todos los bloques de migración necesarios:
function migrateData(data) {
  let d = { ...data }
  const v = d.schemaVersion || 0

  // v0 → v1: proveedores strings → objetos
  if (v < 1 && d.proveedores?.length > 0 && typeof d.proveedores[0] === 'string') {
    d.proveedores = d.proveedores.map((name, i) => ({
      id: 'legacy_' + i, rut: '', razonSocial: name,
      nombreFantasia: '', contacto: '', categoria: '', observaciones: '',
    }))
  }

  // v1 → v2: agregar facturaRefId a facturas existentes
  if (v < 2 && d.facturas) {
    d.facturas = d.facturas.map(f => ({ facturaRefId: null, ...f }))
  }

  // v2 → v3: agregar papelera
  if (v < 3) {
    d.papelera = d.papelera || []
  }

  // v3 → v4: agregar plazoPago a proveedores existentes
  if (v < 4 && d.proveedores) {
    d.proveedores = d.proveedores.map(p => ({ plazoPago: null, ...p }))
  }

  d.schemaVersion = SCHEMA_VERSION
  return d
}
Cada bloque de migración es idempotente y aditivo: solo agrega campos o transforma datos existentes, nunca elimina información. Esto garantiza que un backup creado con la v1 de la app pueda importarse correctamente en la v4 sin pérdida de datos.
VersiónCambio
v0 → v1proveedores era un array de strings; se convierte a array de objetos con id, rut, razonSocial, etc. Los proveedores migrados reciben IDs del tipo 'legacy_0', 'legacy_1'.
v1 → v2Se agrega el campo facturaRefId: null a todas las facturas existentes para soportar la vinculación de Notas de Crédito.
v2 → v3Se agrega el array papelera: [] para soportar el soft-delete. Los datos anteriores no tienen papelera, así que se inicializa vacía.
v3 → v4Se agrega plazoPago: null a todos los proveedores existentes para soportar el cálculo de fecha de vencimiento por proveedor.

API completa del hook

useStore() retorna el estado completo del store junto con todas las funciones de mutación. Las funciones están memoizadas con useCallback (dependencias vacías) para no causar re-renders innecesarios en componentes hijos.

Gastos Caja

FunciónDescripción
addGastoCaja(gasto)Agrega un nuevo gasto de caja. Retorna el id generado.
addGastosCaja(gastos[])Importación masiva (usada por el importador CSV). Agrega múltiples gastos en una sola mutación.
updateGastoCaja(id, data)Actualiza parcialmente un gasto por su id.
deleteGastoCaja(id)Mueve el gasto a papelera[] con deletedAt y deletedFrom: 'gastosCaja'.

Facturas

FunciónDescripción
addFactura(factura)Agrega una nueva factura o NC. Retorna el id generado.
addFacturas(facturas[])Importación masiva desde CSV SII.
updateFactura(id, data)Actualiza parcialmente una factura por su id.
deleteFactura(id)Mueve la factura a papelera[] con deletedFrom: 'facturas'.
marcarPagado(tipo, id, metodoPago, fechaPago)Marca un documento (caja o factura) como pagado. Si no se provee fechaPago, usa la fecha actual.
revertirPago(tipo, id)Revierte un pago: establece pagado: false y fechaPago: null.

Categorías

FunciónDescripción
addCategoria(nombre)Agrega una categoría al array. Usa Set para evitar duplicados.
updateCategoria(oldName, newName)Renombra la categoría en el catálogo y en todos los proveedores. No actualiza documentos (eso lo hace cascadeCategoriaRename).
deleteCategoria(nombre)Elimina la categoría del catálogo. No afecta documentos existentes.
cascadeCategoriaRename(oldName, newName, scope)Actualiza la categoría en gastos y facturas. scope puede ser 'pending' (solo pendientes), 'all' (todos) o cualquier otro valor (ninguno).

Proveedores

FunciónDescripción
addProveedor(nameOrObj)Acepta un string (crea proveedor mínimo) o un objeto completo. Previene duplicados comparando razón social y nombre fantasía en minúsculas.
updateProveedor(id, data)Actualiza parcialmente un proveedor.
deleteProveedor(id)Elimina el proveedor del directorio. No va a papelera.
cascadeProveedorCategoria(prov, newCategoria, scope)Actualiza la categoría en documentos asociados al proveedor. La coincidencia se hace por razón social, nombre fantasía o RUT.

Compromisos

FunciónDescripción
addCompromiso(comp)Crea un nuevo compromiso con activo: true, ultimoRegistro: null y creadoEn con la fecha actual. Retorna el id.
updateCompromiso(id, data)Actualiza parcialmente un compromiso.
deleteCompromiso(id)Mueve el compromiso a papelera[] con deletedFrom: 'compromisos'.
registrarCompromiso(id, overrides)Genera un documento (GastoCaja o Factura) a partir del compromiso y actualiza ultimoRegistro y ultimoRegistroId. Si tipo es 'factura', calcula neto e IVA automáticamente.

Papelera

FunciónDescripción
restaurarDePapelera(id)Mueve el ítem de vuelta a su array de origen (gastosCaja, facturas o compromisos) eliminando los campos deletedAt y deletedFrom.
eliminarDePapelera(id)Elimina permanentemente un ítem de la papelera. No se puede deshacer.
vaciarPapelera()Elimina permanentemente todos los ítems de papelera[].

Datos

FunciónDescripción
exportData()Serializa el estado completo como JSON y lo descarga como gastosapp_backup_YYYY-MM-DD.json. Incluye schemaVersion, exportedAt y appName en el payload.
importData(raw)Aplica migrateData al objeto importado y reemplaza el estado completo. Se usa desde el módulo de Respaldo.
reloadData()Recarga el estado desde localStorage y notifica a todos los listeners. Útil para sincronizar si los datos se modificaron externamente.

Clave de almacenamiento

Todo el estado se persiste bajo una única clave de localStorage:
localStorage.getItem('gastos_app_data')
// Retorna el JSON completo del estado, incluyendo schemaVersion: 4
Para inspeccionar o depurar los datos directamente, abre las DevTools de Electron (disponibles en modo desarrollo) y ejecuta:
JSON.parse(localStorage.getItem('gastos_app_data'))
El objeto retornado contiene los arrays gastosCaja, facturas, compromisos, categorias, proveedores y papelera, además de schemaVersion: 4.
El límite de localStorage en Electron es aproximadamente 5 MB. Para empresas con muchos registros, se recomienda exportar backups periódicos desde el módulo de Respaldo. Si el volumen de datos crece significativamente, está documentada en la deuda técnica la migración a SQLite (better-sqlite3) como alternativa con mayor capacidad y rendimiento.

Build docs developers (and LLMs) love