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';
-
-
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+ | Producto |
+ Cant. |
+ Monto Total |
+
+
+
+
+
+ | 35 ? substr($prod['producto'], 0, 35) . '...' : $prod['producto']); ?> |
+
+
+
+
+ |
+
+ S/
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
| Producto |
Cant. |
- Monto Total |
- Rent. % |
+ Monto |
- 0) ? ($utilidad / $prod['monto_total']) * 100 : 0;
- $rentClass = $rentabilidad > 30 ? 'text-success' : ($rentabilidad > 15 ? 'text-warning' : 'text-danger');
- ?>
+
- |
+ 25 ? substr($prod['producto'], 0, 25) . '...' : $prod['producto']); ?> |
-
+
|
-
+ |
S/
|
-
- %
- |
-
-
- | No hay datos en este periodo |
-
-
@@ -619,53 +682,39 @@ include 'layout_header.php';
-
+
-
+
-
+
-
+
| Producto |
Cant. |
- Monto Total |
- Rent. % |
+ Monto |
- 0) ? ($utilidad / $prod['monto_total']) * 100 : 0;
- $rentClass = $rentabilidad > 30 ? 'text-success' : ($rentabilidad > 15 ? 'text-warning' : 'text-danger');
- ?>
+
- |
+ 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'
];