diff --git a/assets/uploads/vouchers/6a087cb941aee-0458.png b/assets/uploads/vouchers/6a087cb941aee-0458.png new file mode 100644 index 00000000..8aaaa9c2 Binary files /dev/null and b/assets/uploads/vouchers/6a087cb941aee-0458.png differ diff --git a/assets/uploads/vouchers/6a087d48517f4-Captura de pantalla 2026-05-16 091928.png b/assets/uploads/vouchers/6a087d48517f4-Captura de pantalla 2026-05-16 091928.png new file mode 100644 index 00000000..ca9d2c49 Binary files /dev/null and b/assets/uploads/vouchers/6a087d48517f4-Captura de pantalla 2026-05-16 091928.png differ diff --git a/assets/uploads/vouchers/6a087db014754-Captura de pantalla 2026-05-16 092221.png b/assets/uploads/vouchers/6a087db014754-Captura de pantalla 2026-05-16 092221.png new file mode 100644 index 00000000..c65a3548 Binary files /dev/null and b/assets/uploads/vouchers/6a087db014754-Captura de pantalla 2026-05-16 092221.png differ diff --git a/assets/uploads/vouchers/6a0880f6853c5-Captura de pantalla 2026-05-16 093616.png b/assets/uploads/vouchers/6a0880f6853c5-Captura de pantalla 2026-05-16 093616.png new file mode 100644 index 00000000..8ef7e5f4 Binary files /dev/null and b/assets/uploads/vouchers/6a0880f6853c5-Captura de pantalla 2026-05-16 093616.png differ diff --git a/assets/uploads/vouchers/6a088c7a0c3f7-Captura de pantalla 2026-05-16 102519.png b/assets/uploads/vouchers/6a088c7a0c3f7-Captura de pantalla 2026-05-16 102519.png new file mode 100644 index 00000000..b475f6a6 Binary files /dev/null and b/assets/uploads/vouchers/6a088c7a0c3f7-Captura de pantalla 2026-05-16 102519.png differ diff --git a/assets/uploads/vouchers/6a0891641e520-6827.png b/assets/uploads/vouchers/6a0891641e520-6827.png new file mode 100644 index 00000000..2b0c03f6 Binary files /dev/null and b/assets/uploads/vouchers/6a0891641e520-6827.png differ diff --git a/assets/uploads/vouchers/6a0895f1e21ea-416.png b/assets/uploads/vouchers/6a0895f1e21ea-416.png new file mode 100644 index 00000000..f6feaa86 Binary files /dev/null and b/assets/uploads/vouchers/6a0895f1e21ea-416.png differ diff --git a/assets/uploads/vouchers/6a089bd64d777-Screenshot_345.png b/assets/uploads/vouchers/6a089bd64d777-Screenshot_345.png new file mode 100644 index 00000000..dff7ea86 Binary files /dev/null and b/assets/uploads/vouchers/6a089bd64d777-Screenshot_345.png differ diff --git a/assets/uploads/vouchers/6a08ad2f45a79-Screenshot_346.png b/assets/uploads/vouchers/6a08ad2f45a79-Screenshot_346.png new file mode 100644 index 00000000..a7933552 Binary files /dev/null and b/assets/uploads/vouchers/6a08ad2f45a79-Screenshot_346.png differ diff --git a/assets/uploads/vouchers/6a08b07d7f9d1-Captura de pantalla 2026-05-16 125850.png b/assets/uploads/vouchers/6a08b07d7f9d1-Captura de pantalla 2026-05-16 125850.png new file mode 100644 index 00000000..28c620be Binary files /dev/null and b/assets/uploads/vouchers/6a08b07d7f9d1-Captura de pantalla 2026-05-16 125850.png differ diff --git a/assets/uploads/vouchers/6a08b2c204c66-Captura de pantalla 2026-05-16 130732.png b/assets/uploads/vouchers/6a08b2c204c66-Captura de pantalla 2026-05-16 130732.png new file mode 100644 index 00000000..074e6021 Binary files /dev/null and b/assets/uploads/vouchers/6a08b2c204c66-Captura de pantalla 2026-05-16 130732.png differ diff --git a/assets/uploads/vouchers/6a08bd028e4e3-Screenshot_347.png b/assets/uploads/vouchers/6a08bd028e4e3-Screenshot_347.png new file mode 100644 index 00000000..61387245 Binary files /dev/null and b/assets/uploads/vouchers/6a08bd028e4e3-Screenshot_347.png differ diff --git a/assets/uploads/vouchers/6a08c14d1b182-Screenshot_348.png b/assets/uploads/vouchers/6a08c14d1b182-Screenshot_348.png new file mode 100644 index 00000000..8d75d25c Binary files /dev/null and b/assets/uploads/vouchers/6a08c14d1b182-Screenshot_348.png differ diff --git a/assets/uploads/vouchers/6a08de08c3cef-480.png b/assets/uploads/vouchers/6a08de08c3cef-480.png new file mode 100644 index 00000000..31740d47 Binary files /dev/null and b/assets/uploads/vouchers/6a08de08c3cef-480.png differ diff --git a/dashboard_principal.php b/dashboard_principal.php index e707e201..38319706 100644 --- a/dashboard_principal.php +++ b/dashboard_principal.php @@ -12,95 +12,423 @@ if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_role'], ['Administ $db = db(); -// 1. Estadísticas de hoy -$hoy = date('Y-m-d'); -$stmtHoy = $db->prepare("SELECT COUNT(*) as total_pedidos, SUM(monto_total) as total_dinero FROM pedidos WHERE DATE(created_at) = ? AND estado != 'RETORNADO'"); -$stmtHoy->execute([$hoy]); -$statsHoy = $stmtHoy->fetch(PDO::FETCH_ASSOC); +// --- LÓGICA DE FILTROS --- +$period = $_GET['period'] ?? '7'; +$start_date = $_GET['start_date'] ?? ''; +$end_date = $_GET['end_date'] ?? ''; -// 2. Pedidos Pendientes (ROTULADO 📦) +$date_condition = ""; +$label_period = ""; + +if ($period === 'custom' && !empty($start_date) && !empty($end_date)) { + $date_condition = "DATE(created_at) BETWEEN '$start_date' AND '$end_date'"; + $label_period = "Desde " . date('d/m/Y', strtotime($start_date)) . " hasta " . date('d/m/Y', strtotime($end_date)); +} else { + switch ($period) { + case 'today': + $date_condition = "DATE(created_at) = CURDATE()"; + $label_period = "Hoy"; + break; + case '7': + $date_condition = "DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"; + $label_period = "Últimos 7 días"; + break; + case '30': + $date_condition = "DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)"; + $label_period = "Últimos 30 días"; + break; + case 'month': + $date_condition = "MONTH(created_at) = MONTH(CURDATE()) AND YEAR(created_at) = YEAR(CURDATE())"; + $label_period = "Este Mes"; + break; + case 'year': + $date_condition = "DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)"; + $label_period = "Último Año"; + break; + default: + $date_condition = "DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"; + $label_period = "Últimos 7 días"; + $period = '7'; + } +} + +// 1. Estadísticas del periodo seleccionado +$stmtStats = $db->query("SELECT COUNT(*) as total_pedidos, SUM(monto_total) as total_dinero FROM pedidos WHERE $date_condition AND estado != 'RETORNADO'"); +$statsPeriodo = $stmtStats->fetch(PDO::FETCH_ASSOC); + +// 2. Pedidos Pendientes (Global, no depende del filtro de fecha usualmente, pero lo mantendremos así) $stmtPendientes = $db->query("SELECT COUNT(*) FROM pedidos WHERE estado = 'ROTULADO 📦'"); $pendientes = $stmtPendientes->fetchColumn(); -// 3. Recaudo Esperado (En tránsito, En destino, Ruta contraentrega) +// 3. Recaudo Esperado (Global) $stmtRecaudo = $db->query("SELECT SUM(monto_debe) FROM pedidos WHERE estado IN ('EN TRANSITO 🚛', 'EN DESTINO 🏬', 'RUTA_CONTRAENTREGA') AND (estado_pago != 'Pagado' OR estado_pago IS NULL)"); $recaudoEsperado = $stmtRecaudo->fetchColumn() ?: 0; -// 4. Stock Crítico (menos de 5 unidades en total entre todas las sedes) +// 4. Stock Crítico (Global) $stmtStock = $db->query("SELECT COUNT(*) FROM (SELECT product_id, SUM(quantity) as total_stock FROM stock_sedes GROUP BY product_id HAVING total_stock <= 5) as critico"); $stockCritico = $stmtStock->fetchColumn() ?: 0; -// 5. Datos para Gráfico de Ventas (Últimos 7 días) -$ventas7dias = []; -for ($i = 6; $i >= 0; $i--) { - $fecha = date('Y-m-d', strtotime("-$i days")); - $stmt = $db->prepare("SELECT COUNT(*) as cant, SUM(monto_total) as monto FROM pedidos WHERE DATE(created_at) = ? AND estado != 'RETORNADO'"); - $stmt->execute([$fecha]); - $res = $stmt->fetch(PDO::FETCH_ASSOC); - $ventas7dias[] = [ - 'fecha' => date('d/m', strtotime($fecha)), - 'cantidad' => $res['cant'] ?: 0, - 'monto' => $res['monto'] ?: 0 - ]; +// 5. Datos para Gráfico de Ventas (Ajustado al periodo) +$ventasTendencia = []; +if ($period === 'today') { + // Si es hoy, mostrar por horas + $stmt = $db->query("SELECT HOUR(created_at) as hora, COUNT(*) as cant, SUM(monto_total) as monto FROM pedidos WHERE $date_condition AND estado != 'RETORNADO' GROUP BY HOUR(created_at) ORDER BY hora ASC"); + $res = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($res as $r) { + $ventasTendencia[] = ['fecha' => $r['hora'] . ':00', 'cantidad' => $r['cant'], 'monto' => $r['monto']]; + } +} elseif ($period === 'year') { + // Si es un año, mostrar por meses + $stmt = $db->query("SELECT DATE_FORMAT(created_at, '%Y-%m') as mes, COUNT(*) as cant, SUM(monto_total) as monto FROM pedidos WHERE $date_condition AND estado != 'RETORNADO' GROUP BY mes ORDER BY mes ASC"); + $res = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($res as $r) { + $ventasTendencia[] = ['fecha' => date('M Y', strtotime($r['mes'] . '-01')), 'cantidad' => $r['cant'], 'monto' => $r['monto']]; + } +} else { + // Mostrar por días + $stmt = $db->query("SELECT DATE(created_at) as fecha, COUNT(*) as cant, SUM(monto_total) as monto FROM pedidos WHERE $date_condition AND estado != 'RETORNADO' GROUP BY DATE(created_at) ORDER BY fecha ASC"); + $res = $stmt->fetchAll(PDO::FETCH_ASSOC); + foreach ($res as $r) { + $ventasTendencia[] = ['fecha' => date('d/m', strtotime($r['fecha'])), 'cantidad' => $r['cant'], 'monto' => $r['monto']]; + } } -// 6. Estados de Pedidos -$stmtEstados = $db->query("SELECT estado, COUNT(*) as total FROM pedidos GROUP BY estado"); +// 6. Estados de Pedidos (Ajustado al periodo) +$stmtEstados = $db->query("SELECT estado, COUNT(*) as total FROM pedidos WHERE $date_condition GROUP BY estado"); $estadosData = $stmtEstados->fetchAll(PDO::FETCH_ASSOC); -// 7. Top Productos -$stmtTopProd = $db->query("SELECT producto, COUNT(*) as ventas FROM pedidos GROUP BY producto ORDER BY ventas DESC LIMIT 5"); +// 7. Top Productos (Ajustado al periodo) +$stmtTopProd = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos WHERE $date_condition AND estado != 'RETORNADO' GROUP BY producto ORDER BY ventas DESC LIMIT 5"); $topProductos = $stmtTopProd->fetchAll(PDO::FETCH_ASSOC); -// 8. Ventas por Asesor -$stmtAsesores = $db->query("SELECT u.nombre_asesor, COUNT(p.id) as total_pedidos, COALESCE(SUM(p.monto_total), 0) as total_monto +// 8. Ventas por Asesor (Ajustado al periodo) +$stmtAsesores = $db->query("SELECT u.nombre_asesor, + COUNT(p.id) as total_pedidos, + COALESCE(SUM(p.monto_total), 0) as total_monto, + COUNT(CASE WHEN p.estado = 'COMPLETADO ✅' THEN 1 END) as completados FROM users u - LEFT JOIN pedidos p ON u.id = p.asesor_id AND p.estado != 'RETORNADO' - WHERE u.role = 'Asesor' + LEFT JOIN pedidos p ON u.id = p.asesor_id AND $date_condition AND p.estado != 'RETORNADO' + WHERE u.role IN ('Asesor', 'admin', 'Administrador') GROUP BY u.id + HAVING total_pedidos > 0 ORDER BY total_monto DESC"); $ventasAsesores = $stmtAsesores->fetchAll(PDO::FETCH_ASSOC); +// 9. Comparativa Mensual (Se mantiene global para contexto) +$mesActual = date('Y-m'); +$mesPasado = date('Y-m', strtotime('first day of last month')); +$stmtMesActual = $db->prepare("SELECT SUM(monto_total) as monto FROM pedidos WHERE DATE_FORMAT(created_at, '%Y-%m') = ? AND estado != 'RETORNADO'"); +$stmtMesActual->execute([$mesActual]); +$montoMesActual = $stmtMesActual->fetchColumn() ?: 0; +$stmtMesPasado = $db->prepare("SELECT SUM(monto_total) as monto FROM pedidos WHERE DATE_FORMAT(created_at, '%Y-%m') = ? AND estado != 'RETORNADO'"); +$stmtMesPasado->execute([$mesPasado]); +$montoMesPasado = $stmtMesPasado->fetchColumn() ?: 0; +$crecimientoMensual = $montoMesPasado > 0 ? (($montoMesActual - $montoMesPasado) / $montoMesPasado) * 100 : 0; + +// 10. Eficiencia Logística (Ajustado al periodo) +$stmtTiempo = $db->query("SELECT AVG(TIMESTAMPDIFF(HOUR, created_at, fecha_completado)) / 24 as promedio_dias + FROM pedidos + WHERE $date_condition AND estado = 'COMPLETADO ✅' AND fecha_completado IS NOT NULL"); +$tiempoPromedio = $stmtTiempo->fetchColumn() ?: 0; + +$stmtRetorno = $db->query("SELECT (COUNT(CASE WHEN estado = 'RETORNADO' THEN 1 END) * 100.0 / NULLIF(COUNT(*), 0)) as tasa_retorno + FROM pedidos WHERE $date_condition"); +$tasaRetorno = $stmtRetorno->fetchColumn() ?: 0; + +// 11. Utilidad Total Estimada (Ajustado al periodo) +$stmtUtilidad = $db->query(" + SELECT SUM(p.monto_total - (COALESCE(pr.costo_unitario, 0) * p.cantidad)) as utilidad_total + FROM pedidos p + LEFT JOIN products pr ON p.producto = pr.nombre + WHERE p.$date_condition AND p.estado != 'RETORNADO' +"); +$utilidadTotal = $stmtUtilidad->fetchColumn() ?: 0; + +// 12. Datos Detallados por Canal (Provincia vs Contraentrega) +$stmtDetalleCanal = $db->query("SELECT + CASE WHEN agencia = 'CONTRAENTREGA' THEN 'Contraentrega' ELSE 'Provincia' END as canal, + estado, + COUNT(*) as total, + SUM(monto_total) as monto + FROM pedidos + WHERE $date_condition + GROUP BY canal, estado"); +$detalleCanalRaw = $stmtDetalleCanal->fetchAll(PDO::FETCH_ASSOC); + +// 13. Top Productos Detallado (Para la tabla) +$stmtTopDetalle = $db->query(" + SELECT p.producto, SUM(p.cantidad) as total_cantidad, SUM(p.monto_total) as monto_total, AVG(pr.costo_unitario) as costo_promedio + FROM pedidos p + LEFT JOIN products pr ON p.producto = pr.nombre + WHERE p.$date_condition AND p.estado != 'RETORNADO' + GROUP BY p.producto + ORDER BY total_cantidad DESC + LIMIT 5 +"); +$topProductosDetalle = $stmtTopDetalle->fetchAll(PDO::FETCH_ASSOC); + +$canalesResumen = [ + 'Provincia' => ['pedidos' => 0, 'monto' => 0, 'estados' => []], + 'Contraentrega' => ['pedidos' => 0, 'monto' => 0, 'estados' => []] +]; + +foreach ($detalleCanalRaw as $row) { + $canal = $row['canal']; + $estado = $row['estado']; + + if ($estado != 'RETORNADO') { + $canalesResumen[$canal]['pedidos'] += $row['total']; + $canalesResumen[$canal]['monto'] += $row['monto']; + } + + if (!isset($canalesResumen[$canal]['estados'][$estado])) { + $canalesResumen[$canal]['estados'][$estado] = 0; + } + $canalesResumen[$canal]['estados'][$estado] += $row['total']; +} + $pageTitle = "Dashboard Principal"; include 'layout_header.php'; ?>
+ +
+
+
+
+

