FASE 1 - PWA y Frontend: - Crear templates/base.html, dashboard.html, analytics.html, executive.html - Crear static/css/main.css con diseño responsivo - Agregar static/js/app.js, pwa.js, camera.js, charts.js - Implementar manifest.json y service-worker.js para PWA - Soporte para captura de tickets desde cámara móvil FASE 2 - Analytics: - Crear módulo analytics/ con predictions.py, trends.py, comparisons.py - Implementar predicción básica con promedio móvil + tendencia lineal - Agregar endpoints /api/analytics/trends, predictions, comparisons - Integrar Chart.js para gráficas interactivas FASE 3 - Reportes PDF: - Crear módulo reports/ con pdf_generator.py - Implementar SalesReportPDF con generar_reporte_diario y ejecutivo - Agregar comando /reporte [diario|semanal|ejecutivo] - Agregar endpoints /api/reports/generate y /api/reports/download FASE 4 - Mejoras OCR: - Crear módulo ocr/ con processor.py, preprocessor.py, patterns.py - Implementar AmountDetector con patrones múltiples de montos - Agregar preprocesador adaptativo con pipelines para diferentes condiciones - Soporte para corrección de rotación (deskew) y threshold Otsu Dependencias agregadas: - reportlab, matplotlib (PDF) - scipy, pandas (analytics) - imutils, deskew, cachetools (OCR) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
239 lines
6.5 KiB
JavaScript
239 lines
6.5 KiB
JavaScript
/**
|
|
* Sales Bot - Main Application JavaScript
|
|
*/
|
|
|
|
// Utility functions
|
|
const Utils = {
|
|
formatMoney(amount) {
|
|
return new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN' }).format(amount || 0);
|
|
},
|
|
|
|
formatDate(dateStr) {
|
|
if (!dateStr) return '';
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleDateString('es-MX', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
},
|
|
|
|
formatTime(dateStr) {
|
|
if (!dateStr) return '';
|
|
const date = new Date(dateStr);
|
|
return date.toLocaleTimeString('es-MX', { hour: '2-digit', minute: '2-digit' });
|
|
},
|
|
|
|
formatDateTime(dateStr) {
|
|
if (!dateStr) return '';
|
|
return `${this.formatDate(dateStr)} ${this.formatTime(dateStr)}`;
|
|
},
|
|
|
|
debounce(func, wait) {
|
|
let timeout;
|
|
return function executedFunction(...args) {
|
|
const later = () => {
|
|
clearTimeout(timeout);
|
|
func(...args);
|
|
};
|
|
clearTimeout(timeout);
|
|
timeout = setTimeout(later, wait);
|
|
};
|
|
},
|
|
|
|
showNotification(message, type = 'info') {
|
|
// Create notification element
|
|
const notification = document.createElement('div');
|
|
notification.className = `notification notification-${type}`;
|
|
notification.textContent = message;
|
|
notification.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
padding: 15px 25px;
|
|
border-radius: 8px;
|
|
color: white;
|
|
font-weight: 500;
|
|
z-index: 9999;
|
|
animation: slideIn 0.3s ease;
|
|
background: ${type === 'success' ? '#00ff88' : type === 'error' ? '#ff4444' : '#00d4ff'};
|
|
color: ${type === 'success' || type === 'info' ? '#000' : '#fff'};
|
|
`;
|
|
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.style.animation = 'slideOut 0.3s ease';
|
|
setTimeout(() => notification.remove(), 300);
|
|
}, 3000);
|
|
}
|
|
};
|
|
|
|
// API Client
|
|
const API = {
|
|
async get(endpoint) {
|
|
try {
|
|
const response = await fetch(endpoint);
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`API GET ${endpoint}:`, error);
|
|
throw error;
|
|
}
|
|
},
|
|
|
|
async post(endpoint, data) {
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data)
|
|
});
|
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`API POST ${endpoint}:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Dashboard module
|
|
const Dashboard = {
|
|
async loadSummary() {
|
|
try {
|
|
const data = await API.get('/api/dashboard/resumen');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error loading summary:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
async loadRanking() {
|
|
try {
|
|
const data = await API.get('/api/dashboard/ranking');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error loading ranking:', error);
|
|
return [];
|
|
}
|
|
},
|
|
|
|
async loadRecentSales() {
|
|
try {
|
|
const data = await API.get('/api/dashboard/ventas-recientes');
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error loading recent sales:', error);
|
|
return [];
|
|
}
|
|
}
|
|
};
|
|
|
|
// Analytics module
|
|
const Analytics = {
|
|
async loadTrends(days = 30) {
|
|
try {
|
|
const data = await API.get(`/api/analytics/trends?days=${days}`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error loading trends:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
async loadPredictions(period = 30) {
|
|
try {
|
|
const data = await API.get(`/api/analytics/predictions?period=${period}`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error loading predictions:', error);
|
|
return null;
|
|
}
|
|
},
|
|
|
|
async loadComparisons(type = 'monthly') {
|
|
try {
|
|
const data = await API.get(`/api/analytics/comparisons?type=${type}`);
|
|
return data;
|
|
} catch (error) {
|
|
console.error('Error loading comparisons:', error);
|
|
return null;
|
|
}
|
|
}
|
|
};
|
|
|
|
// Offline support
|
|
const OfflineManager = {
|
|
isOnline: navigator.onLine,
|
|
|
|
init() {
|
|
window.addEventListener('online', () => {
|
|
this.isOnline = true;
|
|
Utils.showNotification('Conexion restaurada', 'success');
|
|
this.syncData();
|
|
});
|
|
|
|
window.addEventListener('offline', () => {
|
|
this.isOnline = false;
|
|
Utils.showNotification('Sin conexion - Modo offline', 'error');
|
|
});
|
|
},
|
|
|
|
async cacheData(key, data) {
|
|
try {
|
|
localStorage.setItem(`salesbot_${key}`, JSON.stringify({
|
|
data,
|
|
timestamp: Date.now()
|
|
}));
|
|
} catch (e) {
|
|
console.error('Error caching data:', e);
|
|
}
|
|
},
|
|
|
|
getCachedData(key, maxAge = 300000) { // 5 minutes default
|
|
try {
|
|
const cached = localStorage.getItem(`salesbot_${key}`);
|
|
if (!cached) return null;
|
|
|
|
const { data, timestamp } = JSON.parse(cached);
|
|
if (Date.now() - timestamp > maxAge) return null;
|
|
|
|
return data;
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
},
|
|
|
|
syncData() {
|
|
// Sync any pending data when back online
|
|
console.log('Syncing data...');
|
|
}
|
|
};
|
|
|
|
// Initialize
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
OfflineManager.init();
|
|
});
|
|
|
|
// Add CSS animations
|
|
const style = document.createElement('style');
|
|
style.textContent = `
|
|
@keyframes slideIn {
|
|
from { transform: translateX(100%); opacity: 0; }
|
|
to { transform: translateX(0); opacity: 1; }
|
|
}
|
|
@keyframes slideOut {
|
|
from { transform: translateX(0); opacity: 1; }
|
|
to { transform: translateX(100%); opacity: 0; }
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
// Export for use in templates
|
|
window.Utils = Utils;
|
|
window.API = API;
|
|
window.Dashboard = Dashboard;
|
|
window.Analytics = Analytics;
|