diff --git a/assets/uploads/marketing_images/6a0c8d55a3395.png b/assets/uploads/marketing_images/6a0c8d55a3395.png new file mode 100644 index 00000000..342ff855 Binary files /dev/null and b/assets/uploads/marketing_images/6a0c8d55a3395.png differ diff --git a/assets/uploads/vouchers/6a0c6f56d57b3-9719.png b/assets/uploads/vouchers/6a0c6f56d57b3-9719.png new file mode 100644 index 00000000..5220aa27 Binary files /dev/null and b/assets/uploads/vouchers/6a0c6f56d57b3-9719.png differ diff --git a/assets/uploads/vouchers/6a0c6fd00bc00-883.png b/assets/uploads/vouchers/6a0c6fd00bc00-883.png new file mode 100644 index 00000000..9e216e5b Binary files /dev/null and b/assets/uploads/vouchers/6a0c6fd00bc00-883.png differ diff --git a/assets/uploads/vouchers/6a0c70b6716f9-7778.png b/assets/uploads/vouchers/6a0c70b6716f9-7778.png new file mode 100644 index 00000000..2e90742e Binary files /dev/null and b/assets/uploads/vouchers/6a0c70b6716f9-7778.png differ diff --git a/assets/uploads/vouchers/6a0c71aaf37a5-716.png b/assets/uploads/vouchers/6a0c71aaf37a5-716.png new file mode 100644 index 00000000..15997657 Binary files /dev/null and b/assets/uploads/vouchers/6a0c71aaf37a5-716.png differ diff --git a/assets/uploads/vouchers/6a0c722f2d696-999.png b/assets/uploads/vouchers/6a0c722f2d696-999.png new file mode 100644 index 00000000..613db810 Binary files /dev/null and b/assets/uploads/vouchers/6a0c722f2d696-999.png differ diff --git a/assets/uploads/vouchers/6a0c7329305be-6448.png b/assets/uploads/vouchers/6a0c7329305be-6448.png new file mode 100644 index 00000000..1ce8fb53 Binary files /dev/null and b/assets/uploads/vouchers/6a0c7329305be-6448.png differ diff --git a/assets/uploads/vouchers/6a0c794d3be79-490.png b/assets/uploads/vouchers/6a0c794d3be79-490.png new file mode 100644 index 00000000..5a3a2e8b Binary files /dev/null and b/assets/uploads/vouchers/6a0c794d3be79-490.png differ diff --git a/assets/uploads/vouchers/6a0c79a0305ce-Captura de pantalla 2026-05-19 095409.png b/assets/uploads/vouchers/6a0c79a0305ce-Captura de pantalla 2026-05-19 095409.png new file mode 100644 index 00000000..46f3cae6 Binary files /dev/null and b/assets/uploads/vouchers/6a0c79a0305ce-Captura de pantalla 2026-05-19 095409.png differ diff --git a/assets/uploads/vouchers/6a0c80cc97b69-606.png b/assets/uploads/vouchers/6a0c80cc97b69-606.png new file mode 100644 index 00000000..45d46f9e Binary files /dev/null and b/assets/uploads/vouchers/6a0c80cc97b69-606.png differ diff --git a/assets/uploads/vouchers/6a0c84687bd94-Screenshot_350.png b/assets/uploads/vouchers/6a0c84687bd94-Screenshot_350.png new file mode 100644 index 00000000..b68c3366 Binary files /dev/null and b/assets/uploads/vouchers/6a0c84687bd94-Screenshot_350.png differ diff --git a/assets/uploads/vouchers/6a0c878f24c9b-192.png b/assets/uploads/vouchers/6a0c878f24c9b-192.png new file mode 100644 index 00000000..0aa1ad8b Binary files /dev/null and b/assets/uploads/vouchers/6a0c878f24c9b-192.png differ diff --git a/assets/uploads/vouchers/6a0c926f949df-Captura de pantalla 2026-05-19 113752.png b/assets/uploads/vouchers/6a0c926f949df-Captura de pantalla 2026-05-19 113752.png new file mode 100644 index 00000000..3795ddb0 Binary files /dev/null and b/assets/uploads/vouchers/6a0c926f949df-Captura de pantalla 2026-05-19 113752.png differ diff --git a/dashboard_principal.php b/dashboard_principal.php index ca0b2082..229becd6 100644 --- a/dashboard_principal.php +++ b/dashboard_principal.php @@ -49,6 +49,18 @@ if ($period === 'custom' && !empty($start_date) && !empty($end_date)) { $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"; @@ -105,14 +117,18 @@ if ($period === 'today' || $period === 'yesterday') { $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 p WHERE $date_condition AND estado != 'RETORNADO' GROUP BY producto ORDER BY ventas DESC LIMIT 5"); +// 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 5"); +$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, @@ -159,8 +175,7 @@ $utilidadTotal = $stmtUtilidad->fetchColumn() ?: 0; // 12. Datos Detallados por Canal (Provincia vs Contraentrega) $stmtDetalleCanal = $db->query("SELECT CASE - WHEN estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') THEN 'Contraentrega' - WHEN estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA' THEN 'Contraentrega' + WHEN estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO') THEN 'Contraentrega' ELSE 'Provincia' END as canal, estado, @@ -171,15 +186,15 @@ $stmtDetalleCanal = $db->query("SELECT GROUP BY canal, estado"); $detalleCanalRaw = $stmtDetalleCanal->fetchAll(PDO::FETCH_ASSOC); -// 13. Top Productos Detallado (Para la tabla) +// 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 != 'RETORNADO' + WHERE $date_condition AND p.estado NOT IN ('RETORNADO', 'GESTIONES') GROUP BY p.producto ORDER BY total_cantidad DESC - LIMIT 5 + LIMIT 10 "); $topProductosDetalle = $stmtTopDetalle->fetchAll(PDO::FETCH_ASSOC); @@ -191,10 +206,22 @@ $stmtTopDetalleCE = $db->query(" WHERE $date_condition AND p.estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') GROUP BY p.producto ORDER BY total_cantidad DESC - LIMIT 5 + 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 @@ -205,7 +232,7 @@ $stmtProdRendimientoCE = $db->query(" COUNT(*) as total FROM pedidos p WHERE $date_condition - AND (estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') OR (estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA')) + AND estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO') GROUP BY producto ORDER BY total DESC LIMIT 10 @@ -222,7 +249,7 @@ $stmtProdRendimientoProv = $db->query(" COUNT(*) as total FROM pedidos p WHERE $date_condition - AND NOT (estado IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA') OR (estado = 'RETORNADO' AND agencia = 'CONTRAENTREGA')) + AND estado NOT IN ('RUTA_CONTRAENTREGA', 'ENTREGA EXITOSA', 'RETORNADO') GROUP BY producto ORDER BY total DESC LIMIT 10 @@ -271,6 +298,9 @@ include 'layout_header.php'; 15 Días 30 Días Este Mes + Mes Anterior + 3 Meses + 6 Meses 1 Año @@ -565,53 +595,86 @@ include 'layout_header.php';
- -
-
+ +
+
-
Top 5 Productos más Vendidos
- Por Unidades +
Top 10 Productos Más Vendidos (Total)
+ Rendimiento Global
- +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + +
ProductoCant.Monto Total
35 ? substr($prod['producto'], 0, 35) . '...' : $prod['producto']); ?> + + + + + S/ +
+
+
+
+
+
+
+
+ +
+ +
+
+
+
Top 10 Contraentrega
+ Motorizado +
+
+
- +
- - + - 0) ? ($utilidad / $prod['monto_total']) * 100 : 0; - $rentClass = $rentabilidad > 30 ? 'text-success' : ($rentabilidad > 15 ? 'text-warning' : 'text-danger'); - ?> + - + - - - - - - -
Producto Cant.Monto TotalRent. %Monto
25 ? substr($prod['producto'], 0, 25) . '...' : $prod['producto']); ?> - + + S/ - % -
No hay datos en este periodo
@@ -619,53 +682,39 @@ include 'layout_header.php';
- +
-
+
-
Top 5 Contraentrega
- Solo Contraentrega +
Top 10 Provincia
+ Agencias
- +
- +
- - + - 0) ? ($utilidad / $prod['monto_total']) * 100 : 0; - $rentClass = $rentabilidad > 30 ? 'text-success' : ($rentabilidad > 15 ? 'text-warning' : 'text-danger'); - ?> + - + - - - - - - -
Producto Cant.Monto TotalRent. %Monto
25 ? substr($prod['producto'], 0, 25) . '...' : $prod['producto']); ?> - + + S/ - % -
No hay datos en este periodo
@@ -930,16 +979,47 @@ include 'layout_header.php'; new Chart(ctxProductos, { type: 'bar', data: { - labels: 20 ? substr($p['producto'], 0, 20) . '...' : $p['producto']; }, $topProductos)); ?>, + labels: 30 ? substr($p['producto'], 0, 30) . '...' : $p['producto']; }, $topProductos)); ?>, datasets: [{ - label: 'Unidades Vendidas', + label: 'Unidades', data: , - backgroundColor: '#0d6efd' + backgroundColor: '#0d6efd', + borderRadius: 5, + barPercentage: 0.7 }] }, options: { - indexAxis: 'y', - responsive: true + 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' } + } + } + } } }); @@ -948,16 +1028,86 @@ include 'layout_header.php'; new Chart(ctxProductosCE, { type: 'bar', data: { - labels: 20 ? substr($p['producto'], 0, 20) . '...' : $p['producto']; }, $topProductosCE)); ?>, + labels: 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto']; }, $topProductosCE)); ?>, datasets: [{ - label: 'Unidades Vendidas (CE)', + label: 'Unidades', data: , - backgroundColor: '#ffc107' + backgroundColor: '#ffc107', + borderRadius: 5, + barPercentage: 0.7 }] }, options: { - indexAxis: 'y', - responsive: true + 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: 15 ? substr($p['producto'], 0, 15) . '...' : $p['producto']; }, $topProductosProv)); ?>, + datasets: [{ + label: 'Unidades', + data: , + 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' } + } + } + } } }); diff --git a/db/migrations/015_add_cp_t_cp_f_to_flujo_caja.sql b/db/migrations/015_add_cp_t_cp_f_to_flujo_caja.sql new file mode 100644 index 00000000..1363f8f0 --- /dev/null +++ b/db/migrations/015_add_cp_t_cp_f_to_flujo_caja.sql @@ -0,0 +1,3 @@ +-- Add cp_t and cp_f columns to flujo_caja table +ALTER TABLE flujo_caja ADD COLUMN cp_t INT DEFAULT 0; +ALTER TABLE flujo_caja ADD COLUMN cp_f INT DEFAULT 0; diff --git a/flujo_de_caja.php b/flujo_de_caja.php index 34649dbc..8b13e742 100644 --- a/flujo_de_caja.php +++ b/flujo_de_caja.php @@ -13,7 +13,7 @@ $end_date = "$year-$month-$days_in_month"; $db_columns = [ 'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos', 'tu1', 'tu2', 'tu3', 'fl1', 'fl2', 'fl3', - 'rc_contraent', 'sc', 'c_pedidos' + 'rc_contraent', 'sc', 'cp_t', 'cp_f' ]; // Columns to display in the table body in order @@ -53,7 +53,8 @@ for ($day = 1; $day <= $days_in_month; $day++) { // Calculate totals $totals = array_fill_keys($display_columns, 0); $totals['sc'] = 0; -$totals['c_pedidos'] = 0; +$totals['cp_t'] = 0; +$totals['cp_f'] = 0; $totals['total_ingresos'] = 0; $totals['total_inversion'] = 0; $totals['recaudo_final'] = 0; @@ -69,7 +70,8 @@ foreach ($all_days_data as $date => &$day_data) { $totals[$col] += (float)($day_data[$col] ?? 0); } $totals['sc'] += (float)($day_data['sc'] ?? 0); - $totals['c_pedidos'] += (int)($day_data['c_pedidos'] ?? 0); + $totals['cp_t'] += (int)($day_data['cp_t'] ?? 0); + $totals['cp_f'] += (int)($day_data['cp_f'] ?? 0); $totals['total_ingresos'] += $ingresos_dia; $totals['total_inversion'] += $inversion_dia; $totals['recaudo_final'] += ($ingresos_dia - $inversion_dia); @@ -130,7 +132,7 @@ unset($day_data); Fecha - C.pedidos + C.pedidos Ingresos Inversion Publicitaria RC ENVIO @@ -141,6 +143,8 @@ unset($day_data); Sc + CP. T + CP. F BCP/YAPE B. NACION INTERBANK @@ -158,7 +162,8 @@ unset($day_data); $day_data): ?> - + + @@ -176,7 +181,8 @@ unset($day_data); TOTAL - + + @@ -218,7 +224,8 @@ document.addEventListener('DOMContentLoaded', function() { function updateGrandTotals() { const grandTotals = { - 'c_pedidos': 0, + 'cp_t': 0, + 'cp_f': 0, 'sc': 0, 'total-ingresos': 0, 'total-inversion': 0, @@ -229,7 +236,8 @@ document.addEventListener('DOMContentLoaded', function() { grandTotals[''] += parseFloat(row.querySelector(`[data-column=""]`).textContent.replace(/,/g, '')) || 0; - grandTotals['c_pedidos'] += parseInt(row.querySelector(`[data-column="c_pedidos"]`).textContent.replace(/,/g, '')) || 0; + grandTotals['cp_t'] += parseInt(row.querySelector(`[data-column="cp_t"]`).textContent.replace(/,/g, '')) || 0; + grandTotals['cp_f'] += parseInt(row.querySelector(`[data-column="cp_f"]`).textContent.replace(/,/g, '')) || 0; grandTotals['sc'] += parseFloat(row.querySelector(`[data-column="sc"]`).textContent.replace(/,/g, '')) || 0; grandTotals['total-ingresos'] += parseFloat(row.querySelector('.total-ingresos').textContent.replace(/,/g, '')) || 0; grandTotals['total-inversion'] += parseFloat(row.querySelector('.total-inversion').textContent.replace(/,/g, '')) || 0; @@ -239,7 +247,7 @@ document.addEventListener('DOMContentLoaded', function() { for (const key in grandTotals) { const th = table.querySelector(`tfoot [data-total-column="${key}"]`); if (th) { - if (key === 'c_pedidos') { + if (key === 'cp_t' || key === 'cp_f') { th.textContent = grandTotals[key]; } else { th.textContent = grandTotals[key].toFixed(2); @@ -269,7 +277,7 @@ document.addEventListener('DOMContentLoaded', function() { const column = cell.dataset.column; let value; - if (column === 'c_pedidos') { + if (column === 'cp_t' || column === 'cp_f') { value = parseInt(cell.textContent.replace(/,/g, '')); } else { value = parseFloat(cell.textContent.replace(/,/g, '')); @@ -279,7 +287,7 @@ document.addEventListener('DOMContentLoaded', function() { value = 0; } - if (column === 'c_pedidos') { + if (column === 'cp_t' || column === 'cp_f') { cell.textContent = value; } else { cell.textContent = value.toFixed(2); diff --git a/save_flujo_caja.php b/save_flujo_caja.php index 481b667b..ddd44cce 100644 --- a/save_flujo_caja.php +++ b/save_flujo_caja.php @@ -10,7 +10,7 @@ if ($data) { // Column name validation to prevent SQL injection $allowed_columns = [ - 'c_pedidos', 'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos', + 'c_pedidos', 'cp_t', 'cp_f', 'bcp_yape', 'b_nacion', 'interbank', 'bbva', 'otros_ingresos', 'tu1', 'tu2', 'tu3', 'fl1', 'fl2', 'fl3', 'rc_contraent', 'sc', 'total_inversion_publicitaria' ];