1274 lines
58 KiB
PHP
1274 lines
58 KiB
PHP
<?php
|
|
if (session_status() === PHP_SESSION_NONE) {
|
|
session_start();
|
|
}
|
|
require_once 'db/config.php';
|
|
|
|
// Verificar si es admin
|
|
if (!isset($_SESSION['user_id']) || !in_array($_SESSION['user_role'], ['Administrador', 'admin'])) {
|
|
header("Location: index.php");
|
|
exit();
|
|
}
|
|
|
|
$db = db();
|
|
|
|
// --- LÓGICA DE FILTROS ---
|
|
$period = $_GET['period'] ?? '7';
|
|
$start_date = $_GET['start_date'] ?? '';
|
|
$end_date = $_GET['end_date'] ?? '';
|
|
|
|
$date_condition = "";
|
|
$label_period = "";
|
|
|
|
if ($period === 'custom' && !empty($start_date) && !empty($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(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(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(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 30 DAY)";
|
|
$label_period = "Últimos 30 días";
|
|
break;
|
|
case 'month':
|
|
$date_condition = "MONTH(p.created_at) = MONTH(CURDATE()) AND YEAR(p.created_at) = YEAR(CURDATE())";
|
|
$label_period = "Este Mes";
|
|
break;
|
|
case 'last_month':
|
|
$date_condition = "MONTH(p.created_at) = MONTH(DATE_SUB(CURDATE(), INTERVAL 1 MONTH)) AND YEAR(p.created_at) = YEAR(DATE_SUB(CURDATE(), INTERVAL 1 MONTH))";
|
|
$label_period = "Mes Anterior";
|
|
break;
|
|
case '3_months':
|
|
$date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)";
|
|
$label_period = "Últimos 3 Meses";
|
|
break;
|
|
case '6_months':
|
|
$date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 6 MONTH)";
|
|
$label_period = "Últimos 6 Meses";
|
|
break;
|
|
case 'year':
|
|
$date_condition = "DATE(p.created_at) >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)";
|
|
$label_period = "Último Año";
|
|
break;
|
|
default:
|
|
$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 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í)
|
|
$stmtPendientes = $db->query("SELECT COUNT(*) FROM pedidos WHERE estado = 'ROTULADO 📦'");
|
|
$pendientes = $stmtPendientes->fetchColumn();
|
|
|
|
// 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 (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 (Ajustado al periodo)
|
|
$ventasTendencia = [];
|
|
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(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(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']];
|
|
}
|
|
}
|
|
|
|
// 6. Estados de Pedidos (Ajustado al periodo)
|
|
$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) - TOTAL
|
|
$stmtTopProd = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado NOT IN ('RETORNADO', 'GESTIONES') GROUP BY producto ORDER BY ventas DESC LIMIT 10");
|
|
$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 10");
|
|
$topProductosCE = $stmtTopProdCE->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 7c. Top Productos Provincia (Ajustado al periodo)
|
|
$stmtTopProdProv = $db->query("SELECT producto, SUM(cantidad) as ventas FROM pedidos p WHERE $date_condition AND estado NOT IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO', 'GESTIONES') GROUP BY producto ORDER BY ventas DESC LIMIT 10");
|
|
$topProductosProv = $stmtTopProdProv->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 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 $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 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 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, 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 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, 0) * p.cantidad)) as utilidad_total
|
|
FROM pedidos p
|
|
LEFT JOIN products pr ON p.producto = pr.nombre
|
|
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 estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO') THEN 'Contraentrega'
|
|
ELSE 'Provincia'
|
|
END as canal,
|
|
estado,
|
|
COUNT(*) as total,
|
|
SUM(monto_total) as monto
|
|
FROM pedidos p
|
|
WHERE $date_condition
|
|
GROUP BY canal, estado");
|
|
$detalleCanalRaw = $stmtDetalleCanal->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 13. Top Productos Detallado (Para la tabla) - TOTAL
|
|
$stmtTopDetalle = $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 NOT IN ('RETORNADO', 'GESTIONES')
|
|
GROUP BY p.producto
|
|
ORDER BY total_cantidad DESC
|
|
LIMIT 10
|
|
");
|
|
$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 10
|
|
");
|
|
$topProductosDetalleCE = $stmtTopDetalleCE->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 13c. Top Productos Detallado Provincia
|
|
$stmtTopDetalleProv = $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 NOT IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO', 'GESTIONES')
|
|
GROUP BY p.producto
|
|
ORDER BY total_cantidad DESC
|
|
LIMIT 10
|
|
");
|
|
$topProductosDetalleProv = $stmtTopDetalleProv->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 14. Rendimiento de Productos en Contraentrega (Para Gráfica de Barras Apiladas)
|
|
$stmtProdRendimientoCE = $db->query("
|
|
SELECT
|
|
producto,
|
|
COUNT(CASE WHEN estado = 'ENTREGA EXITOSA' THEN 1 END) as exitosas,
|
|
COUNT(CASE WHEN estado = 'RETORNADO' THEN 1 END) as retornados,
|
|
COUNT(CASE WHEN estado = 'RUTA_CONTRAENTREGA' THEN 1 END) as en_ruta,
|
|
COUNT(*) as total
|
|
FROM pedidos p
|
|
WHERE $date_condition
|
|
AND estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO')
|
|
GROUP BY producto
|
|
ORDER BY total DESC
|
|
LIMIT 10
|
|
");
|
|
$prodRendimientoCE = $stmtProdRendimientoCE->fetchAll(PDO::FETCH_ASSOC);
|
|
|
|
// 15. Rendimiento de Productos en Provincia
|
|
$stmtProdRendimientoProv = $db->query("
|
|
SELECT
|
|
producto,
|
|
COUNT(CASE WHEN estado = 'COMPLETADO ✅' THEN 1 END) as completados,
|
|
COUNT(CASE WHEN estado = 'GESTIONES' THEN 1 END) as gestiones,
|
|
COUNT(CASE WHEN estado IN ('ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬') THEN 1 END) as en_proceso,
|
|
COUNT(*) as total
|
|
FROM pedidos p
|
|
WHERE $date_condition
|
|
AND estado NOT IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO')
|
|
GROUP BY producto
|
|
ORDER BY total DESC
|
|
LIMIT 10
|
|
");
|
|
$prodRendimientoProv = $stmtProdRendimientoProv->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';
|
|
?>
|
|
|
|
<div class="container-fluid mt-4">
|
|
<!-- Filtros -->
|
|
<div class="card shadow mb-4">
|
|
<div class="card-body">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-6">
|
|
<h1 class="h3 mb-0 text-gray-800">Dashboard de Control</h1>
|
|
<p class="text-muted mb-0">Mostrando datos de: <strong><?php echo $label_period; ?></strong></p>
|
|
</div>
|
|
<div class="col-md-6 text-md-end mt-3 mt-md-0">
|
|
<form action="" method="GET" class="d-inline-block">
|
|
<div class="btn-group me-2" role="group">
|
|
<a href="?period=today" class="btn btn-outline-primary btn-sm <?php echo $period == 'today' ? 'active' : ''; ?>">Hoy</a>
|
|
<a href="?period=yesterday" class="btn btn-outline-primary btn-sm <?php echo $period == 'yesterday' ? 'active' : ''; ?>">Ayer</a>
|
|
<a href="?period=7" class="btn btn-outline-primary btn-sm <?php echo $period == '7' ? 'active' : ''; ?>">7 Días</a>
|
|
<a href="?period=15" class="btn btn-outline-primary btn-sm <?php echo $period == '15' ? 'active' : ''; ?>">15 Días</a>
|
|
<a href="?period=30" class="btn btn-outline-primary btn-sm <?php echo $period == '30' ? 'active' : ''; ?>">30 Días</a>
|
|
<a href="?period=month" class="btn btn-outline-primary btn-sm <?php echo $period == 'month' ? 'active' : ''; ?>">Este Mes</a>
|
|
<a href="?period=last_month" class="btn btn-outline-primary btn-sm <?php echo $period == 'last_month' ? 'active' : ''; ?>">Mes Anterior</a>
|
|
<a href="?period=3_months" class="btn btn-outline-primary btn-sm <?php echo $period == '3_months' ? 'active' : ''; ?>">3 Meses</a>
|
|
<a href="?period=6_months" class="btn btn-outline-primary btn-sm <?php echo $period == '6_months' ? 'active' : ''; ?>">6 Meses</a>
|
|
<a href="?period=year" class="btn btn-outline-primary btn-sm <?php echo $period == 'year' ? 'active' : ''; ?>">1 Año</a>
|
|
</div>
|
|
</form>
|
|
<button class="btn btn-sm btn-outline-secondary" type="button" data-bs-toggle="collapse" data-bs-target="#customDateCollapse">
|
|
<i class="fas fa-calendar-alt"></i> Personalizado
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div class="collapse mt-3" id="customDateCollapse">
|
|
<form action="" method="GET" class="row g-2 justify-content-end">
|
|
<input type="hidden" name="period" value="custom">
|
|
<div class="col-auto">
|
|
<label class="visually-hidden">Desde</label>
|
|
<input type="date" name="start_date" class="form-control form-control-sm" value="<?php echo $start_date; ?>" required>
|
|
</div>
|
|
<div class="col-auto">
|
|
<label class="visually-hidden">Hasta</label>
|
|
<input type="date" name="end_date" class="form-control form-control-sm" value="<?php echo $end_date; ?>" required>
|
|
</div>
|
|
<div class="col-auto">
|
|
<button type="submit" class="btn btn-primary btn-sm">Filtrar</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tarjetas de Resumen -->
|
|
<div class="row mb-4">
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-primary text-white shadow h-100">
|
|
<div class="card-body">
|
|
<h6 class="card-title">Ventas Periodo</h6>
|
|
<h3>S/ <?php echo number_format($statsPeriodo['total_dinero'] ?: 0, 2); ?></h3>
|
|
<p class="mb-0"><?php echo $statsPeriodo['total_pedidos']; ?> pedidos</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-3 col-md-6 mb-3">
|
|
<div class="card bg-success text-white shadow h-100">
|
|
<div class="card-body">
|
|
<h6 class="card-title">Utilidad Estimada</h6>
|
|
<h3>S/ <?php echo number_format($utilidadTotal, 2); ?></h3>
|
|
<p class="mb-0">Margen bruto del periodo</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-2 col-md-4 mb-3">
|
|
<div class="card bg-warning text-dark shadow h-100">
|
|
<div class="card-body">
|
|
<h6 class="card-title">Pendientes</h6>
|
|
<h3><?php echo $pendientes; ?></h3>
|
|
<p class="mb-0">Por procesar</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-2 col-md-4 mb-3">
|
|
<div class="card bg-info text-white shadow h-100">
|
|
<div class="card-body">
|
|
<h6 class="card-title">Recaudo Esperado</h6>
|
|
<h3>S/ <?php echo number_format($recaudoEsperado, 2); ?></h3>
|
|
<p class="mb-0">En ruta (Global)</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-xl-2 col-md-4 mb-3">
|
|
<div class="card bg-danger text-white shadow h-100">
|
|
<div class="card-body">
|
|
<h6 class="card-title">Stock Crítico</h6>
|
|
<h3><?php echo $stockCritico; ?></h3>
|
|
<p class="mb-0">Bajo stock</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tarjetas de Eficiencia y Crecimiento -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4 mb-3">
|
|
<div class="card border-left-primary shadow h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col mr-2">
|
|
<div class="text-xs font-weight-bold text-primary text-uppercase mb-1">Ventas Mes Actual vs Pasado</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800">S/ <?php echo number_format($montoMesActual, 2); ?></div>
|
|
<div class="mt-2 mb-0 text-muted text-xs">
|
|
<span class="<?php echo $crecimientoMensual >= 0 ? 'text-success' : 'text-danger'; ?> mr-2">
|
|
<i class="fa <?php echo $crecimientoMensual >= 0 ? 'fa-arrow-up' : 'fa-arrow-down'; ?>"></i> <?php echo number_format(abs($crecimientoMensual), 1); ?>%
|
|
</span>
|
|
<span>Desde el mes pasado</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-calendar fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<div class="card border-left-info shadow h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col mr-2">
|
|
<div class="text-xs font-weight-bold text-info text-uppercase mb-1">Tiempo Promedio de Entrega</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo number_format($tiempoPromedio, 1); ?> Días</div>
|
|
<div class="mt-2 mb-0 text-muted text-xs">
|
|
<span>En el periodo seleccionado</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-shipping-fast fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-4 mb-3">
|
|
<div class="card border-left-danger shadow h-100 py-2">
|
|
<div class="card-body">
|
|
<div class="row no-gutters align-items-center">
|
|
<div class="col mr-2">
|
|
<div class="text-xs font-weight-bold text-danger text-uppercase mb-1">Tasa de Retorno (Devoluciones)</div>
|
|
<div class="h5 mb-0 font-weight-bold text-gray-800"><?php echo number_format($tasaRetorno, 1); ?>%</div>
|
|
<div class="mt-2 mb-0 text-muted text-xs">
|
|
<span>En el periodo seleccionado</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-auto">
|
|
<i class="fas fa-undo fa-2x text-gray-300"></i>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráficas Circulares por Canal -->
|
|
<div class="row mb-4">
|
|
<!-- Gráfica Provincia -->
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card shadow h-100">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 text-primary"><i class="fas fa-chart-pie me-2"></i> Estados Provincia</h5>
|
|
<span class="badge bg-primary text-white">Distribución Interna</span>
|
|
</div>
|
|
<div class="card-body d-flex flex-column align-items-center justify-content-center" style="min-height: 300px;">
|
|
<div style="width: 100%; max-width: 450px;">
|
|
<canvas id="provinciaPieChart"></canvas>
|
|
</div>
|
|
<div class="mt-3 text-center">
|
|
<div class="h4 mb-0 fw-bold text-primary"><?php echo $canalesResumen['Provincia']['pedidos']; ?></div>
|
|
<div class="small text-muted">Pedidos Totales Provincia</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráfica Contraentrega -->
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card shadow h-100">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 text-warning"><i class="fas fa-chart-pie me-2"></i> Estados Contraentrega</h5>
|
|
<span class="badge bg-warning text-dark">Distribución Interna</span>
|
|
</div>
|
|
<div class="card-body d-flex flex-column align-items-center justify-content-center" style="min-height: 300px;">
|
|
<div style="width: 100%; max-width: 450px;">
|
|
<canvas id="contraentregaPieChart"></canvas>
|
|
</div>
|
|
<div class="mt-3 text-center">
|
|
<div class="h4 mb-0 fw-bold text-warning"><?php echo $canalesResumen['Contraentrega']['pedidos']; ?></div>
|
|
<div class="small text-muted">Pedidos Totales Contraentrega</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Comparativa de Canales: Provincia vs Contraentrega -->
|
|
<div class="row mb-4">
|
|
<!-- Sección Provincia -->
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card shadow h-100">
|
|
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="fas fa-map-marked-alt me-2"></i> Provincia (Agencias)</h5>
|
|
<span class="badge bg-light text-primary">Logística Externa</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row mb-3">
|
|
<div class="col-6 border-end">
|
|
<small class="text-muted d-block">Pedidos</small>
|
|
<h4 class="mb-0"><?php echo $canalesResumen['Provincia']['pedidos']; ?></h4>
|
|
</div>
|
|
<div class="col-6">
|
|
<small class="text-muted d-block">Monto Total</small>
|
|
<h4 class="mb-0 text-primary">S/ <?php echo number_format($canalesResumen['Provincia']['monto'], 2); ?></h4>
|
|
</div>
|
|
</div>
|
|
<h6 class="border-bottom pb-2 mb-3">Distribución de Estados</h6>
|
|
<div class="list-group list-group-flush">
|
|
<?php
|
|
$estadosProv = ['ROTULADO 📦', 'EN TRANSITO 🚛', 'EN DESTINO 🏬', 'COMPLETADO ✅', 'GESTIONES'];
|
|
foreach ($estadosProv as $est):
|
|
$count = $canalesResumen['Provincia']['estados'][$est] ?? 0;
|
|
$pct = $canalesResumen['Provincia']['pedidos'] > 0 ? ($count / array_sum($canalesResumen['Provincia']['estados'])) * 100 : 0;
|
|
?>
|
|
<div class="list-group-item d-flex justify-content-between align-items-center px-0">
|
|
<span><?php echo $est; ?></span>
|
|
<div class="d-flex align-items-center">
|
|
<span class="badge bg-secondary rounded-pill me-2"><?php echo $count; ?></span>
|
|
<small class="text-muted" style="width: 40px;"><?php echo round($pct); ?>%</small>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sección Contraentrega -->
|
|
<div class="col-md-6 mb-3">
|
|
<div class="card shadow h-100">
|
|
<div class="card-header bg-warning text-dark d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0"><i class="fas fa-motorcycle me-2"></i> Contraentrega (Local)</h5>
|
|
<span class="badge bg-dark text-white">Ruta Propia</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row mb-3">
|
|
<div class="col-6 border-end">
|
|
<small class="text-muted d-block">Pedidos</small>
|
|
<h4 class="mb-0"><?php echo $canalesResumen['Contraentrega']['pedidos']; ?></h4>
|
|
</div>
|
|
<div class="col-6">
|
|
<small class="text-muted d-block">Monto Total</small>
|
|
<h4 class="mb-0 text-warning">S/ <?php echo number_format($canalesResumen['Contraentrega']['monto'], 2); ?></h4>
|
|
</div>
|
|
</div>
|
|
<h6 class="border-bottom pb-2 mb-3">Estados Ruta Contraentrega</h6>
|
|
<div class="list-group list-group-flush">
|
|
<?php
|
|
$estadosCE = ['RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO'];
|
|
foreach ($estadosCE as $est):
|
|
$count = $canalesResumen['Contraentrega']['estados'][$est] ?? 0;
|
|
$totalCE = array_sum($canalesResumen['Contraentrega']['estados']);
|
|
$pct = $totalCE > 0 ? ($count / $totalCE) * 100 : 0;
|
|
|
|
$color = 'secondary';
|
|
if ($est == 'RUTA_CONTRAENTREGA') $color = 'primary';
|
|
if ($est == 'ENTREGA EXITOSA') $color = 'success';
|
|
if ($est == 'RETORNADO') $color = 'danger';
|
|
?>
|
|
<div class="list-group-item d-flex justify-content-between align-items-center px-0">
|
|
<strong><?php echo $est; ?></strong>
|
|
<div class="d-flex align-items-center">
|
|
<span class="badge bg-<?php echo $color; ?> rounded-pill me-2"><?php echo $count; ?></span>
|
|
<small class="text-muted" style="width: 40px;"><?php echo round($pct); ?>%</small>
|
|
</div>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<div class="mt-3">
|
|
<a href="ruta_contraentrega.php" class="btn btn-sm btn-outline-warning w-100">Ver Detalle de Ruta</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Gráfico de Ventas -->
|
|
<div class="col-md-7 mb-4">
|
|
<div class="card shadow">
|
|
<div class="card-header bg-white">
|
|
<h5 class="mb-0">Tendencia de Ventas (<?php echo $label_period; ?>)</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="ventasChart" height="100"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- Gráfico de Estados -->
|
|
<div class="col-md-5 mb-4">
|
|
<div class="card shadow">
|
|
<div class="card-header bg-white">
|
|
<h5 class="mb-0">Distribución de Estados</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="estadosChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Top Productos Total -->
|
|
<div class="col-12 mb-4">
|
|
<div class="card shadow h-100">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 text-primary"><i class="fas fa-trophy me-2"></i> Top 10 Productos Más Vendidos (Total)</h5>
|
|
<span class="badge bg-primary">Rendimiento Global</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-12 mb-4">
|
|
<canvas id="productosChart" style="height: 550px;"></canvas>
|
|
</div>
|
|
<div class="col-12">
|
|
<div class="table-responsive">
|
|
<table class="table table-hover table-sm align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Producto</th>
|
|
<th class="text-center">Cant.</th>
|
|
<th class="text-end">Monto Total</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($topProductosDetalle as $prod): ?>
|
|
<tr>
|
|
<td class="fw-bold text-dark small" style="font-size: 0.85rem;"><?php echo htmlspecialchars(strlen($prod['producto']) > 35 ? substr($prod['producto'], 0, 35) . '...' : $prod['producto']); ?></td>
|
|
<td class="text-center">
|
|
<span class="badge bg-light text-primary border border-primary px-3">
|
|
<?php echo number_format($prod['total_cantidad']); ?>
|
|
</span>
|
|
</td>
|
|
<td class="text-end fw-bold text-success" style="font-size: 0.85rem;">
|
|
S/ <?php echo number_format($prod['monto_total'], 2); ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Top Productos Contraentrega -->
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card shadow h-100 border-left-warning">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 text-warning">Top 10 Contraentrega</h5>
|
|
<span class="badge bg-warning text-dark">Motorizado</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="productosCEChart" class="mb-4" style="height: 450px;"></canvas>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-hover table-sm align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Producto</th>
|
|
<th class="text-center">Cant.</th>
|
|
<th class="text-end">Monto</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($topProductosDetalleCE as $prod): ?>
|
|
<tr>
|
|
<td class="fw-bold text-dark small" style="font-size: 0.75rem;"><?php echo htmlspecialchars(strlen($prod['producto']) > 25 ? substr($prod['producto'], 0, 25) . '...' : $prod['producto']); ?></td>
|
|
<td class="text-center">
|
|
<span class="badge bg-light text-warning border border-warning">
|
|
<?php echo number_format($prod['total_cantidad']); ?>
|
|
</span>
|
|
</td>
|
|
<td class="text-end fw-bold text-success small" style="font-size: 0.75rem;">
|
|
S/ <?php echo number_format($prod['monto_total'], 2); ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Top Productos Provincia -->
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card shadow h-100 border-left-info">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 text-info">Top 10 Provincia</h5>
|
|
<span class="badge bg-info text-white">Agencias</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="productosProvChart" class="mb-4" style="height: 450px;"></canvas>
|
|
|
|
<div class="table-responsive">
|
|
<table class="table table-hover table-sm align-middle">
|
|
<thead class="table-light">
|
|
<tr>
|
|
<th>Producto</th>
|
|
<th class="text-center">Cant.</th>
|
|
<th class="text-end">Monto</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<?php foreach ($topProductosDetalleProv as $prod): ?>
|
|
<tr>
|
|
<td class="fw-bold text-dark small" style="font-size: 0.75rem;"><?php echo htmlspecialchars(strlen($prod['producto']) > 25 ? substr($prod['producto'], 0, 25) . '...' : $prod['producto']); ?></td>
|
|
<td class="text-center">
|
|
<span class="badge bg-light text-info border border-info">
|
|
<?php echo number_format($prod['total_cantidad']); ?>
|
|
</span>
|
|
</td>
|
|
<td class="text-end fw-bold text-success small" style="font-size: 0.75rem;">
|
|
S/ <?php echo number_format($prod['monto_total'], 2); ?>
|
|
</td>
|
|
</tr>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Rendimiento Detallado por Producto en Provincia -->
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card shadow border-left-primary h-100">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 text-primary"><i class="fas fa-chart-bar me-2"></i> Rendimiento Producto (Provincia)</h5>
|
|
<span class="badge bg-primary text-white">Top 10</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div style="height: 350px;">
|
|
<canvas id="rendimientoProdProvChart"></canvas>
|
|
</div>
|
|
<div class="mt-3 small text-muted text-center">
|
|
<i class="fas fa-info-circle me-1"></i> Completados vs Gestiones en Provincia.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rendimiento Detallado por Producto en Contraentrega -->
|
|
<div class="col-md-6 mb-4">
|
|
<div class="card shadow border-left-warning h-100">
|
|
<div class="card-header bg-white d-flex justify-content-between align-items-center">
|
|
<h5 class="mb-0 text-warning"><i class="fas fa-chart-bar me-2"></i> Rendimiento Producto (Contraentrega)</h5>
|
|
<span class="badge bg-warning text-dark">Top 10</span>
|
|
</div>
|
|
<div class="card-body">
|
|
<div style="height: 350px;">
|
|
<canvas id="rendimientoProdCEChart"></canvas>
|
|
</div>
|
|
<div class="mt-3 small text-muted text-center">
|
|
<i class="fas fa-info-circle me-1"></i> Éxito vs Retorno en Contraentrega.
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Ventas por Asesor -->
|
|
<div class="col-md-12 mb-4">
|
|
<div class="card shadow">
|
|
<div class="card-header bg-white">
|
|
<h5 class="mb-0">Rendimiento por Asesor (Monto Total)</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="asesoresChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
|
|
<script>
|
|
// Registrar el plugin de etiquetas de datos
|
|
Chart.register(ChartDataLabels);
|
|
|
|
// Gráfico de Provincia (Doughnut)
|
|
const ctxProvincia = document.getElementById('provinciaPieChart').getContext('2d');
|
|
new Chart(ctxProvincia, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: [
|
|
'ROTULADO 📦 (<?php echo $canalesResumen['Provincia']['estados']['ROTULADO 📦'] ?? 0; ?>)',
|
|
'EN TRANSITO 🚛 (<?php echo $canalesResumen['Provincia']['estados']['EN TRANSITO 🚛'] ?? 0; ?>)',
|
|
'EN DESTINO 🏬 (<?php echo $canalesResumen['Provincia']['estados']['EN DESTINO 🏬'] ?? 0; ?>)',
|
|
'COMPLETADO ✅ (<?php echo $canalesResumen['Provincia']['estados']['COMPLETADO ✅'] ?? 0; ?>)',
|
|
'GESTIONES (<?php echo $canalesResumen['Provincia']['estados']['GESTIONES'] ?? 0; ?>)'
|
|
],
|
|
datasets: [{
|
|
data: [
|
|
<?php echo $canalesResumen['Provincia']['estados']['ROTULADO 📦'] ?? 0; ?>,
|
|
<?php echo $canalesResumen['Provincia']['estados']['EN TRANSITO 🚛'] ?? 0; ?>,
|
|
<?php echo $canalesResumen['Provincia']['estados']['EN DESTINO 🏬'] ?? 0; ?>,
|
|
<?php echo $canalesResumen['Provincia']['estados']['COMPLETADO ✅'] ?? 0; ?>,
|
|
<?php echo $canalesResumen['Provincia']['estados']['GESTIONES'] ?? 0; ?>
|
|
],
|
|
backgroundColor: ['#ffc107', '#0dcaf0', '#6610f2', '#198754', '#6c757d'],
|
|
hoverOffset: 10,
|
|
borderWidth: 0,
|
|
cutout: '70%'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'right',
|
|
labels: {
|
|
boxWidth: 15,
|
|
padding: 20,
|
|
color: '#000',
|
|
font: { size: 14, weight: 'bold' }
|
|
}
|
|
},
|
|
datalabels: {
|
|
color: '#fff',
|
|
font: { weight: 'bold', size: 16 },
|
|
formatter: (value, ctx) => {
|
|
let sum = 0;
|
|
let dataArr = ctx.chart.data.datasets[0].data;
|
|
dataArr.map(data => { sum += data; });
|
|
let percentage = (value * 100 / sum).toFixed(0) + "%";
|
|
return value > 0 ? percentage : '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Contraentrega (Doughnut)
|
|
const ctxContraentrega = document.getElementById('contraentregaPieChart').getContext('2d');
|
|
new Chart(ctxContraentrega, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: [
|
|
'RUTA CE (<?php echo $canalesResumen['Contraentrega']['estados']['RUTA_CONTRAENTREGA'] ?? 0; ?>)',
|
|
'EXITOSA ✅ (<?php echo $canalesResumen['Contraentrega']['estados']['ENTREGA EXITOSA'] ?? 0; ?>)',
|
|
'RETORNADO (<?php echo $canalesResumen['Contraentrega']['estados']['RETORNADO'] ?? 0; ?>)'
|
|
],
|
|
datasets: [{
|
|
data: [
|
|
<?php echo $canalesResumen['Contraentrega']['estados']['RUTA_CONTRAENTREGA'] ?? 0; ?>,
|
|
<?php echo $canalesResumen['Contraentrega']['estados']['ENTREGA EXITOSA'] ?? 0; ?>,
|
|
<?php echo $canalesResumen['Contraentrega']['estados']['RETORNADO'] ?? 0; ?>
|
|
],
|
|
backgroundColor: ['#0d6efd', '#198754', '#dc3545'],
|
|
hoverOffset: 10,
|
|
borderWidth: 0,
|
|
cutout: '70%'
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'right',
|
|
labels: {
|
|
boxWidth: 15,
|
|
padding: 20,
|
|
color: '#000',
|
|
font: { size: 14, weight: 'bold' }
|
|
}
|
|
},
|
|
datalabels: {
|
|
color: '#fff',
|
|
font: { weight: 'bold', size: 16 },
|
|
formatter: (value, ctx) => {
|
|
let sum = 0;
|
|
let dataArr = ctx.chart.data.datasets[0].data;
|
|
dataArr.map(data => { sum += data; });
|
|
let percentage = (value * 100 / sum).toFixed(0) + "%";
|
|
return value > 0 ? percentage : '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Ventas
|
|
const ctxVentas = document.getElementById('ventasChart').getContext('2d');
|
|
new Chart(ctxVentas, {
|
|
type: 'line',
|
|
data: {
|
|
labels: <?php echo json_encode(array_column($ventasTendencia, 'fecha')); ?>,
|
|
datasets: [{
|
|
label: 'Monto (S/)',
|
|
data: <?php echo json_encode(array_column($ventasTendencia, 'monto')); ?>,
|
|
borderColor: '#0d6efd',
|
|
backgroundColor: 'rgba(13, 110, 253, 0.1)',
|
|
fill: true,
|
|
yAxisID: 'y',
|
|
}, {
|
|
label: 'Cantidad Pedidos',
|
|
data: <?php echo json_encode(array_column($ventasTendencia, 'cantidad')); ?>,
|
|
borderColor: '#198754',
|
|
borderDash: [5, 5],
|
|
yAxisID: 'y1',
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
scales: {
|
|
y: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'left',
|
|
title: { display: true, text: 'Soles' }
|
|
},
|
|
y1: {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'right',
|
|
grid: { drawOnChartArea: false },
|
|
title: { display: true, text: 'Pedidos' }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Estados
|
|
const ctxEstados = document.getElementById('estadosChart').getContext('2d');
|
|
new Chart(ctxEstados, {
|
|
type: 'doughnut',
|
|
data: {
|
|
labels: <?php
|
|
$labelsConValores = array_map(function($item) {
|
|
return $item['estado'] . ' (' . $item['total'] . ')';
|
|
}, $estadosData);
|
|
echo json_encode($labelsConValores);
|
|
?>,
|
|
datasets: [{
|
|
data: <?php echo json_encode(array_column($estadosData, 'total')); ?>,
|
|
backgroundColor: ['#ffc107', '#0dcaf0', '#198754', '#dc3545', '#6c757d', '#0d6efd', '#20c997', '#6610f2'],
|
|
borderWidth: 0
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
plugins: {
|
|
legend: {
|
|
display: true,
|
|
position: 'right',
|
|
labels: {
|
|
boxWidth: 15,
|
|
padding: 15,
|
|
color: '#000',
|
|
font: { size: 12, weight: 'bold' }
|
|
}
|
|
},
|
|
datalabels: {
|
|
color: '#fff',
|
|
font: { weight: 'bold', size: 14 },
|
|
formatter: (value, ctx) => {
|
|
let sum = 0;
|
|
let dataArr = ctx.chart.data.datasets[0].data;
|
|
dataArr.map(data => { sum += data; });
|
|
let percentage = (value * 100 / sum).toFixed(0) + "%";
|
|
return value > 0 ? percentage : '';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Productos
|
|
const ctxProductos = document.getElementById('productosChart').getContext('2d');
|
|
new Chart(ctxProductos, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: <?php echo json_encode(array_map(function($p) { return strlen($p['producto']) > 30 ? substr($p['producto'], 0, 30) . '...' : $p['producto']; }, $topProductos)); ?>,
|
|
datasets: [{
|
|
label: 'Unidades',
|
|
data: <?php echo json_encode(array_column($topProductos, 'ventas')); ?>,
|
|
backgroundColor: '#0d6efd',
|
|
borderRadius: 5,
|
|
barPercentage: 0.7
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: 'x',
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
datalabels: {
|
|
anchor: 'end',
|
|
align: 'top',
|
|
color: '#000',
|
|
offset: 5,
|
|
font: { size: 13, weight: 'bold' }
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: {
|
|
drawBorder: false,
|
|
color: 'rgba(0,0,0,0.05)'
|
|
},
|
|
title: { display: true, text: 'Unidades Vendidas', font: { weight: 'bold' } }
|
|
},
|
|
x: {
|
|
grid: { display: false },
|
|
ticks: {
|
|
maxRotation: 45,
|
|
minRotation: 45,
|
|
font: { size: 11, weight: 'bold' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Productos Contraentrega
|
|
const ctxProductosCE = document.getElementById('productosCEChart').getContext('2d');
|
|
new Chart(ctxProductosCE, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: <?php echo json_encode(array_map(function($p) { return strlen($p['producto']) > 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto']; }, $topProductosCE)); ?>,
|
|
datasets: [{
|
|
label: 'Unidades',
|
|
data: <?php echo json_encode(array_column($topProductosCE, 'ventas')); ?>,
|
|
backgroundColor: '#ffc107',
|
|
borderRadius: 5,
|
|
barPercentage: 0.7
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: 'x',
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
datalabels: {
|
|
anchor: 'end',
|
|
align: 'top',
|
|
color: '#000',
|
|
font: { size: 11, weight: 'bold' }
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: { color: 'rgba(0,0,0,0.05)' }
|
|
},
|
|
x: {
|
|
grid: { display: false },
|
|
ticks: {
|
|
maxRotation: 45,
|
|
minRotation: 45,
|
|
font: { size: 10, weight: 'bold' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Productos Provincia
|
|
const ctxProductosProv = document.getElementById('productosProvChart').getContext('2d');
|
|
new Chart(ctxProductosProv, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: <?php echo json_encode(array_map(function($p) { return strlen($p['producto']) > 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto']; }, $topProductosProv)); ?>,
|
|
datasets: [{
|
|
label: 'Unidades',
|
|
data: <?php echo json_encode(array_column($topProductosProv, 'ventas')); ?>,
|
|
backgroundColor: '#0dcaf0',
|
|
borderRadius: 5,
|
|
barPercentage: 0.7
|
|
}]
|
|
},
|
|
options: {
|
|
indexAxis: 'x',
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false },
|
|
datalabels: {
|
|
anchor: 'end',
|
|
align: 'top',
|
|
color: '#000',
|
|
font: { size: 11, weight: 'bold' }
|
|
}
|
|
},
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
grid: { color: 'rgba(0,0,0,0.05)' }
|
|
},
|
|
x: {
|
|
grid: { display: false },
|
|
ticks: {
|
|
maxRotation: 45,
|
|
minRotation: 45,
|
|
font: { size: 10, weight: 'bold' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Rendimiento por Producto en Contraentrega (Barras Apiladas - Vertical)
|
|
const ctxRendimientoCE = document.getElementById('rendimientoProdCEChart').getContext('2d');
|
|
new Chart(ctxRendimientoCE, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: <?php echo json_encode(array_map(function($p) {
|
|
return strlen($p['producto']) > 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto'];
|
|
}, $prodRendimientoCE)); ?>,
|
|
datasets: [
|
|
{
|
|
label: 'Exitosa ✅',
|
|
data: <?php echo json_encode(array_column($prodRendimientoCE, 'exitosas')); ?>,
|
|
backgroundColor: '#198754',
|
|
},
|
|
{
|
|
label: 'Retornado ❌',
|
|
data: <?php echo json_encode(array_column($prodRendimientoCE, 'retornados')); ?>,
|
|
backgroundColor: '#dc3545',
|
|
},
|
|
{
|
|
label: 'En Ruta 🚛',
|
|
data: <?php echo json_encode(array_column($prodRendimientoCE, 'en_ruta')); ?>,
|
|
backgroundColor: '#0d6efd',
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
indexAxis: 'x',
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: {
|
|
stacked: true,
|
|
ticks: {
|
|
maxRotation: 45,
|
|
minRotation: 45,
|
|
color: '#000',
|
|
font: { size: 10, weight: 'bold' }
|
|
}
|
|
},
|
|
y: {
|
|
stacked: true,
|
|
title: { display: true, text: 'Cantidad' }
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
labels: { boxWidth: 12, font: { size: 11 } }
|
|
},
|
|
datalabels: {
|
|
color: '#fff',
|
|
font: { weight: 'bold', size: 10 },
|
|
formatter: (value) => value > 0 ? value : ''
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Rendimiento por Producto en Provincia (Barras Apiladas - Vertical)
|
|
const ctxRendimientoProv = document.getElementById('rendimientoProdProvChart').getContext('2d');
|
|
new Chart(ctxRendimientoProv, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: <?php echo json_encode(array_map(function($p) {
|
|
return strlen($p['producto']) > 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto'];
|
|
}, $prodRendimientoProv)); ?>,
|
|
datasets: [
|
|
{
|
|
label: 'Completado ✅',
|
|
data: <?php echo json_encode(array_column($prodRendimientoProv, 'completados')); ?>,
|
|
backgroundColor: '#198754',
|
|
},
|
|
{
|
|
label: 'Gestiones ⚠️',
|
|
data: <?php echo json_encode(array_column($prodRendimientoProv, 'gestiones')); ?>,
|
|
backgroundColor: '#6c757d',
|
|
},
|
|
{
|
|
label: 'En Proceso 🚛',
|
|
data: <?php echo json_encode(array_column($prodRendimientoProv, 'en_proceso')); ?>,
|
|
backgroundColor: '#0dcaf0',
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
indexAxis: 'x',
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
x: {
|
|
stacked: true,
|
|
ticks: {
|
|
maxRotation: 45,
|
|
minRotation: 45,
|
|
color: '#000',
|
|
font: { size: 10, weight: 'bold' }
|
|
}
|
|
},
|
|
y: {
|
|
stacked: true,
|
|
title: { display: true, text: 'Cantidad' }
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
labels: { boxWidth: 12, font: { size: 11 } }
|
|
},
|
|
datalabels: {
|
|
color: '#fff',
|
|
font: { weight: 'bold', size: 10 },
|
|
formatter: (value) => value > 0 ? value : ''
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Gráfico de Asesores
|
|
const ctxAsesores = document.getElementById('asesoresChart').getContext('2d');
|
|
new Chart(ctxAsesores, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: <?php echo json_encode(array_column($ventasAsesores, 'nombre_asesor')); ?>,
|
|
datasets: [{
|
|
label: 'Monto Total (S/)',
|
|
data: <?php echo json_encode(array_column($ventasAsesores, 'total_monto')); ?>,
|
|
backgroundColor: '#198754',
|
|
yAxisID: 'y',
|
|
}, {
|
|
label: 'Efectividad (%)',
|
|
data: <?php echo json_encode(array_map(function($a) {
|
|
return $a['total_pedidos'] > 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 %' }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<?php include 'layout_footer.php'; ?>
|