Skip to main content

Documentation 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.

Ferred is built offline-first — every branch has a local SQLite database that serves as the primary data source. The system automatically falls back to SQLite when Supabase is unreachable and syncs changes when connectivity is restored.

¿Por qué offline-first?

Hardware stores cannot afford downtime at the point of sale. Internet connectivity in retail environments — particularly in smaller cities and industrial zones — is frequently unreliable. A dropped connection to Supabase that blocks sales registration translates directly into lost revenue and frustrated customers. Ferred solves this by treating the remote database as an eventual-consistency target rather than a hard dependency. Every branch operates independently and reconciles with the cloud when the link is available.

SQLite local por sucursal

Each branch runs its own SQLite database file, initialized on server startup via initSqlite() in sqlite.client.ts. The database path is resolved from the env.sqlite.path configuration variable and defaults to the data/ directory.
sqlite.client.ts
export function initSqlite(): Database.Database {
  if (_db) return _db;

  const dbPath = env.sqlite.path;
  fs.mkdirSync(path.dirname(dbPath), { recursive: true });

  _db = new Database(dbPath);
  _db.pragma('journal_mode = WAL');
  _db.pragma('foreign_keys = ON');

  const schema = readSchema();
  _db.exec(schema);

  return _db;
}
WAL (Write-Ahead Logging) mode is enabled so reads never block writes, which is critical during concurrent sales and background sync operations.

Detección de conectividad

SyncService.checkConnectivity() determines online status by issuing a raw SELECT 1 query through Prisma. If the query succeeds the service is considered online; any exception flips the flag to offline and notifies registered listeners.
sync.service.ts
async checkConnectivity(): Promise<boolean> {
  try {
    await prisma.$queryRaw`SELECT 1`;
    setOnline(true);
    return true;
  } catch {
    setOnline(false);
    return false;
  }
},
Listeners can subscribe to connectivity changes with onConnectivityChange(cb), which is useful for UI status indicators and conditional feature gating.

Fallback automático

Routes that query inventory first call SyncService.checkConnectivity(). When offline, they read directly from SQLite. When online, they query Prisma and cache the result. A secondary catch block also catches mid-request connection failures using esErrorConexionPrisma(), which inspects Prisma error codes (P1001, P1002, P1008, P1017) and common connection-refused messages.
inventario.routes.ts
const online = await SyncService.checkConnectivity();

if (!online) {
  console.info(`[inventario.stock] modo=offline origen=sqlite sucursalId=${sucursalId}`);
  return res.json(obtenerStockSucursalSqlite(sucursalId));
}

// ... Prisma query ...

} catch (err) {
  if (esErrorConexionPrisma(err)) {
    console.info(`[inventario.stock] modo=offline origen=sqlite motivo=prisma_caido`);
    return res.json(obtenerStockSucursalSqlite(sucursalId));
  }
  return next(err);
}

OfflineCache

OfflineCache is an in-memory key-value store with a five-minute time-to-live. It avoids redundant SQLite reads for frequently accessed data like product lists and branch stock.
sync.service.ts
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes

export const OfflineCache = {
  set(key: string, data: unknown) {
    cache.set(key, { data, expiresAt: Date.now() + CACHE_TTL });
  },

  get<T>(key: string): T | null {
    const entry = cache.get(key);
    if (!entry) return null;
    if (Date.now() > entry.expiresAt) {
      cache.delete(key);
      return null;
    }
    return entry.data as T;
  },

  invalidate(prefix: string) {
    for (const key of cache.keys()) {
      if (key.startsWith(prefix)) cache.delete(key);
    }
  },
};
Cache entries are invalidated on any mutation (stock adjustment, product creation, transfer) by calling OfflineCache.invalidate(prefix) with the relevant key prefix (e.g., stock:${sucursalId}).

Operaciones en modo offline

Disponible sin conexión

  • Login de usuario (contra hash local)
  • Consulta de productos y categorías
  • Registro de ventas
  • Consulta de stock por sucursal
  • Recepción de mercancía

Requiere conexión

  • Emisión de DTE a Hacienda
  • Consulta de stock comparativo multi-sucursal
  • Transferencias entre sucursales
  • Reportes consolidados de administración
During offline login, the server responds with the header X-Auth-Mode: offline to signal to the client that authentication was resolved locally against the SQLite user store rather than Supabase. The client can use this header to display a connectivity warning in the UI.

Build docs developers (and LLMs) love