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>
267 lines
9.3 KiB
JavaScript
267 lines
9.3 KiB
JavaScript
/**
|
|
* Sales Bot - Chart.js Integration and Chart Utilities
|
|
*/
|
|
|
|
// Chart default configuration
|
|
Chart.defaults.color = '#888';
|
|
Chart.defaults.borderColor = 'rgba(255, 255, 255, 0.1)';
|
|
Chart.defaults.font.family = "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif";
|
|
|
|
// Color palette
|
|
const ChartColors = {
|
|
primary: '#00d4ff',
|
|
secondary: '#00ff88',
|
|
warning: '#ffaa00',
|
|
danger: '#ff4444',
|
|
purple: '#aa00ff',
|
|
gradient: (ctx, color1, color2) => {
|
|
const gradient = ctx.createLinearGradient(0, 0, 0, 300);
|
|
gradient.addColorStop(0, color1);
|
|
gradient.addColorStop(1, color2);
|
|
return gradient;
|
|
}
|
|
};
|
|
|
|
// Chart factory
|
|
const ChartFactory = {
|
|
// Line chart for trends
|
|
createTrendChart(canvasId, data, options = {}) {
|
|
const ctx = document.getElementById(canvasId);
|
|
if (!ctx) return null;
|
|
|
|
return new Chart(ctx.getContext('2d'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
label: data.label || 'Datos',
|
|
data: data.values || [],
|
|
borderColor: options.color || ChartColors.primary,
|
|
backgroundColor: options.fill ?
|
|
ChartColors.gradient(ctx.getContext('2d'), 'rgba(0, 212, 255, 0.3)', 'rgba(0, 212, 255, 0)') :
|
|
'transparent',
|
|
fill: options.fill !== false,
|
|
tension: 0.4,
|
|
pointRadius: options.points ? 4 : 0,
|
|
pointHoverRadius: 6
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
intersect: false,
|
|
mode: 'index'
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: options.legend !== false,
|
|
labels: { color: '#888' }
|
|
},
|
|
tooltip: {
|
|
backgroundColor: 'rgba(26, 26, 46, 0.9)',
|
|
titleColor: '#fff',
|
|
bodyColor: '#888',
|
|
borderColor: ChartColors.primary,
|
|
borderWidth: 1,
|
|
padding: 12,
|
|
displayColors: false,
|
|
callbacks: {
|
|
label: (ctx) => options.formatValue ?
|
|
options.formatValue(ctx.parsed.y) :
|
|
ctx.parsed.y
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: { color: 'rgba(255,255,255,0.05)' },
|
|
ticks: { color: '#888', maxRotation: 45 }
|
|
},
|
|
y: {
|
|
grid: { color: 'rgba(255,255,255,0.05)' },
|
|
ticks: {
|
|
color: '#888',
|
|
callback: options.formatYAxis || ((value) => value)
|
|
},
|
|
beginAtZero: options.beginAtZero !== false
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Bar chart for comparisons
|
|
createBarChart(canvasId, data, options = {}) {
|
|
const ctx = document.getElementById(canvasId);
|
|
if (!ctx) return null;
|
|
|
|
return new Chart(ctx.getContext('2d'), {
|
|
type: options.horizontal ? 'bar' : 'bar',
|
|
data: {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
label: data.label || 'Datos',
|
|
data: data.values || [],
|
|
backgroundColor: data.colors || [
|
|
ChartColors.primary,
|
|
ChartColors.secondary,
|
|
ChartColors.warning,
|
|
ChartColors.purple
|
|
],
|
|
borderRadius: 8,
|
|
barThickness: options.barThickness || 'flex'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
indexAxis: options.horizontal ? 'y' : 'x',
|
|
plugins: {
|
|
legend: { display: false },
|
|
tooltip: {
|
|
backgroundColor: 'rgba(26, 26, 46, 0.9)',
|
|
titleColor: '#fff',
|
|
bodyColor: '#888',
|
|
borderColor: ChartColors.primary,
|
|
borderWidth: 1,
|
|
padding: 12,
|
|
callbacks: {
|
|
label: (ctx) => options.formatValue ?
|
|
options.formatValue(ctx.parsed[options.horizontal ? 'x' : 'y']) :
|
|
ctx.parsed[options.horizontal ? 'x' : 'y']
|
|
}
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: { display: !options.horizontal },
|
|
ticks: { color: '#888' }
|
|
},
|
|
y: {
|
|
grid: { color: options.horizontal ? 'transparent' : 'rgba(255,255,255,0.05)' },
|
|
ticks: { color: '#888' }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Doughnut chart for distribution
|
|
createDoughnutChart(canvasId, data, options = {}) {
|
|
const ctx = document.getElementById(canvasId);
|
|
if (!ctx) return null;
|
|
|
|
return new Chart(ctx.getContext('2d'), {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: data.labels || [],
|
|
datasets: [{
|
|
data: data.values || [],
|
|
backgroundColor: data.colors || [
|
|
ChartColors.primary,
|
|
ChartColors.secondary,
|
|
ChartColors.warning,
|
|
ChartColors.purple,
|
|
ChartColors.danger
|
|
],
|
|
borderWidth: 0,
|
|
cutout: options.cutout || '70%'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: options.legend !== false,
|
|
position: options.legendPosition || 'bottom',
|
|
labels: { color: '#888', padding: 15 }
|
|
},
|
|
tooltip: {
|
|
backgroundColor: 'rgba(26, 26, 46, 0.9)',
|
|
titleColor: '#fff',
|
|
bodyColor: '#888',
|
|
borderColor: ChartColors.primary,
|
|
borderWidth: 1,
|
|
padding: 12
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// Multi-line chart for comparisons
|
|
createMultiLineChart(canvasId, datasets, labels, options = {}) {
|
|
const ctx = document.getElementById(canvasId);
|
|
if (!ctx) return null;
|
|
|
|
const colors = [ChartColors.primary, ChartColors.secondary, ChartColors.warning, ChartColors.purple];
|
|
|
|
return new Chart(ctx.getContext('2d'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: datasets.map((ds, i) => ({
|
|
label: ds.label,
|
|
data: ds.values,
|
|
borderColor: ds.color || colors[i % colors.length],
|
|
backgroundColor: 'transparent',
|
|
borderDash: ds.dashed ? [5, 5] : [],
|
|
tension: 0.4,
|
|
pointRadius: 0,
|
|
pointHoverRadius: 6
|
|
}))
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
intersect: false,
|
|
mode: 'index'
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
labels: { color: '#888' }
|
|
},
|
|
tooltip: {
|
|
backgroundColor: 'rgba(26, 26, 46, 0.9)',
|
|
titleColor: '#fff',
|
|
bodyColor: '#888',
|
|
borderColor: ChartColors.primary,
|
|
borderWidth: 1,
|
|
padding: 12
|
|
}
|
|
},
|
|
scales: {
|
|
x: {
|
|
grid: { color: 'rgba(255,255,255,0.05)' },
|
|
ticks: { color: '#888' }
|
|
},
|
|
y: {
|
|
grid: { color: 'rgba(255,255,255,0.05)' },
|
|
ticks: { color: '#888' },
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
};
|
|
|
|
// Helper to format currency in charts
|
|
function formatCurrency(value) {
|
|
return new Intl.NumberFormat('es-MX', {
|
|
style: 'currency',
|
|
currency: 'MXN',
|
|
minimumFractionDigits: 0,
|
|
maximumFractionDigits: 0
|
|
}).format(value);
|
|
}
|
|
|
|
// Export
|
|
window.ChartFactory = ChartFactory;
|
|
window.ChartColors = ChartColors;
|
|
window.formatCurrency = formatCurrency;
|