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>
This commit is contained in:
204
pos/static/js/sync-engine.js
Normal file
204
pos/static/js/sync-engine.js
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
// /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
|
||||||
|
};
|
||||||
|
|
||||||
|
})();
|
||||||
BIN
pos/static/pwa/icon-192.png
Normal file
BIN
pos/static/pwa/icon-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
BIN
pos/static/pwa/icon-512.png
Normal file
BIN
pos/static/pwa/icon-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
13
pos/static/pwa/manifest.json
Normal file
13
pos/static/pwa/manifest.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "Nexus POS",
|
||||||
|
"short_name": "NexusPOS",
|
||||||
|
"description": "Sistema de Punto de Venta para Refaccionarias",
|
||||||
|
"start_url": "/pos/login",
|
||||||
|
"display": "standalone",
|
||||||
|
"background_color": "#0d0d0d",
|
||||||
|
"theme_color": "#F5A623",
|
||||||
|
"icons": [
|
||||||
|
{"src": "/pos/static/pwa/icon-192.png", "sizes": "192x192", "type": "image/png"},
|
||||||
|
{"src": "/pos/static/pwa/icon-512.png", "sizes": "512x512", "type": "image/png"}
|
||||||
|
]
|
||||||
|
}
|
||||||
134
pos/static/pwa/sw.js
Normal file
134
pos/static/pwa/sw.js
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
// /home/Autopartes/pos/static/pwa/sw.js
|
||||||
|
// Nexus POS — Service Worker v1
|
||||||
|
|
||||||
|
const CACHE_NAME = 'nexus-pos-v1';
|
||||||
|
|
||||||
|
const APP_SHELL = [
|
||||||
|
'/pos/login',
|
||||||
|
'/pos/sale',
|
||||||
|
'/pos/catalog',
|
||||||
|
'/pos/inventory',
|
||||||
|
'/pos/customers',
|
||||||
|
'/pos/invoicing',
|
||||||
|
'/pos/accounting',
|
||||||
|
'/pos/dashboard',
|
||||||
|
'/pos/config',
|
||||||
|
'/pos/reports',
|
||||||
|
'/pos/static/css/tokens.css',
|
||||||
|
'/pos/static/css/common.css',
|
||||||
|
'/pos/static/js/app-init.js',
|
||||||
|
'/pos/static/js/sidebar.js',
|
||||||
|
'/pos/static/js/login.js',
|
||||||
|
'/pos/static/js/pos.js',
|
||||||
|
'/pos/static/js/catalog.js',
|
||||||
|
'/pos/static/js/inventory.js',
|
||||||
|
'/pos/static/js/customers.js',
|
||||||
|
'/pos/static/js/invoicing.js',
|
||||||
|
'/pos/static/js/accounting.js',
|
||||||
|
'/pos/static/js/dashboard.js',
|
||||||
|
'/pos/static/js/config.js',
|
||||||
|
'/pos/static/js/reports.js',
|
||||||
|
'/pos/static/js/offline-banner.js',
|
||||||
|
'/pos/static/js/sync-engine.js',
|
||||||
|
'/pos/static/pwa/manifest.json',
|
||||||
|
'/pos/static/pwa/icon-192.png',
|
||||||
|
'/pos/static/pwa/icon-512.png'
|
||||||
|
];
|
||||||
|
|
||||||
|
// ─── Install: pre-cache app shell ────────────────────────────────
|
||||||
|
self.addEventListener('install', function (event) {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.open(CACHE_NAME).then(function (cache) {
|
||||||
|
return cache.addAll(APP_SHELL);
|
||||||
|
}).then(function () {
|
||||||
|
return self.skipWaiting();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Activate: purge old caches ──────────────────────────────────
|
||||||
|
self.addEventListener('activate', function (event) {
|
||||||
|
event.waitUntil(
|
||||||
|
caches.keys().then(function (names) {
|
||||||
|
return Promise.all(
|
||||||
|
names.filter(function (n) { return n !== CACHE_NAME; })
|
||||||
|
.map(function (n) { return caches.delete(n); })
|
||||||
|
);
|
||||||
|
}).then(function () {
|
||||||
|
return self.clients.claim();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Fetch strategy ──────────────────────────────────────────────
|
||||||
|
self.addEventListener('fetch', function (event) {
|
||||||
|
var url = new URL(event.request.url);
|
||||||
|
|
||||||
|
// API calls → network-first
|
||||||
|
if (url.pathname.indexOf('/pos/api/') !== -1) {
|
||||||
|
event.respondWith(networkFirst(event.request));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Everything else → cache-first
|
||||||
|
event.respondWith(cacheFirst(event.request));
|
||||||
|
});
|
||||||
|
|
||||||
|
function cacheFirst(request) {
|
||||||
|
return caches.match(request).then(function (cached) {
|
||||||
|
if (cached) {
|
||||||
|
// Update cache in background
|
||||||
|
fetch(request).then(function (response) {
|
||||||
|
if (response && response.status === 200) {
|
||||||
|
caches.open(CACHE_NAME).then(function (cache) {
|
||||||
|
cache.put(request, response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).catch(function () { /* offline, ignore */ });
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
return fetch(request).then(function (response) {
|
||||||
|
if (response && response.status === 200) {
|
||||||
|
var clone = response.clone();
|
||||||
|
caches.open(CACHE_NAME).then(function (cache) {
|
||||||
|
cache.put(request, clone);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function networkFirst(request) {
|
||||||
|
return fetch(request).then(function (response) {
|
||||||
|
if (response && response.status === 200) {
|
||||||
|
var clone = response.clone();
|
||||||
|
caches.open(CACHE_NAME).then(function (cache) {
|
||||||
|
cache.put(request, clone);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}).catch(function () {
|
||||||
|
return caches.match(request);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── Background Sync ─────────────────────────────────────────────
|
||||||
|
self.addEventListener('sync', function (event) {
|
||||||
|
if (event.tag === 'nexus-pos-sync') {
|
||||||
|
event.respondWith && event.waitUntil(
|
||||||
|
self.clients.matchAll().then(function (clients) {
|
||||||
|
clients.forEach(function (client) {
|
||||||
|
client.postMessage({ type: 'SYNC_REQUESTED' });
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ─── Message handler ─────────────────────────────────────────────
|
||||||
|
self.addEventListener('message', function (event) {
|
||||||
|
if (event.data && event.data.type === 'SKIP_WAITING') {
|
||||||
|
self.skipWaiting();
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Contabilidad — Nexus Autoparts POS</title>
|
<title>Contabilidad — Nexus Autoparts POS</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
@@ -2302,6 +2304,8 @@
|
|||||||
<script src="/pos/static/js/app-init.js"></script>
|
<script src="/pos/static/js/app-init.js"></script>
|
||||||
<script src="/pos/static/js/sidebar.js"></script>
|
<script src="/pos/static/js/sidebar.js"></script>
|
||||||
<script src="/pos/static/js/accounting.js"></script>
|
<script src="/pos/static/js/accounting.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Catalogo — Nexus Autoparts POS</title>
|
<title>Catalogo — Nexus Autoparts POS</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
@@ -630,5 +633,8 @@
|
|||||||
<script src="/pos/static/js/sidebar.js"></script>
|
<script src="/pos/static/js/sidebar.js"></script>
|
||||||
<script src="/pos/static/js/catalog.js"></script>
|
<script src="/pos/static/js/catalog.js"></script>
|
||||||
<script src="/pos/static/js/offline-banner.js"></script>
|
<script src="/pos/static/js/offline-banner.js"></script>
|
||||||
|
<script src="/pos/static/js/chat.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Configuración — Nexus Autoparts POS</title>
|
<title>Configuración — Nexus Autoparts POS</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
@@ -1824,6 +1826,8 @@
|
|||||||
<script src="/pos/static/js/app-init.js"></script>
|
<script src="/pos/static/js/app-init.js"></script>
|
||||||
<script src="/pos/static/js/sidebar.js"></script>
|
<script src="/pos/static/js/sidebar.js"></script>
|
||||||
<script src="/pos/static/js/config.js"></script>
|
<script src="/pos/static/js/config.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Nexus Autoparts — Clientes</title>
|
<title>Nexus Autoparts — Clientes</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =====================================================================
|
/* =====================================================================
|
||||||
@@ -2149,5 +2151,7 @@
|
|||||||
<script src="/pos/static/js/sidebar.js"></script>
|
<script src="/pos/static/js/sidebar.js"></script>
|
||||||
<script src="/pos/static/js/customers.js"></script>
|
<script src="/pos/static/js/customers.js"></script>
|
||||||
<script src="/pos/static/js/offline-banner.js"></script>
|
<script src="/pos/static/js/offline-banner.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Nexus Autoparts — Dashboard</title>
|
<title>Nexus Autoparts — Dashboard</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
@@ -1952,6 +1954,8 @@
|
|||||||
<script src="/pos/static/js/app-init.js"></script>
|
<script src="/pos/static/js/app-init.js"></script>
|
||||||
<script src="/pos/static/js/sidebar.js"></script>
|
<script src="/pos/static/js/sidebar.js"></script>
|
||||||
<script src="/pos/static/js/dashboard.js"></script>
|
<script src="/pos/static/js/dashboard.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Inventario — Nexus Autoparts POS</title>
|
<title>Inventario — Nexus Autoparts POS</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
@@ -2527,5 +2529,7 @@
|
|||||||
<script src="/pos/static/js/sidebar.js"></script>
|
<script src="/pos/static/js/sidebar.js"></script>
|
||||||
<script src="/pos/static/js/inventory.js"></script>
|
<script src="/pos/static/js/inventory.js"></script>
|
||||||
<script src="/pos/static/js/offline-banner.js"></script>
|
<script src="/pos/static/js/offline-banner.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Facturación CFDI — Nexus Autoparts POS</title>
|
<title>Facturación CFDI — Nexus Autoparts POS</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
@@ -2724,6 +2726,8 @@
|
|||||||
<script src="/pos/static/js/app-init.js"></script>
|
<script src="/pos/static/js/app-init.js"></script>
|
||||||
<script src="/pos/static/js/sidebar.js"></script>
|
<script src="/pos/static/js/sidebar.js"></script>
|
||||||
<script src="/pos/static/js/invoicing.js"></script>
|
<script src="/pos/static/js/invoicing.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Nexus Autoparts — Punto de Venta</title>
|
<title>Nexus Autoparts — Punto de Venta</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="stylesheet" href="/pos/static/css/chat.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =====================================================================
|
/* =====================================================================
|
||||||
@@ -1527,5 +1530,8 @@
|
|||||||
setInterval(updateClock, 30000);
|
setInterval(updateClock, 30000);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script src="/pos/static/js/chat.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -6,6 +6,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Reportes — Nexus Autoparts POS</title>
|
<title>Reportes — Nexus Autoparts POS</title>
|
||||||
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
<link rel="stylesheet" href="/pos/static/css/tokens.css" />
|
||||||
|
<link rel="manifest" href="/pos/static/pwa/manifest.json" />
|
||||||
|
<meta name="theme-color" content="#F5A623" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* =========================================================================
|
/* =========================================================================
|
||||||
@@ -2918,6 +2920,8 @@
|
|||||||
<script src="/pos/static/js/app-init.js"></script>
|
<script src="/pos/static/js/app-init.js"></script>
|
||||||
<script src="/pos/static/js/sidebar.js"></script>
|
<script src="/pos/static/js/sidebar.js"></script>
|
||||||
<script src="/pos/static/js/reports.js"></script>
|
<script src="/pos/static/js/reports.js"></script>
|
||||||
|
<script src="/pos/static/js/sync-engine.js"></script>
|
||||||
|
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user