Ferred sigue una arquitectura hexagonal (Ports & Adapters) en el backend Express y un modelo offline-first en la capa de datos. Cada sucursal tiene su propia base de datos SQLite que actúa como fuente de verdad local. Cuando hay conectividad, elDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Carlos-Gnd/FERRED-Inventario-y-Ventas/llms.txt
Use this file to discover all available pages before exploring further.
SyncService propaga los cambios pendientes a Supabase (PostgreSQL) en la nube. El frontend React corre dentro de un proceso Electron que también inicia el servidor Express de forma embebida, por lo que la aplicación funciona completa sin necesidad de acceso a internet ni de ningún servidor externo.
Estructura del monorepo
El repositorio usa pnpm workspaces con tres aplicaciones principales:| Paquete | Ruta | Descripción |
|---|---|---|
renderer | apps/renderer | React + Vite + Tailwind + Zustand. Interfaz de usuario del POS. |
server | apps/server | Express.js con arquitectura hexagonal. Incluye adaptadores HTTP, base de datos, sync y DTE. |
electron | apps/electron | Wrapper de escritorio. Inicia el servidor Express como proceso hijo e inyecta SQLITE_PATH y BRANCH_ID. |
Diagrama de arquitectura
Arquitectura hexagonal — ports & adapters
El núcleo de negocio (casos de uso, entidades de dominio) no depende de ningún framework ni base de datos. Los adaptadores implementan los puertos definidos por el dominio.adapters/http
Rutas Express y middleware JWT. Cada recurso tiene su propio archivo de rutas:
auth.routes, ventas.routes, dte.routes, sync.routes, etc. El middleware jwtMiddleware protege todos los endpoints excepto /api/auth y /health.adapters/db
Dos implementaciones de persistencia en paralelo: Prisma para Supabase/PostgreSQL (operaciones en línea y sync) y better-sqlite3 para el SQLite local por sucursal (operaciones offline). El cliente SQLite se inicializa en
initSqlite() al arrancar el servidor.adapters/sync
SyncService ejecuta pushPendientes() cada 30 segundos. Lee hasta 50 registros de sync_log con estado PENDIENTE y los aplica en Supabase vía Prisma. OfflineCache guarda respuestas en memoria con TTL de 5 minutos para evitar lecturas redundantes cuando no hay internet.adapters/dte
Adaptador para la API del Ministerio de Hacienda de El Salvador (
apitest.dtes.mh.gob.sv en sandbox). Si las credenciales DTE no están configuradas, las facturas se generan en modo simulado localmente sin llamar al API externo.Flujo de datos offline-first
Toda operación de escritura sigue este camino, independientemente de si hay conexión:- El adaptador HTTP recibe la petición y ejecuta la lógica de negocio.
- El resultado se persiste en SQLite local (
ferred_branch{BRANCH_ID}.db). - Se llama a
logPendiente(tabla, operacion, payload), que escribe un registro en la tablasync_logcon estadoPENDIENTE. - Si en ese momento hay internet,
logPendientetambién crea el registro en elsyncLogremoto de Supabase vía Prisma. - Si no hay internet, el registro queda solo en SQLite local hasta que
SyncServicelo detecte en el próximo ciclo de 30 segundos.
Pipeline de sincronización
| Paso | Función | Descripción |
|---|---|---|
| 1 | logPendienteLocal | Inserta en sync_log de SQLite con status PENDIENTE. |
| 2 | SyncService.checkConnectivity | Prueba SELECT 1 contra Supabase vía Prisma para determinar si hay internet. |
| 3 | SyncService.pushPendientes | Lee hasta 50 registros pendientes con menos de 5 intentos fallidos. |
| 4 | SyncService.aplicarOperacion | Aplica CREATE, UPDATE o DELETE en Supabase usando el modelo Prisma correspondiente. |
| 5 | marcarSincronizado | Actualiza el registro en sync_log a SINCRONIZADO y registra el timestamp. |
| 6 | marcarError | Incrementa el contador de intentos. Tras 5 fallos, cambia el status a ERROR. |
Despliegue en producción
Ferred usa tres servicios de nube complementarios: Netlify sirve el frontend estático (rama
main con deploy automático), Railway aloja el servidor Express en contenedor (también rama main, deploy automático), y Supabase provee PostgreSQL 15 siempre activo como base de datos centralizada. Cada instalación de escritorio Electron corre su propio Express embebido y su propio SQLite, independiente de Railway.| Entorno | URL | Servicio |
|---|---|---|
| Frontend | https://ferred.netlify.app | Netlify (CDN estático) |
| Backend API | https://server-production-3252.up.railway.app | Railway (contenedor Node.js) |
| Base de datos | Proyecto Supabase (privado) | Supabase PostgreSQL 15 |
| Desktop local | http://127.0.0.1:3001 (embebido) | Express dentro de Electron |