Dashboard de Control

+

Mostrando datos de:

+
+
+
+ +
+ +
+
+
+
+ +
+ + +
+
+ + +
+
+ +
+
+
+
+
+
-
+
-
Ventas Hoy
-

S/

-

pedidos

+
Ventas Periodo
+

S/

+

pedidos

-
+
+
+
+
Utilidad Estimada
+

S/

+

Margen bruto del periodo

+
+
+
+
-
Pedidos Pendientes
+
Pendientes

Por procesar

-
-
+
+
Recaudo Esperado

S/

-

En ruta / Contraentrega

+

En ruta (Global)

-
+
Stock Crítico

-

Productos < 5 unidades

+

Bajo stock

+
+
+
+
+ + +
+
+
+
+
+
+
Ventas Mes Actual vs Pasado
+
S/
+
+ + % + + Desde el mes pasado +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Tiempo Promedio de Entrega
+
Días
+
+ En el periodo seleccionado +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
Tasa de Retorno (Devoluciones)
+
%
+
+ En el periodo seleccionado +
+
+
+ +
+
+
+
+
+
+ + +
+ +
+
+
+
Provincia (Agencias)
+ Logística Externa +
+
+
+
+ Pedidos +

+
+
+ Monto Total +

S/

+
+
+
Distribución de Estados
+
+ 0 ? ($count / array_sum($canalesResumen['Provincia']['estados'])) * 100 : 0; + ?> +
+ +
+ + % +
+
+ +
+
+
+
+ + +
+
+
+
Contraentrega (Local)
+ Ruta Propia +
+
+
+
+ Pedidos +

