Files
Autoparts-DB/pos/static/js/sync-engine.js
consultoria-as 32581739ad feat(pos): PWA con Service Worker + sync offline
- Add manifest.json, sw.js with cache-first/network-first strategies
- Add sync-engine.js with IndexedDB queue for offline operations
- Add /pos/api/sync/inventory endpoint for offline inventory cache
- Add /pos/sw.js route for proper SW scope registration
- Generate PWA icons (192x192, 512x512)
- Update all 10 HTML templates with manifest link, theme-color meta,
  SW registration, and sync-engine script

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 07:18:01 +00:00

205 lines
7.4 KiB
JavaScript

// /home/Autopartes/pos/static/js/sync-engine.js
// Nexus POS — Offline sync engine using IndexedDB
(function () {
'use strict';
var DB_NAME = 'nexus_pos_offline';
var DB_VERSION = 1;
var QUEUE_STORE = 'sync_queue';
var INVENTORY_STORE = 'inventory_cache';
var db = null;
// ─── IndexedDB setup ──────────────────────────────────────────
function openDB() {
return new Promise(function (resolve, reject) {
if (db) { resolve(db); return; }
var req = indexedDB.open(DB_NAME, DB_VERSION);
req.onupgradeneeded = function (e) {
var d = e.target.result;
if (!d.objectStoreNames.contains(QUEUE_STORE)) {
d.createObjectStore(QUEUE_STORE, { keyPath: 'id', autoIncrement: true });
}
if (!d.objectStoreNames.contains(INVENTORY_STORE)) {
var inv = d.createObjectStore(INVENTORY_STORE, { keyPath: 'item_id' });
inv.createIndex('sku', 'sku', { unique: false });
inv.createIndex('name', 'name', { unique: false });
}
};
req.onsuccess = function (e) {
db = e.target.result;
resolve(db);
};
req.onerror = function () { reject(req.error); };
});
}
// ─── Queue operations for offline replay ──────────────────────
function queueOperation(url, method, body) {
return openDB().then(function (d) {
return new Promise(function (resolve, reject) {
var tx = d.transaction(QUEUE_STORE, 'readwrite');
tx.objectStore(QUEUE_STORE).add({
url: url,
method: method,
body: body || null,
timestamp: Date.now()
});
tx.oncomplete = function () { resolve(); };
tx.onerror = function () { reject(tx.error); };
});
});
}
// ─── Process queued operations ────────────────────────────────
function processQueue() {
return openDB().then(function (d) {
return new Promise(function (resolve, reject) {
var tx = d.transaction(QUEUE_STORE, 'readonly');
var req = tx.objectStore(QUEUE_STORE).getAll();
req.onsuccess = function () { resolve(req.result); };
req.onerror = function () { reject(req.error); };
});
}).then(function (ops) {
if (!ops.length) return Promise.resolve({ synced: 0 });
// Replay in order
var chain = Promise.resolve();
var synced = 0;
var failed = 0;
ops.forEach(function (op) {
chain = chain.then(function () {
var opts = { method: op.method, headers: { 'Content-Type': 'application/json' } };
if (op.body) opts.body = JSON.stringify(op.body);
return fetch(op.url, opts).then(function (resp) {
if (resp.ok) {
synced++;
return removeFromQueue(op.id);
}
failed++;
}).catch(function () {
failed++;
});
});
});
return chain.then(function () {
return { synced: synced, failed: failed, total: ops.length };
});
});
}
function removeFromQueue(id) {
return openDB().then(function (d) {
return new Promise(function (resolve, reject) {
var tx = d.transaction(QUEUE_STORE, 'readwrite');
tx.objectStore(QUEUE_STORE).delete(id);
tx.oncomplete = function () { resolve(); };
tx.onerror = function () { reject(tx.error); };
});
});
}
function getQueueCount() {
return openDB().then(function (d) {
return new Promise(function (resolve, reject) {
var tx = d.transaction(QUEUE_STORE, 'readonly');
var req = tx.objectStore(QUEUE_STORE).count();
req.onsuccess = function () { resolve(req.result); };
req.onerror = function () { reject(req.error); };
});
});
}
// ─── Inventory cache ──────────────────────────────────────────
function cacheInventory() {
return fetch('/pos/api/sync/inventory').then(function (resp) {
if (!resp.ok) throw new Error('Sync inventory failed: ' + resp.status);
return resp.json();
}).then(function (data) {
var items = data.items || [];
return openDB().then(function (d) {
return new Promise(function (resolve, reject) {
var tx = d.transaction(INVENTORY_STORE, 'readwrite');
var store = tx.objectStore(INVENTORY_STORE);
store.clear();
items.forEach(function (item) { store.put(item); });
tx.oncomplete = function () { resolve(items.length); };
tx.onerror = function () { reject(tx.error); };
});
});
});
}
function getCachedInventory(query) {
return openDB().then(function (d) {
return new Promise(function (resolve, reject) {
var tx = d.transaction(INVENTORY_STORE, 'readonly');
var req = tx.objectStore(INVENTORY_STORE).getAll();
req.onsuccess = function () {
var all = req.result;
if (!query) { resolve(all); return; }
var q = query.toLowerCase();
var filtered = all.filter(function (item) {
return (item.sku && item.sku.toLowerCase().indexOf(q) !== -1) ||
(item.name && item.name.toLowerCase().indexOf(q) !== -1) ||
(item.barcode && item.barcode.toLowerCase().indexOf(q) !== -1);
});
resolve(filtered);
};
req.onerror = function () { reject(req.error); };
});
});
}
// ─── Connectivity helpers ─────────────────────────────────────
function isOnline() {
return navigator.onLine;
}
// ─── Auto-sync on reconnect ───────────────────────────────────
window.addEventListener('online', function () {
console.log('[SyncEngine] Online — processing queue...');
processQueue().then(function (result) {
if (result.synced > 0) {
console.log('[SyncEngine] Synced ' + result.synced + ' operations');
}
}).catch(function (err) {
console.error('[SyncEngine] Queue processing error:', err);
});
});
window.addEventListener('offline', function () {
console.log('[SyncEngine] Offline — operations will be queued');
});
// Listen for SW sync messages
if ('serviceWorker' in navigator) {
navigator.serviceWorker.addEventListener('message', function (event) {
if (event.data && event.data.type === 'SYNC_REQUESTED') {
processQueue();
}
});
}
// ─── Init DB on load ──────────────────────────────────────────
openDB().catch(function (err) {
console.error('[SyncEngine] Failed to open IndexedDB:', err);
});
// ─── Public API ───────────────────────────────────────────────
window.SyncEngine = {
queueOperation: queueOperation,
processQueue: processQueue,
getQueueCount: getQueueCount,
isOnline: isOnline,
cacheInventory: cacheInventory,
getCachedInventory: getCachedInventory
};
})();