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'; ?>
Mostrando datos de:
+pedidos
+pedidos
Margen bruto del periodo
+Por procesar
En ruta / Contraentrega
+En ruta (Global)
Productos < 5 unidades
+Bajo stock
+| Producto | +Cant. | +Monto Total | +Rent. % | +
|---|---|---|---|
| + | + + + + | ++ S/ + | ++ % + | +
| No hay datos en este periodo | +|||
| Asesor | -Pedidos | -Total Ventas | -
|---|---|---|
| - | - | S/ | -