+
+
+ Monto Total +

S/

+
+
+
Estados Ruta Contraentrega
+
+ 0 ? ($count / $totalCE) * 100 : 0; + + $color = 'secondary'; + if ($est == 'RUTA_CONTRAENTREGA') $color = 'primary'; + if ($est == 'ENTREGA EXITOSA') $color = 'success'; + if ($est == 'RETORNADO') $color = 'danger'; + ?> +
+ +
+ + % +
+
+ +
+
@@ -111,7 +439,7 @@ include 'layout_header.php';
-
Tendencia de Ventas (Últimos 7 días)
+
Tendencia de Ventas ()
@@ -135,11 +463,53 @@ include 'layout_header.php';
-
+
Top 5 Productos más Vendidos
+ Por Unidades
- + + +
+ + + + + + + + + + + 0) ? ($utilidad / $prod['monto_total']) * 100 : 0; + $rentClass = $rentabilidad > 30 ? 'text-success' : ($rentabilidad > 15 ? 'text-warning' : 'text-danger'); + ?> + + + + + + + + + + + + + +
ProductoCant.Monto TotalRent. %
+ + + + + S/ + + % +
No hay datos en este periodo
+
@@ -147,29 +517,10 @@ include 'layout_header.php';
-
Rendimiento por Asesor
+
Rendimiento por Asesor (Monto Total)
-
- - - - - - - - - - - - - - - - - -
AsesorPedidosTotal Ventas
S/
-
+
@@ -183,17 +534,17 @@ include 'layout_header.php'; new Chart(ctxVentas, { type: 'line', data: { - labels: , + labels: , datasets: [{ label: 'Monto (S/)', - data: , + data: , borderColor: '#0d6efd', backgroundColor: 'rgba(13, 110, 253, 0.1)', fill: true, yAxisID: 'y', }, { label: 'Cantidad Pedidos', - data: , + data: , borderColor: '#198754', borderDash: [5, 5], yAxisID: 'y1', @@ -227,7 +578,7 @@ include 'layout_header.php'; labels: , datasets: [{ data: , - backgroundColor: ['#ffc107', '#0dcaf0', '#198754', '#dc3545', '#6c757d', '#0d6efd'] + backgroundColor: ['#ffc107', '#0dcaf0', '#198754', '#dc3545', '#6c757d', '#0d6efd', '#20c997', '#6610f2'] }] } }); @@ -237,7 +588,7 @@ include 'layout_header.php'; new Chart(ctxProductos, { type: 'bar', data: { - labels: , + labels: 20 ? substr($p['producto'], 0, 20) . '...' : $p['producto']; }, $topProductos)); ?>, datasets: [{ label: 'Unidades Vendidas', data: , @@ -249,6 +600,47 @@ include 'layout_header.php'; responsive: true } }); + + // Gráfico de Asesores + const ctxAsesores = document.getElementById('asesoresChart').getContext('2d'); + new Chart(ctxAsesores, { + type: 'bar', + data: { + labels: , + datasets: [{ + label: 'Monto Total (S/)', + data: , + backgroundColor: '#198754', + yAxisID: 'y', + }, { + label: 'Efectividad (%)', + data: 0 ? round(($a['completados'] / $a['total_pedidos']) * 100, 1) : 0; + }, $ventasAsesores)); ?>, + type: 'line', + borderColor: '#ffc107', + backgroundColor: '#ffc107', + yAxisID: 'y1', + }] + }, + options: { + responsive: true, + scales: { + y: { + beginAtZero: true, + position: 'left', + title: { display: true, text: 'Soles' } + }, + y1: { + beginAtZero: true, + position: 'right', + max: 100, + grid: { drawOnChartArea: false }, + title: { display: true, text: 'Efectividad %' } + } + } + } + }); \ No newline at end of file