SekiPOS server sync

This commit is contained in:
2026-06-23 15:20:14 -04:00
parent 5704980dbd
commit ccd1836d38
15 changed files with 1063 additions and 149 deletions

View File

@@ -324,6 +324,31 @@
<option value="xlarge">Extra Grande</option>
</select>
</div>
<hr>
<h6 class="text-muted text-uppercase small mb-2"><i class="bi bi-cloud-arrow-up me-1"></i>Sincronización</h6>
<div class="mb-2">
<label class="form-label text-muted small mb-1">Servidor (URL)</label>
<input type="text" id="setting-server-url" class="form-control" placeholder="Ej: http://192.168.1.100:5000" autocomplete="off">
</div>
<div class="mb-2">
<label class="form-label text-muted small mb-1">Nombre de esta caja</label>
<input type="text" id="setting-display-name" class="form-control" placeholder="Ej: Caja Principal" autocomplete="off">
</div>
<div class="mb-2">
<label class="form-label text-muted small mb-1">Clave de sincronización</label>
<input type="password" id="setting-sync-secret" class="form-control" placeholder="Ingrese la clave del servidor" autocomplete="off">
</div>
<div class="d-flex justify-content-between align-items-center mb-2 p-2 rounded" style="background: var(--input-bg);">
<div>
<span id="sync-mode-badge" class="badge bg-secondary me-1">---</span>
<small class="text-muted d-block mt-1" id="sync-status-text">Estado: <span id="sync-status-label">No configurado</span></small>
<small class="text-muted d-block" id="sync-instance-id"></small>
</div>
<button class="btn btn-sm btn-outline-secondary" onclick="syncNow()" id="btn-sync-now" title="Sincronizar ahora">
<i class="bi bi-arrow-clockwise"></i>
</button>
</div>
</div>
<div class="modal-footer d-flex">
<button class="btn btn-secondary flex-grow-1" data-bs-dismiss="modal">Cancelar</button>
@@ -338,6 +363,48 @@
document.getElementById('setting-sii-fields').classList.toggle('d-none', !isChecked);
}
async function loadSyncConfig() {
try {
const res = await fetch('/api/instance/config');
const data = await res.json();
const isServer = data.mode === 'server';
const badge = document.getElementById('sync-mode-badge');
badge.textContent = isServer ? 'Servidor' : 'Cliente';
badge.className = 'badge ' + (isServer ? 'bg-success' : 'bg-primary') + ' me-1';
const urlInput = document.getElementById('setting-server-url');
const nameInput = document.getElementById('setting-display-name');
const syncBtn = document.getElementById('btn-sync-now');
urlInput.value = data.server_url || '';
nameInput.value = data.display_name || '';
document.getElementById('setting-sync-secret').value = data.sync_secret || '';
urlInput.disabled = isServer;
syncBtn.style.display = isServer ? 'none' : '';
document.getElementById('sync-instance-id').textContent = 'ID: ' + (data.instance_id || '').slice(0, 8) + '...';
document.getElementById('sync-status-label').textContent = isServer ? 'Activo' : (data.server_url ? 'Conectado' : 'No configurado');
} catch (e) {
document.getElementById('sync-status-label').textContent = 'Error al cargar';
}
}
async function syncNow() {
const btn = document.getElementById('btn-sync-now');
btn.disabled = true;
btn.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
document.getElementById('sync-status-label').textContent = 'Sincronizando...';
try {
const res = await fetch('/api/sync/trigger', { method: 'POST' });
const data = await res.json();
document.getElementById('sync-status-label').textContent = data.status === 'ok' ? 'Sincronizado' : 'Error';
} catch (e) {
document.getElementById('sync-status-label').textContent = 'Error de conexión';
}
btn.disabled = false;
btn.innerHTML = '<i class="bi bi-arrow-clockwise"></i>';
}
document.addEventListener('DOMContentLoaded', () => {
const modalEl = document.getElementById('settingsModal');
if (modalEl) {
@@ -357,12 +424,13 @@
document.getElementById('setting-address').value = localStorage.getItem('seki_address') || '';
toggleSiiFields();
loadSyncConfig();
});
}
});
function savePosSettings() {
async function savePosSettings() {
const bizName = document.getElementById('setting-biz-name').value.trim() || 'SekiPOS';
const autoPrint = document.getElementById('setting-auto-print').checked;
const askDetails = document.getElementById('setting-ask-order-details').checked;
@@ -370,6 +438,9 @@
const foodMode = document.getElementById('setting-food-mode').checked;
const lastScanned = document.getElementById('setting-last-scanned').checked;
const comandaSize = document.getElementById('setting-comanda-size').value;
const serverUrl = document.getElementById('setting-server-url').value.trim();
const displayName = document.getElementById('setting-display-name').value.trim() || 'Caja Principal';
const syncSecret = document.getElementById('setting-sync-secret').value.trim();
localStorage.setItem('seki_biz_name', bizName);
localStorage.setItem('seki_auto_print', autoPrint);
localStorage.setItem('seki_ask_order_details', askDetails);
@@ -378,6 +449,16 @@
localStorage.setItem('modo_comida', foodMode);
localStorage.setItem('seki_last_scanned', lastScanned);
localStorage.setItem('seki_comanda_size', comandaSize);
try {
await fetch('/api/instance/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ server_url: serverUrl, display_name: displayName, sync_secret: syncSecret })
});
} catch (e) {
console.error('Failed to save sync config', e);
}
if (showSii) {
localStorage.setItem('seki_rut', document.getElementById('setting-rut').value.trim());