diff --git a/assets/uploads/vouchers/6a0b2f023dfb0-704.png b/assets/uploads/vouchers/6a0b2f023dfb0-704.png new file mode 100644 index 00000000..15f04a75 Binary files /dev/null and b/assets/uploads/vouchers/6a0b2f023dfb0-704.png differ diff --git a/assets/uploads/vouchers/6a0b33dadd300-Captura de pantalla 2026-05-18 104300.png b/assets/uploads/vouchers/6a0b33dadd300-Captura de pantalla 2026-05-18 104300.png new file mode 100644 index 00000000..831a0b2f Binary files /dev/null and b/assets/uploads/vouchers/6a0b33dadd300-Captura de pantalla 2026-05-18 104300.png differ diff --git a/assets/uploads/vouchers/6a0b35f5208d1-1215.png b/assets/uploads/vouchers/6a0b35f5208d1-1215.png new file mode 100644 index 00000000..a8bd2010 Binary files /dev/null and b/assets/uploads/vouchers/6a0b35f5208d1-1215.png differ diff --git a/assets/uploads/vouchers/6a0b36b8e4044-607.png b/assets/uploads/vouchers/6a0b36b8e4044-607.png new file mode 100644 index 00000000..b8885571 Binary files /dev/null and b/assets/uploads/vouchers/6a0b36b8e4044-607.png differ diff --git a/assets/uploads/vouchers/6a0b39d6978af-771.png b/assets/uploads/vouchers/6a0b39d6978af-771.png new file mode 100644 index 00000000..ae240779 Binary files /dev/null and b/assets/uploads/vouchers/6a0b39d6978af-771.png differ diff --git a/assets/uploads/vouchers/6a0b3b844e619-205.png b/assets/uploads/vouchers/6a0b3b844e619-205.png new file mode 100644 index 00000000..0c8740a7 Binary files /dev/null and b/assets/uploads/vouchers/6a0b3b844e619-205.png differ diff --git a/assets/uploads/vouchers/6a0b3c7018e3d-Captura de pantalla 2026-05-18 112004.png b/assets/uploads/vouchers/6a0b3c7018e3d-Captura de pantalla 2026-05-18 112004.png new file mode 100644 index 00000000..ad7b6631 Binary files /dev/null and b/assets/uploads/vouchers/6a0b3c7018e3d-Captura de pantalla 2026-05-18 112004.png differ diff --git a/assets/uploads/vouchers/6a0b3d7d3ad11-Captura de pantalla 2026-05-18 112419.png b/assets/uploads/vouchers/6a0b3d7d3ad11-Captura de pantalla 2026-05-18 112419.png new file mode 100644 index 00000000..afa40d47 Binary files /dev/null and b/assets/uploads/vouchers/6a0b3d7d3ad11-Captura de pantalla 2026-05-18 112419.png differ diff --git a/assets/uploads/vouchers/6a0b3e0ad66aa-0255.png b/assets/uploads/vouchers/6a0b3e0ad66aa-0255.png new file mode 100644 index 00000000..8c806c1c Binary files /dev/null and b/assets/uploads/vouchers/6a0b3e0ad66aa-0255.png differ diff --git a/assets/uploads/vouchers/6a0b3eee68f77-018.png b/assets/uploads/vouchers/6a0b3eee68f77-018.png new file mode 100644 index 00000000..660a9b23 Binary files /dev/null and b/assets/uploads/vouchers/6a0b3eee68f77-018.png differ diff --git a/dashboard_principal.php b/dashboard_principal.php index 38319706..9e1b0e16 100644 --- a/dashboard_principal.php +++ b/dashboard_principal.php @@ -21,39 +21,47 @@ $date_condition = ""; $label_period = ""; if ($period === 'custom' && !empty($start_date) && !empty($end_date)) { - $date_condition = "DATE(created_at) BETWEEN '$start_date' AND '$end_date'"; + $date_condition = "DATE(p.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()"; + $date_condition = "DATE(p.created_at) = CURDATE()"; $label_period = "Hoy"; break; + case 'yesterday': + $date_condition = "DATE(p.created_at) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)"; + $label_period = "Ayer"; + break; case '7': - $date_condition = "DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"; + $date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 7 DAY)"; $label_period = "Últimos 7 días"; break; + case '15': + $date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 15 DAY)"; + $label_period = "Últimos 15 días"; + break; case '30': - $date_condition = "DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)"; + $date_condition = "DATE(p.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())"; + $date_condition = "MONTH(p.created_at) = MONTH(CURDATE()) AND YEAR(p.created_at) = YEAR(CURDATE())"; $label_period = "Este Mes"; break; case 'year': - $date_condition = "DATE(created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)"; + $date_condition = "DATE(p.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)"; + $date_condition = "DATE(p.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'"); +$stmtStats = $db->query("SELECT COUNT(*) as total_pedidos, SUM(monto_total) as total_dinero FROM pedidos p 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í) @@ -70,23 +78,23 @@ $stockCritico = $stmtStock->fetchColumn() ?: 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"); +if ($period === 'today' || $period === 'yesterday') { + // Si es hoy o ayer, mostrar por horas + $stmt = $db->query("SELECT HOUR(p.created_at) as hora, COUNT(*) as cant, SUM(monto_total) as monto FROM pedidos p WHERE $date_condition AND estado != 'RETORNADO' GROUP BY HOUR(p.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"); + $stmt = $db->query("SELECT DATE_FORMAT(p.created_at, '%Y-%m') as mes, COUNT(*) as cant, SUM(monto_total) as monto FROM pedidos p 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"); + $stmt = $db->query("SELECT DATE(p.created_at) as fecha, COUNT(*) as cant, SUM(monto_total) as monto FROM pedidos p WHERE $date_condition AND estado != 'RETORNADO' GROUP BY DATE(p.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']]; @@ -94,13 +102,17 @@ if ($period === 'today') { } // 6. Estados de Pedidos (Ajustado al periodo) -$stmtEstados = $db->query("SELECT estado, COUNT(*) as total FROM pedidos WHERE $date_condition GROUP BY estado"); +$stmtEstados = $db->query("SELECT estado, COUNT(*) as total FROM pedidos p WHERE $date_condition GROUP BY estado"); $estadosData = $stmtEstados->fetchAll(PDO::FETCH_ASSOC); // 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"); +$stmtTopProd = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado != 'RETORNADO' GROUP BY producto ORDER BY ventas DESC LIMIT 5"); $topProductos = $stmtTopProd->fetchAll(PDO::FETCH_ASSOC); +// 7b. Top Productos Contraentrega (Ajustado al periodo) +$stmtTopProdCE = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') GROUP BY producto ORDER BY ventas DESC LIMIT 5"); +$topProductosCE = $stmtTopProdCE->fetchAll(PDO::FETCH_ASSOC); + // 8. Ventas por Asesor (Ajustado al periodo) $stmtAsesores = $db->query("SELECT u.nombre_asesor, COUNT(p.id) as total_pedidos, @@ -117,56 +129,72 @@ $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 = $db->prepare("SELECT SUM(monto_total) as monto FROM pedidos p WHERE DATE_FORMAT(p.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 = $db->prepare("SELECT SUM(monto_total) as monto FROM pedidos p WHERE DATE_FORMAT(p.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 +$stmtTiempo = $db->query("SELECT AVG(TIMESTAMPDIFF(HOUR, p.created_at, fecha_completado)) / 24 as promedio_dias + FROM pedidos p 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"); + FROM pedidos p 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 + SELECT SUM(p.monto_total - (COALESCE(pr.costo, 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' + WHERE $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, + CASE + WHEN estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') THEN 'Contraentrega' + WHEN estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA' THEN 'Contraentrega' + ELSE 'Provincia' + END as canal, estado, COUNT(*) as total, SUM(monto_total) as monto - FROM pedidos + FROM pedidos p 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 + SELECT p.producto, SUM(p.cantidad) as total_cantidad, SUM(p.monto_total) as monto_total, AVG(pr.costo) as costo_promedio FROM pedidos p LEFT JOIN products pr ON p.producto = pr.nombre - WHERE p.$date_condition AND p.estado != 'RETORNADO' + WHERE $date_condition AND p.estado != 'RETORNADO' GROUP BY p.producto ORDER BY total_cantidad DESC LIMIT 5 "); $topProductosDetalle = $stmtTopDetalle->fetchAll(PDO::FETCH_ASSOC); +// 13b. Top Productos Detallado Contraentrega +$stmtTopDetalleCE = $db->query(" + SELECT p.producto, SUM(p.cantidad) as total_cantidad, SUM(p.monto_total) as monto_total, AVG(pr.costo) as costo_promedio + FROM pedidos p + LEFT JOIN products pr ON p.producto = pr.nombre + WHERE $date_condition AND p.estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') + GROUP BY p.producto + ORDER BY total_cantidad DESC + LIMIT 5 +"); +$topProductosDetalleCE = $stmtTopDetalleCE->fetchAll(PDO::FETCH_ASSOC); + $canalesResumen = [ 'Provincia' => ['pedidos' => 0, 'monto' => 0, 'estados' => []], 'Contraentrega' => ['pedidos' => 0, 'monto' => 0, 'estados' => []] @@ -204,7 +232,9 @@ include 'layout_header.php';
Hoy + Ayer 7 Días + 15 Días 30 Días Este Mes 1 Año @@ -344,6 +374,47 @@ include 'layout_header.php';
+ +
+ +
+
+
+
Estados Provincia
+ Distribución Interna +
+
+
+ +
+
+
+
Pedidos Totales Provincia
+
+
+
+
+ + +
+
+
+
Estados Contraentrega
+ Distribución Interna +
+
+
+ +
+
+
+
Pedidos Totales Contraentrega
+
+
+
+
+
+
@@ -513,8 +584,65 @@ include 'layout_header.php';
- + +
+
+
+
Top 5 Contraentrega
+ Solo Contraentrega +
+
+ + +
+ + + + + + + + + + + 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
+
+
+
+
+ + +
+ +
Rendimiento por Asesor (Monto Total)
@@ -529,6 +657,62 @@ include 'layout_header.php';