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.

GastosApp es una aplicación de escritorio construida con Electron 42, React 19 y Vite 8. El proceso de desarrollo distingue dos modos: uno puramente web (útil para iterar sobre la UI con hot-reload rápido) y otro completo con Electron donde el proceso principal y el renderer corren en paralelo. Esta guía cubre las convenciones y patrones que rigen la base de código para que cualquier colaborador pueda incorporarse sin fricciones.

Iniciar el entorno

# Levanta solo el servidor Vite en http://localhost:5173
# Ideal para desarrollo de UI: hot-reload instantáneo, sin ventana Electron
npm run dev
La diferencia clave es que npm run dev no instancia el proceso principal de Electron (electron/main.js), por lo que window.electronAPI no estará disponible. Para las operaciones de control de ventana (minimizar, maximizar, cerrar) es necesario usar npm run electron:dev.

Estructura del proyecto

gastos-app/
├── electron/
│   ├── main.js         # Proceso principal (ESM) — BrowserWindow, IPC, setWindowOpenHandler
│   └── preload.cjs     # Bridge IPC (CommonJS) — expone window.electronAPI via contextBridge
├── src/
│   ├── components/
│   │   ├── layout/
│   │   │   └── Sidebar.jsx       # Navegación lateral colapsable (12 ítems)
│   │   └── ui/
│   │       └── index.jsx         # UI primitives: PageHeader, Card, StatCard, Btn, Badge,
│   │                             # Input, InputMonto, Select, Modal, Dialog, Table, etc.
│   ├── pages/                    # 12 componentes de página
│   │   ├── Dashboard.jsx
│   │   ├── GastosCaja.jsx
│   │   ├── Facturas.jsx
│   │   ├── Pendientes.jsx
│   │   ├── PagosRealizados.jsx
│   │   ├── Compromisos.jsx
│   │   ├── Informes.jsx
│   │   ├── Categorias.jsx
│   │   ├── Proveedores.jsx
│   │   ├── ImportarCSV.jsx
│   │   ├── BackupRestore.jsx
│   │   └── Papelera.jsx
│   ├── store/
│   │   └── useStore.js           # Estado global singleton + persistencia en localStorage
│   └── utils/
│       ├── format.js             # formatCLP, formatDate, calcTotals, getPendienteHealth…
│       ├── compromisos.js        # getCompromisoStatus, getStatusMeta, getDueDateLabel…
│       └── sounds.js             # playPaymentSound (Web Audio API, sin archivos externos)
├── tests/
│   ├── format.test.js
│   └── compromisos.test.js
├── documentacion/
│   └── sdlc/                     # Documentación SDLC completa (10 fases)
├── vite.config.js
├── eslint.config.js
└── package.json

Componentes UI reutilizables

Todos los primitivos de interfaz se exportan desde src/components/ui/index.jsx. Usarlos garantiza consistencia visual y respeta las variables CSS del sistema de diseño (temas claro/oscuro).
ComponenteProps principalesDescripción
PageHeadertitle, subtitle, actionsCabecera de página con título h1, subtítulo opcional y área de acciones (botones) a la derecha
Cardchildren, style, classNameContenedor con fondo var(--bg-card), borde y borderRadius: 12
StatCardlabel, value, sub, pre, color, iconTarjeta KPI con etiqueta, valor principal, sub-texto y valor tachado opcional (pre)
Btnvariant, size, onClick, disabledBotón con 5 variantes: primary, secondary, danger, ghost, success; 3 tamaños: sm, md, lg
Badgelabel, colorChip de estado con 5 colores: blue, green, red, yellow, gray
Inputlabel, value, onChange, type, required, placeholderInput estilizado con label, foco en acento y soporte para datalist
InputMontolabel, value, onChange, requiredInput numérico para montos CLP: muestra formato con puntos al perder el foco, solo dígitos al editar
Selectlabel, value, onChange, options, requiredSelect estilizado que acepta array de strings o de { value, label }
Modalopen, onClose, title, widthModal de formulario con overlay, cierre por Escape, montado en document.body vía createPortal
Dialogopen, onClose, title, message, onConfirm, variantDiálogo de confirmación con 3 variantes: danger, warning, info; montado vía createPortal
Tableheaders, sortKey, sortDir, onSortTabla con cabeceras ordenables; headers acepta strings o { label, align, key }
Para filas y celdas de tabla se usan los sub-componentes TR y TD. TD acepta los props mono (fuente tabular), right (alineación) y muted (color secundario).

Patrones de código

GastosApp no usa React Router. La navegación entre páginas se implementa con eventos del DOM para evitar prop drilling y sin instalar dependencias adicionales:
// Disparar navegación desde cualquier componente anidado:
window.dispatchEvent(new CustomEvent('navigate', { detail: 'Compromisos' }))

// App.jsx escucha el evento y actualiza el estado de página activa:
window.addEventListener('navigate', (e) => setCurrentPage(e.detail))
Los componentes Modal y Dialog usan createPortal(content, document.body) para montarse directamente en <body>, fuera del árbol del Sidebar. Esto es necesario porque el Sidebar tiene overflow: hidden, que en Chromium/Electron recorta cualquier position: fixed anidado dentro de ese contexto:
import { createPortal } from 'react-dom'

// Dentro de Modal:
return createPortal(
  <>
    <div style={{ position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.7)', zIndex: 1000 }} />
    <div style={{ position: 'fixed', inset: 0, zIndex: 1001, ... }}>
      {/* contenido del modal */}
    </div>
  </>,
  document.body
)

Fechas como string 'YYYY-MM-DD'

Todos los campos de fecha en el store se almacenan como string 'YYYY-MM-DD', nunca como objetos Date ni como ISO strings UTC. Esto evita por completo los desfases de zona horaria en Electron. Para convertir entre formatos se usan exclusivamente las funciones de utils/format.js (parseLocalDate, formatDate, addDays).
// ✅ Correcto — fecha como string local
const gasto = { fecha: '2025-06-15', ... }

// ❌ Evitar — new Date() serializa en UTC y puede desfasar un día en Chile
const gasto = { fecha: new Date().toISOString(), ... }

Soft delete con papelera

Ningún registro se elimina físicamente del store. Al borrar cualquier documento desde un módulo, el ítem se mueve a state.papelera[] con los campos deletedAt (ISO string) y deletedFrom (nombre del módulo de origen). Al arrancar la app se ejecuta una auto-purga silenciosa que descarta los ítems con más de 30 días en papelera.

ESLint

La configuración eslint.config.js usa el formato plano (flat config) de ESLint 10 con tres extensiones:
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
  globalIgnores(['dist']),
  {
    files: ['**/*.{js,jsx}'],
    extends: [
      js.configs.recommended,
      reactHooks.configs.flat.recommended,
      reactRefresh.configs.vite,
    ],
    languageOptions: {
      globals: globals.browser,
      parserOptions: { ecmaFeatures: { jsx: true } },
    },
  },
])
Los tres plugins activos son:
  • @eslint/js — reglas base recomendadas de JavaScript
  • eslint-plugin-react-hooks — valida las reglas de hooks (exhaustive-deps, rules-of-hooks)
  • eslint-plugin-react-refresh — asegura que los módulos sean compatibles con el HMR de Vite
La carpeta dist/ queda excluida globalmente mediante globalIgnores.
Para añadir un nuevo módulo, crear el componente en src/pages/, agregar la ruta en App.jsx y el ítem correspondiente en Sidebar.jsx.

Build docs developers (and LLMs) love