feat: Implementar PWA, Analytics, Reportes PDF y mejoras OCR

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>
This commit is contained in:
2026-01-19 03:26:16 +00:00
parent ed1658eb2b
commit 9936deaa90
25 changed files with 5501 additions and 282 deletions

238
sales-bot/static/js/app.js Normal file
View File

@@ -0,0 +1,238 @@
/**
* 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;