soporte sin conexion. modal para buscar articulos
This commit is contained in:
@@ -109,6 +109,84 @@ public class ContabilidadGeneralController : Controller
|
||||
return Json(new { success = false, message = "Error al guardar los movimientos. Verifique que el mes no esté cerrado." });
|
||||
}
|
||||
|
||||
// ==================== Sincronización Offline ====================
|
||||
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> SincronizarOffline([FromBody] List<BulkSaveRequest> transacciones)
|
||||
{
|
||||
if (transacciones == null || !transacciones.Any())
|
||||
return BadRequest("No hay transacciones para sincronizar.");
|
||||
|
||||
var resultados = new List<object>();
|
||||
|
||||
foreach (var request in transacciones)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (request.ReporteId <= 0)
|
||||
{
|
||||
resultados.Add(new {
|
||||
success = false,
|
||||
reporteId = request.ReporteId,
|
||||
message = "ID de reporte inválido."
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
var movimientos = request.Movimientos.Select(m => new MovimientoGeneral
|
||||
{
|
||||
Id = m.Id,
|
||||
Tipo = m.Tipo,
|
||||
CategoriaIngresoId = m.CategoriaIngresoId,
|
||||
CategoriaEgresoId = m.CategoriaEgresoId,
|
||||
Monto = m.Monto,
|
||||
Fecha = DateTime.SpecifyKind(m.Fecha, DateTimeKind.Utc),
|
||||
Descripcion = m.Descripcion ?? "",
|
||||
NumeroComprobante = m.NumeroComprobante
|
||||
}).ToList();
|
||||
|
||||
var success = await _contabilidadService.GuardarMovimientosBulkAsync(request.ReporteId, movimientos);
|
||||
|
||||
if (success)
|
||||
{
|
||||
resultados.Add(new {
|
||||
success = true,
|
||||
reporteId = request.ReporteId,
|
||||
message = "Sincronizado exitosamente"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
resultados.Add(new {
|
||||
success = false,
|
||||
reporteId = request.ReporteId,
|
||||
message = "Error al guardar. El mes puede estar cerrado."
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
resultados.Add(new {
|
||||
success = false,
|
||||
reporteId = request.ReporteId,
|
||||
message = $"Error: {ex.Message}"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var exitosos = resultados.Count(r => (bool)((dynamic)r).success);
|
||||
var fallidos = resultados.Count - exitosos;
|
||||
|
||||
return Json(new {
|
||||
success = exitosos > 0,
|
||||
total = transacciones.Count,
|
||||
exitosos = exitosos,
|
||||
fallidos = fallidos,
|
||||
resultados = resultados
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ==================== Cerrar Mes ====================
|
||||
|
||||
[HttpPost]
|
||||
|
||||
@@ -53,13 +53,44 @@ public class MovimientosInventarioController : Controller
|
||||
ViewBag.CantidadGlobal = articulo.CantidadGlobal; // For LOTE validation?
|
||||
}
|
||||
|
||||
ViewBag.Articulos = new SelectList((await _articuloService.GetAllAsync()).Select(x => new { x.Id, Nombre = $"{x.Codigo} - {x.Nombre}" }), "Id", "Nombre", articuloId);
|
||||
ViewBag.Articulos =
|
||||
new SelectList(
|
||||
(await _articuloService.GetAllAsync()).Select(x => new { x.Id, Nombre = $"{x.Codigo} - {x.Nombre}" }),
|
||||
"Id", "Nombre", articuloId);
|
||||
ViewBag.Ubicaciones = new SelectList(await _ubicacionService.GetAllAsync(), "Id", "Nombre");
|
||||
ViewBag.Estados = new SelectList(await _estadoService.GetAllAsync(), "Id", "Nombre");
|
||||
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> BuscarArticulos(string term)
|
||||
{
|
||||
var articulos = await _articuloService.GetAllAsync();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(term))
|
||||
{
|
||||
term = term.ToLower();
|
||||
articulos = articulos
|
||||
.Where(a =>
|
||||
(a.Nombre != null && a.Nombre.ToLower().Contains(term)) ||
|
||||
(a.Codigo != null && a.Codigo.ToLower().Contains(term)) ||
|
||||
(a.Descripcion != null && a.Descripcion.ToLower().Contains(term)))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
// Limit results
|
||||
var resultados = articulos.Take(20).Select(a => new {
|
||||
a.Id,
|
||||
a.Codigo,
|
||||
a.Nombre,
|
||||
Ubicacion = a.UbicacionNombre ?? "Sin ubicación",
|
||||
Stock = a.CantidadGlobal
|
||||
});
|
||||
|
||||
return Json(resultados);
|
||||
}
|
||||
|
||||
// POST: MovimientosInventario/RegistrarTraslado
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
|
||||
@@ -67,7 +67,8 @@ public class ArticuloService : IArticuloService
|
||||
EstadoColor = a.Estado.Color,
|
||||
UbicacionId = a.UbicacionId,
|
||||
UbicacionNombre = a.Ubicacion.Nombre,
|
||||
Activo = a.Activo
|
||||
Activo = a.Activo,
|
||||
CantidadGlobal = a.CantidadGlobal
|
||||
})
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
<h5 class="text-secondary">
|
||||
Saldo Actual: <span class="font-weight-bold @(ViewBag.SaldoActual >= 0 ? "text-success" : "text-danger")">@ViewBag.SaldoActual?.ToString("C")</span>
|
||||
</h5>
|
||||
<div class="mt-2">
|
||||
<span id="connectionStatus" class="badge bg-secondary"><i class="fas fa-wifi"></i> Verificando...</span>
|
||||
<span id="pendingCount" class="badge bg-warning ml-2" style="display:none;">0 pendientes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a asp-action="Index" class="btn btn-secondary btn-sm">
|
||||
@@ -139,6 +143,8 @@
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="~/js/offline-db.js"></script>
|
||||
<script src="~/js/offline-manager.js"></script>
|
||||
<script>
|
||||
const esCerrado = @Model.Cerrado.ToString().ToLower();
|
||||
const reporteId = @Model.Id;
|
||||
@@ -329,26 +335,23 @@
|
||||
}));
|
||||
|
||||
try {
|
||||
const response = await fetch('@Url.Action("GuardarBulk")', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
ReporteId: reporteId,
|
||||
Movimientos: payloadMovimientos
|
||||
})
|
||||
});
|
||||
// ... rest of logic
|
||||
// Use offline manager for save operation
|
||||
const result = await OfflineManager.saveTransaction(
|
||||
reporteId,
|
||||
payloadMovimientos,
|
||||
'@Url.Action("GuardarBulk")'
|
||||
);
|
||||
|
||||
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
// Show toast or alert
|
||||
if (result.offline) {
|
||||
alert(result.message);
|
||||
await OfflineManager.updatePendingCount();
|
||||
} else {
|
||||
alert('Guardado exitosamente');
|
||||
location.reload();
|
||||
}
|
||||
} else {
|
||||
alert('Error: ' + result.message);
|
||||
alert('Error: ' + (result.message || 'Error desconocido'));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
@@ -23,14 +23,107 @@
|
||||
<div class="card-body">
|
||||
<form method="get" asp-action="Create">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Buscar Artículo</label>
|
||||
<select name="articuloId" class="form-select" asp-items="ViewBag.Articulos" onchange="this.form.submit()">
|
||||
<option value="">-- Seleccione un artículo --</option>
|
||||
</select>
|
||||
<div class="form-text">Seleccione para cargar datos actuales.</div>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="@ViewBag.ArticuloNombre" placeholder="Ningún artículo seleccionado" readonly />
|
||||
<input type="hidden" name="articuloId" id="articuloIdInput" value="@ViewBag.ArticuloId" />
|
||||
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalBuscarArticulo">
|
||||
<i class="bi bi-search"></i> Buscar
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text">Busque y seleccione el artículo para cargar sus datos.</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Modal Buscador -->
|
||||
<div class="modal fade" id="modalBuscarArticulo" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Buscar Artículo</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" id="inputBusqueda" class="form-control" placeholder="Nombre, código o descripción...">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="buscarArticulos()">
|
||||
<i class="bi bi-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="table-responsive" style="max-height: 300px; overflow-y: auto;">
|
||||
<table class="table table-hover table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Código</th>
|
||||
<th>Nombre</th>
|
||||
<th>Ubicación</th>
|
||||
<th>Stock</th>
|
||||
<th>Acción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="resultadoBusqueda">
|
||||
<tr><td colspan="5" class="text-center text-muted">Ingrese un término para buscar...</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('inputBusqueda').addEventListener('keypress', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
buscarArticulos();
|
||||
}
|
||||
});
|
||||
|
||||
async function buscarArticulos() {
|
||||
const term = document.getElementById('inputBusqueda').value;
|
||||
if (!term || term.length < 2) {
|
||||
alert("Ingrese al menos 2 caracteres.");
|
||||
return;
|
||||
}
|
||||
|
||||
const tbody = document.getElementById('resultadoBusqueda');
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center"><div class="spinner-border spinner-border-sm text-primary"></div> Buscando...</td></tr>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`@Url.Action("BuscarArticulos")?term=${encodeURIComponent(term)}`);
|
||||
const data = await response.json();
|
||||
|
||||
tbody.innerHTML = '';
|
||||
if (data.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No se encontraron resultados.</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
data.forEach(item => {
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML = `
|
||||
<td><small>${item.codigo || '-'}</small></td>
|
||||
<td>${item.nombre}</td>
|
||||
<td><small>${item.ubicacion}</small></td>
|
||||
<td><span class="badge bg-secondary">${item.stock}</span></td>
|
||||
<td>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="seleccionarArticulo(${item.id})">
|
||||
Seleccionar
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(tr);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-danger">Error al buscar.</td></tr>';
|
||||
}
|
||||
}
|
||||
|
||||
function seleccionarArticulo(id) {
|
||||
// Redirect to same page with id parameter to load details
|
||||
window.location.href = '@Url.Action("Create")?articuloId=' + id;
|
||||
}
|
||||
</script>
|
||||
|
||||
@if (ViewBag.ArticuloId != null)
|
||||
{
|
||||
<div class="alert alert-light border mt-3">
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
123
RS_system/wwwroot/js/offline-db.js
Normal file
123
RS_system/wwwroot/js/offline-db.js
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* IndexedDB Wrapper for Offline Contabilidad
|
||||
* Stores pending transactions when offline
|
||||
*/
|
||||
|
||||
const OfflineDB = {
|
||||
dbName: 'ContabilidadOfflineDB',
|
||||
version: 1,
|
||||
storeName: 'pendingTransactions',
|
||||
|
||||
/**
|
||||
* Initialize the database
|
||||
*/
|
||||
async init() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = indexedDB.open(this.dbName, this.version);
|
||||
|
||||
request.onerror = () => reject(request.error);
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = event.target.result;
|
||||
|
||||
// Create object store if it doesn't exist
|
||||
if (!db.objectStoreNames.contains(this.storeName)) {
|
||||
const objectStore = db.createObjectStore(this.storeName, {
|
||||
keyPath: 'id',
|
||||
autoIncrement: true
|
||||
});
|
||||
objectStore.createIndex('timestamp', 'timestamp', { unique: false });
|
||||
objectStore.createIndex('reporteId', 'reporteId', { unique: false });
|
||||
}
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a pending transaction to the queue
|
||||
*/
|
||||
async addPending(reporteId, movimientos) {
|
||||
const db = await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction([this.storeName], 'readwrite');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
|
||||
const record = {
|
||||
reporteId: reporteId,
|
||||
movimientos: movimientos,
|
||||
timestamp: new Date().toISOString()
|
||||
};
|
||||
|
||||
const request = store.add(record);
|
||||
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all pending transactions
|
||||
*/
|
||||
async getAllPending() {
|
||||
const db = await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction([this.storeName], 'readonly');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const request = store.getAll();
|
||||
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove a specific pending transaction by id
|
||||
*/
|
||||
async removePending(id) {
|
||||
const db = await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction([this.storeName], 'readwrite');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const request = store.delete(id);
|
||||
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear all pending transactions
|
||||
*/
|
||||
async clearPending() {
|
||||
const db = await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction([this.storeName], 'readwrite');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const request = store.clear();
|
||||
|
||||
request.onsuccess = () => resolve();
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Get count of pending transactions
|
||||
*/
|
||||
async getPendingCount() {
|
||||
const db = await this.init();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const transaction = db.transaction([this.storeName], 'readonly');
|
||||
const store = transaction.objectStore(this.storeName);
|
||||
const request = store.count();
|
||||
|
||||
request.onsuccess = () => resolve(request.result);
|
||||
request.onerror = () => reject(request.error);
|
||||
});
|
||||
}
|
||||
};
|
||||
229
RS_system/wwwroot/js/offline-manager.js
Normal file
229
RS_system/wwwroot/js/offline-manager.js
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* Offline Manager for Contabilidad
|
||||
* Handles connection monitoring, offline queue, and synchronization
|
||||
*/
|
||||
|
||||
const OfflineManager = {
|
||||
isOnline: navigator.onLine,
|
||||
isSyncing: false,
|
||||
statusBadge: null,
|
||||
pendingCounter: null,
|
||||
syncInProgress: false,
|
||||
|
||||
/**
|
||||
* Initialize the offline manager
|
||||
*/
|
||||
init() {
|
||||
this.statusBadge = document.getElementById('connectionStatus');
|
||||
this.pendingCounter = document.getElementById('pendingCount');
|
||||
|
||||
// Listen for online/offline events
|
||||
window.addEventListener('online', () => this.handleOnline());
|
||||
window.addEventListener('offline', () => this.handleOffline());
|
||||
|
||||
// Initial status
|
||||
this.updateStatus();
|
||||
this.updatePendingCount();
|
||||
|
||||
// Check for pending transactions on load
|
||||
if (this.isOnline) {
|
||||
this.syncPending();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle online event
|
||||
*/
|
||||
async handleOnline() {
|
||||
this.isOnline = true;
|
||||
this.updateStatus();
|
||||
console.log('Connection restored - starting sync...');
|
||||
await this.syncPending();
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle offline event
|
||||
*/
|
||||
handleOffline() {
|
||||
this.isOnline = false;
|
||||
this.updateStatus();
|
||||
console.log('Connection lost - offline mode enabled');
|
||||
},
|
||||
|
||||
/**
|
||||
* Update connection status badge
|
||||
*/
|
||||
updateStatus() {
|
||||
if (!this.statusBadge) return;
|
||||
|
||||
if (this.isSyncing) {
|
||||
this.statusBadge.className = 'badge bg-warning';
|
||||
this.statusBadge.innerHTML = '<i class="fas fa-sync fa-spin"></i> Sincronizando';
|
||||
} else if (this.isOnline) {
|
||||
this.statusBadge.className = 'badge bg-success';
|
||||
this.statusBadge.innerHTML = '<i class="fas fa-wifi"></i> En línea';
|
||||
} else {
|
||||
this.statusBadge.className = 'badge bg-secondary';
|
||||
this.statusBadge.innerHTML = '<i class="fas fa-wifi-slash"></i> Sin conexión';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Update pending transaction counter
|
||||
*/
|
||||
async updatePendingCount() {
|
||||
if (!this.pendingCounter) return;
|
||||
|
||||
try {
|
||||
const count = await OfflineDB.getPendingCount();
|
||||
if (count > 0) {
|
||||
this.pendingCounter.textContent = `${count} pendiente${count > 1 ? 's' : ''}`;
|
||||
this.pendingCounter.style.display = 'inline-block';
|
||||
} else {
|
||||
this.pendingCounter.style.display = 'none';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating pending count:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Save transaction (online or offline)
|
||||
*/
|
||||
async saveTransaction(reporteId, movimientos, url) {
|
||||
if (this.isOnline) {
|
||||
// Try to save directly
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
ReporteId: reporteId,
|
||||
Movimientos: movimientos
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
return { success: true, message: 'Guardado exitosamente', data: result };
|
||||
} else {
|
||||
throw new Error(result.message || 'Error desconocido');
|
||||
}
|
||||
} catch (error) {
|
||||
// If online but request failed, save to queue
|
||||
console.warn('Request failed, saving to offline queue:', error);
|
||||
await this.saveOffline(reporteId, movimientos);
|
||||
return {
|
||||
success: true,
|
||||
offline: true,
|
||||
message: 'Guardado en cola offline. Se sincronizará automáticamente.'
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Save to offline queue
|
||||
await this.saveOffline(reporteId, movimientos);
|
||||
return {
|
||||
success: true,
|
||||
offline: true,
|
||||
message: 'Sin conexión. Guardado en cola offline.'
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Save to offline queue
|
||||
*/
|
||||
async saveOffline(reporteId, movimientos) {
|
||||
await OfflineDB.addPending(reporteId, movimientos);
|
||||
await this.updatePendingCount();
|
||||
console.log('Transaction saved to offline queue');
|
||||
},
|
||||
|
||||
/**
|
||||
* Synchronize pending transactions
|
||||
*/
|
||||
async syncPending() {
|
||||
if (!this.isOnline || this.syncInProgress) return;
|
||||
|
||||
try {
|
||||
this.syncInProgress = true;
|
||||
this.isSyncing = true;
|
||||
this.updateStatus();
|
||||
|
||||
const pending = await OfflineDB.getAllPending();
|
||||
|
||||
if (pending.length === 0) {
|
||||
console.log('No pending transactions to sync');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`Syncing ${pending.length} pending transaction(s)...`);
|
||||
|
||||
let successCount = 0;
|
||||
let failCount = 0;
|
||||
|
||||
for (const transaction of pending) {
|
||||
try {
|
||||
const response = await fetch('/ContabilidadGeneral/GuardarBulk', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
ReporteId: transaction.reporteId,
|
||||
Movimientos: transaction.movimientos
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
await OfflineDB.removePending(transaction.id);
|
||||
successCount++;
|
||||
console.log(`Transaction ${transaction.id} synced successfully`);
|
||||
} else {
|
||||
failCount++;
|
||||
console.error(`Transaction ${transaction.id} failed:`, result.message);
|
||||
}
|
||||
} catch (error) {
|
||||
failCount++;
|
||||
console.error(`Error syncing transaction ${transaction.id}:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
await this.updatePendingCount();
|
||||
|
||||
if (successCount > 0) {
|
||||
alert(`Sincronización completa: ${successCount} registro(s) guardado(s).${failCount > 0 ? ` ${failCount} fallido(s).` : ''}`);
|
||||
location.reload(); // Refresh to show updated data
|
||||
} else if (failCount > 0) {
|
||||
alert(`Error en sincronización: ${failCount} registro(s) no se pudieron guardar.`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Sync error:', error);
|
||||
} finally {
|
||||
this.syncInProgress = false;
|
||||
this.isSyncing = false;
|
||||
this.updateStatus();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Manually trigger sync
|
||||
*/
|
||||
async manualSync() {
|
||||
if (!this.isOnline) {
|
||||
alert('No hay conexión a internet. La sincronización se realizará automáticamente cuando se restaure la conexión.');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.syncPending();
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize when DOM is ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', () => OfflineManager.init());
|
||||
} else {
|
||||
OfflineManager.init();
|
||||
}
|
||||
Reference in New Issue
Block a user