From 0858a9c9cd40eade05b0af8c695c1ff03cefd57f Mon Sep 17 00:00:00 2001 From: Administrador Ibiza Date: Sat, 3 Jan 2026 22:23:05 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20Implementar=20p=C3=A1gina=20dedicada=20?= =?UTF-8?q?de=20gr=C3=A1ficos=20para=20an=C3=A1lisis=20de=20pagos=20de=20a?= =?UTF-8?q?gua?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Crear nueva página /graficos con 4 tipos de gráficos interactivos - Agregar compatibilidad con tema oscuro en selectores - Implementar exportación a PDF profesional con encabezados - Agregar campo 'Monto Real del Recibo' a configuración mensual - Crear migración para nueva columna real_amount en monthly_bills - Mejorar filtros con botones interactivos en lugar de select múltiple - Agregar resumen ejecutivo con estadísticas detalladas - Optimizar espacio visual y responsividad de gráficos - Integrar Chart.js y jsPDF para funcionalidad avanzada - Corregir problemas de carga de datos y filtros dinámicos --- assets/css/theme.css | 37 + dashboard.php | 208 +++- ...ation_add_real_amount_to_monthly_bills.sql | 3 + models/MonthlyBill.php | 8 +- views/charts/index.php | 885 ++++++++++++++++++ views/configurar/index.php | 9 +- views/layout/base.php | 16 +- 7 files changed, 1149 insertions(+), 17 deletions(-) create mode 100644 database/migration_add_real_amount_to_monthly_bills.sql create mode 100644 views/charts/index.php diff --git a/assets/css/theme.css b/assets/css/theme.css index 0001f12..bfb57be 100755 --- a/assets/css/theme.css +++ b/assets/css/theme.css @@ -93,6 +93,26 @@ body { box-shadow: 0 0 0 0.25rem rgba(var(--navbar-bg-color), 0.25); } +/* Select múltiple específico */ +.form-select[multiple] { + background-color: var(--input-bg-color) !important; + border-color: var(--input-border-color) !important; + color: var(--input-text-color) !important; +} +.form-select[multiple] option { + background-color: var(--input-bg-color) !important; + color: var(--input-text-color) !important; + padding: 0.25rem 0.5rem !important; +} +.form-select[multiple] option:checked { + background-color: rgba(13, 110, 253, 0.2) !important; + color: var(--input-text-color) !important; +} +.dark-mode .form-select[multiple] option:checked { + background-color: rgba(105, 166, 255, 0.2) !important; + color: var(--input-text-color) !important; +} + /* --- TABLAS (Método Bootstrap) --- */ .table { --bs-table-color: var(--table-color); @@ -198,3 +218,20 @@ table { font-size: 14px; } .dark-mode #theme-toggle { color: var(--navbar-color); } + +/* Botones de checkboxes para meses */ +.dark-mode .btn-outline-primary { + border-color: var(--input-border-color) !important; + color: var(--input-text-color) !important; + background-color: var(--input-bg-color) !important; +} +.dark-mode .btn-outline-primary:hover { + background-color: var(--navbar-bg-color) !important; + border-color: var(--navbar-bg-color) !important; + color: var(--navbar-color) !important; +} +.dark-mode .btn-check:checked + .btn-outline-primary { + background-color: var(--navbar-bg-color) !important; + border-color: var(--navbar-bg-color) !important; + color: var(--navbar-color) !important; +} diff --git a/dashboard.php b/dashboard.php index 8a6c6bf..b3cd468 100755 --- a/dashboard.php +++ b/dashboard.php @@ -94,7 +94,55 @@ switch ($page) { case 'config_actions': header('Content-Type: application/json'); - Auth::requireAdmin(); + // Permitir acceso a capturistas para ver gráficos + if (!Auth::isCapturist()) { + echo json_encode(['success' => false, 'message' => 'Acceso denegado']); + exit; + } + + if (isset($_GET['action']) && $_GET['action'] == 'get_monthly_data') { + $year = $_GET['year'] ?? date('Y'); + $monthlyBills = MonthlyBill::getYear($year); + + $configured = []; + $realAmounts = []; + + $months = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', + 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']; + + foreach ($months as $month) { + $bill = $monthlyBills[$month] ?? null; + $configured[$month] = $bill['total_amount'] ?? 0; + $realAmounts[$month] = $bill['real_amount'] ?? 0; + } + + echo json_encode([ + 'success' => true, + 'configured' => $configured, + 'realAmounts' => $realAmounts + ]); + exit; + + } elseif (isset($_GET['action']) && $_GET['action'] == 'get_payments_data') { + $year = $_GET['year'] ?? date('Y'); + $matrix = Payment::getMatrix($year); + $months = $matrix['months']; + + $monthTotals = []; + foreach ($months as $month) { + $monthTotals[$month] = 0; + foreach ($matrix['payments'][$month] ?? [] as $houseId => $paymentData) { + $monthTotals[$month] += $paymentData['amount'] ?? 0; + } + } + + echo json_encode([ + 'success' => true, + 'payments' => $monthTotals, + 'months' => $months + ]); + exit; + } if (isset($_GET['action']) && $_GET['action'] == 'save_monthly_bill') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { @@ -563,11 +611,27 @@ switch ($page) { Auth::requireAdmin(); $view = 'users/index'; break; - case 'importar': - Auth::requireAdmin(); - $concepts = CollectionConcept::all(true); - $view = 'import/index'; - break; + case 'graficos': + $year = $_GET['year'] ?? date('Y'); + $matrix = Payment::getMatrix($year); + $monthlyBills = MonthlyBill::getYear($year); + $months = $matrix['months']; + $monthTotals = []; + + foreach ($months as $month) { + $monthTotals[$month] = 0; + foreach ($matrix['payments'][$month] ?? [] as $houseId => $paymentData) { + $monthTotals[$month] += $paymentData['amount'] ?? 0; + } + } + + $view = 'charts/index'; + break; + case 'importar': + Auth::requireAdmin(); + $concepts = CollectionConcept::all(true); + $view = 'import/index'; + break; case 'concept_view_actions': // Nuevo case para acciones AJAX de concept_view if (isset($_GET['action'])) { header('Content-Type: application/json'); @@ -634,9 +698,137 @@ switch ($page) { exit; } } - break; + break; - case 'reportes_actions': // Nuevo case para acciones de exportación de reportes + case 'charts_export': + // Exportación de gráficos a PDF usando TCPDF (igual que otras exportaciones) + date_default_timezone_set('America/Mexico_City'); + + // Obtener datos del POST + $input = json_decode(file_get_contents('php://input'), true); + $year = $input['year'] ?? date('Y'); + $chartImages = $input['charts'] ?? []; + $chartData = $input['data'] ?? []; + + require_once __DIR__ . '/vendor/autoload.php'; + require_once __DIR__ . '/vendor/tecnickcom/tcpdf/tcpdf.php'; + + $pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false); + + $pdf->SetCreator(PDF_CREATOR); + $pdf->SetAuthor('Ibiza Condominium'); + $pdf->SetTitle('Gráficos de Pagos de Agua - IBIZA ' . $year); + $pdf->SetSubject('Análisis Gráfico de Pagos'); + + $pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, 'Condominio IBIZA-Cto Sierra Morena 152 - Gráficos de Pagos de Agua ' . $year, 'Generado el ' . date('d/m/Y H:i')); + + $pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN)); + $pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA)); + + $pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED); + $pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT); + $pdf->SetHeaderMargin(PDF_MARGIN_HEADER); + $pdf->SetFooterMargin(PDF_MARGIN_FOOTER); + $pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM); + $pdf->setImageScale(PDF_IMAGE_SCALE_RATIO); + + if (@file_exists(dirname(__FILE__).'/lang/eng.php')) { + require_once(dirname(__FILE__).'/lang/eng.php'); + $pdf->setLanguageArray($l); + } + + $pdf->SetFont('helvetica', '', 12); + $pdf->AddPage(); + + // Título principal + $pdf->SetFont('helvetica', 'B', 18); + $pdf->Cell(0, 15, 'Gráficos de Pagos de Agua - IBIZA', 0, 1, 'C'); + $pdf->SetFont('helvetica', '', 10); + $pdf->Cell(0, 8, 'Año: ' . $year . ' - Generado: ' . date('d/m/Y H:i'), 0, 1, 'C'); + $pdf->Ln(10); + + // Función para agregar gráfico + function addChartToPDF($pdf, $imageData, $title, $description = '') { + if ($imageData) { + $pdf->SetFont('helvetica', 'B', 14); + $pdf->Cell(0, 10, $title, 0, 1, 'L'); + + if ($description) { + $pdf->SetFont('helvetica', 'I', 10); + $pdf->Cell(0, 6, $description, 0, 1, 'L'); + } + + // Agregar imagen del gráfico + $pdf->Image('@' . base64_decode(preg_replace('#^data:image/[^;]+;base64,#', '', $imageData)), 15, $pdf->GetY(), 180, 80, 'PNG'); + $pdf->Ln(90); + } + } + + // Página 1: Gráficos principales + $pdf->SetFont('helvetica', 'B', 16); + $pdf->Cell(0, 12, 'Análisis Comparativo de Pagos', 0, 1, 'L'); + $pdf->Ln(5); + + if (isset($chartImages['comparisonChart'])) { + addChartToPDF($pdf, $chartImages['comparisonChart'], + '1. Comparación: Pagos Reales vs Monto Configurado', + 'Este gráfico compara los pagos efectivamente recibidos con los montos que fueron configurados para cada mes.'); + } + + if (isset($chartImages['trendsChart'])) { + addChartToPDF($pdf, $chartImages['trendsChart'], + '2. Tendencias de Pagos a lo Largo del Año', + 'Visualiza la evolución mensual de los ingresos por concepto de agua.'); + } + + if (isset($chartImages['realAmountChart'])) { + addChartToPDF($pdf, $chartImages['realAmountChart'], + '3. Comparación: Monto Real del Recibo vs Pagos Efectivos', + 'Compara el costo real del servicio con lo que efectivamente se cobra y paga.'); + } + + // Nueva página para resumen ejecutivo + $pdf->AddPage(); + $pdf->SetFont('helvetica', 'B', 16); + $pdf->Cell(0, 12, 'Resumen Ejecutivo', 0, 1, 'L'); + $pdf->Ln(5); + + if (isset($chartImages['summaryChart'])) { + addChartToPDF($pdf, $chartImages['summaryChart'], + 'Distribución General de Cobranza', + 'Vista panorámica del estado de cobranza con métricas clave.'); + } + + // Estadísticas textuales + $pdf->SetFont('helvetica', 'B', 12); + $pdf->Cell(0, 10, 'Métricas de Rendimiento', 0, 1, 'L'); + $pdf->Ln(5); + + $pdf->SetFont('helvetica', '', 11); + $totalPayments = array_sum($chartData['payments'] ?? []); + $totalConfigured = array_sum($chartData['configured'] ?? []); + $totalReal = array_sum($chartData['realAmounts'] ?? []); + + $pdf->Cell(80, 8, 'Total Pagos Recibidos:', 0, 0, 'L'); + $pdf->Cell(40, 8, '$ ' . number_format($totalPayments, 2), 0, 1, 'R'); + + $pdf->Cell(80, 8, 'Monto Total Configurado:', 0, 0, 'L'); + $pdf->Cell(40, 8, '$ ' . number_format($totalConfigured, 2), 0, 1, 'R'); + + $pdf->Cell(80, 8, 'Monto Real Total:', 0, 0, 'L'); + $pdf->Cell(40, 8, '$ ' . number_format($totalReal, 2), 0, 1, 'R'); + + if ($totalConfigured > 0) { + $efficiency = round(($totalPayments / $totalConfigured) * 100, 1); + $pdf->Cell(80, 8, 'Eficiencia de Cobranza:', 0, 0, 'L'); + $pdf->Cell(40, 8, $efficiency . '%', 0, 1, 'R'); + } + + // Salida del PDF + $pdf->Output('Graficos_Pagos_Agua_IBIZA_' . $year . '_' . date('Y-m-d') . '.pdf', 'D'); + exit; + + case 'reportes_actions': // Nuevo case para acciones de exportación de reportes date_default_timezone_set('America/Mexico_City'); // Asegurar zona horaria if (isset($_GET['action'])) { diff --git a/database/migration_add_real_amount_to_monthly_bills.sql b/database/migration_add_real_amount_to_monthly_bills.sql new file mode 100644 index 0000000..c758f51 --- /dev/null +++ b/database/migration_add_real_amount_to_monthly_bills.sql @@ -0,0 +1,3 @@ +-- Migration to add real_amount to monthly_bills table +ALTER TABLE `monthly_bills` +ADD COLUMN `real_amount` DECIMAL(10, 2) NULL DEFAULT NULL AFTER `amount_per_house`; \ No newline at end of file diff --git a/models/MonthlyBill.php b/models/MonthlyBill.php index e5d5f96..f8785fa 100755 --- a/models/MonthlyBill.php +++ b/models/MonthlyBill.php @@ -36,10 +36,11 @@ class MonthlyBill { if ($existing) { $db->execute( - "UPDATE monthly_bills SET total_amount = ?, amount_per_house = ?, due_date = ? WHERE id = ?", + "UPDATE monthly_bills SET total_amount = ?, amount_per_house = ?, real_amount = ?, due_date = ? WHERE id = ?", [ $data['total_amount'], $amountPerHouse, + $data['real_amount'] ?? null, $data['due_date'] ?? null, $existing['id'] ] @@ -47,13 +48,14 @@ class MonthlyBill { return $existing['id']; } else { $db->execute( - "INSERT INTO monthly_bills (year, month, total_amount, amount_per_house, due_date) - VALUES (?, ?, ?, ?, ?)", + "INSERT INTO monthly_bills (year, month, total_amount, amount_per_house, real_amount, due_date) + VALUES (?, ?, ?, ?, ?, ?)", [ $data['year'], $data['month'], $data['total_amount'], $amountPerHouse, + $data['real_amount'] ?? null, $data['due_date'] ?? null ] ); diff --git a/views/charts/index.php b/views/charts/index.php new file mode 100644 index 0000000..33b7e68 --- /dev/null +++ b/views/charts/index.php @@ -0,0 +1,885 @@ + + +
+
+

Gráficos de Pagos de Agua

+

Análisis visual de pagos, tendencias y comparación con montos configurados

+
+
+ +
+
+
+
+
Filtros
+
+
+
+
+ + +
+
+ +
+ + + + + + +
+
Selecciona los meses a incluir en los gráficos
+
+
+ + +
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+ +
+
+
Comparación: Pagos Reales vs Monto Configurado por Mes
+
+ +
+ Interpretación: + Este gráfico compara los pagos efectivamente recibidos con los montos que fueron configurados para cada mes. + Las barras azules representan pagos reales, las rojas el monto configurado esperado. +
+ +
+ + + + + +
+
+
Comparación: Monto Real del Recibo vs Pagos Efectivos
+
+ +
+ Monto real vs efectivo: + Compara el costo real del servicio (según el recibo) con lo que efectivamente se cobra y paga. +
+ +
+ + +
+
+
Resumen Ejecutivo - Estado General de Cobranza
+
+
+
+ +
+
+
+ +
+ Cargando estadísticas... +
+
+
+
+
+ Insights: + Vista panorámica del estado de cobranza con métricas clave para toma de decisiones. +
+ +
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/views/configurar/index.php b/views/configurar/index.php index 80f0fa4..9eb7c3a 100755 --- a/views/configurar/index.php +++ b/views/configurar/index.php @@ -15,7 +15,7 @@
-
@@ -37,6 +37,11 @@
+
+ Monto Real del Recibo + +
Fecha de Vencimiento ; @@ -85,6 +91,7 @@ function saveMonth(month) { month: month, total_amount: parseFloat(total), amount_per_house: parseFloat(perHouse), + real_amount: realAmount ? parseFloat(realAmount) : null, due_date: dueDate || null }) }) diff --git a/views/layout/base.php b/views/layout/base.php index 183de5a..8a5450e 100755 --- a/views/layout/base.php +++ b/views/layout/base.php @@ -53,11 +53,16 @@ Finanzas - + +