Ferred uses a last-write-wins strategy for most entities, with upsert operations that prefer the latest local change. Stock quantities use a special totalizer approach to avoid losing mutations from concurrent branches.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.
Estrategia last-write-wins
WhenpushPendientes() applies a pending entry to Supabase, it calls aplicarOperacion(), which issues an upsert keyed on id for most tables. This means the local value unconditionally overwrites whatever is in the remote database at that moment.
This is intentional: the branch that syncs last wins. For most POS entities — categories, supplier records, user profiles — this is acceptable because updates are infrequent and branch-local. The risk of a meaningful conflict is low.
sync.service.ts
Stock total: sincronizarStockTotal()
Branch-level stock (stockSucursal) is managed per-branch and is subject to concurrent writes. To avoid one branch’s sync overwriting another’s quantity, the stockActual field on the Producto record is always recomputed by aggregating all branch stocks — never set directly.
sincronizarStockTotal() (in stock-sync.service.ts) runs after every stock mutation and after every inter-branch transfer:
inventario.routes.ts
stockActual is always a derived value — the sum across all stockSucursal rows for that product. Even if two branches sync overlapping mutations, the final aggregate is correct because each branch owns its own row.
Productos offline nuevos (id < 0)
Products created while offline are inserted into the local SQLite database with auto-incremented IDs. When returned to the client, these IDs are negated (id: -Math.abs(localId)) to signal that the record has not been assigned a real PostgreSQL ID yet.
During sync, crearProductoDesdePendiente() resolves the conflict by upserting on codigoBarras (barcode) rather than id:
sync.service.ts
Limpiar payload
Before any sync write,limpiarPayload() strips the payload down to the scalar fields defined in CAMPOS_ESCALARES for that table. This removes nested relations, computed fields, and any client-only metadata that would cause Prisma to reject the operation.
sync.service.ts
producto payload that includes a nested categoria object or a stocks array will have those fields silently dropped before the upsert is issued.
Reintentos y límite de errores
Eachsync_log entry tracks the number of failed attempts in the intentos column. On every failed sync, marcarError() increments the counter. When intentos reaches MAX_INTENTOS = 5, the status transitions to ERROR and the entry is excluded from future sync cycles.
sync.local.ts
ERROR status must be resolved manually by inspecting the error column for the root cause, correcting the underlying data issue if necessary, and resetting the status to PENDIENTE.
Operaciones no soportadas
IfaplicarOperacion() receives a table name not in TABLAS_PERMITIDAS, it throws immediately. The exception is caught by pushPendientes(), which calls marcarError() and moves on to the next entry. The table name is recorded in the error column of the sync entry.