Finalización del módulo Luz Cámara: Corrección de errores JS, exportación profesional a PDF y reportes de deudores
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Auth {
|
class Auth
|
||||||
public static function check() {
|
{
|
||||||
|
public static function check()
|
||||||
|
{
|
||||||
if (!isset($_SESSION['user_id'])) {
|
if (!isset($_SESSION['user_id'])) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -17,64 +19,85 @@ class Auth {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function user() {
|
public static function user()
|
||||||
|
{
|
||||||
if (!self::check()) {
|
if (!self::check()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return $_SESSION;
|
return $_SESSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function id() {
|
public static function id()
|
||||||
|
{
|
||||||
return $_SESSION['user_id'] ?? null;
|
return $_SESSION['user_id'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function role() {
|
public static function role()
|
||||||
|
{
|
||||||
return $_SESSION['role'] ?? null;
|
return $_SESSION['role'] ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isAdmin() {
|
public static function isAdmin()
|
||||||
|
{
|
||||||
return self::role() === 'ADMIN';
|
return self::role() === 'ADMIN';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isCapturist() {
|
public static function isCapturist()
|
||||||
|
{
|
||||||
return self::role() === 'CAPTURIST' || self::isAdmin();
|
return self::role() === 'CAPTURIST' || self::isAdmin();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isViewer() {
|
public static function isViewer()
|
||||||
|
{
|
||||||
return self::role() === 'VIEWER';
|
return self::role() === 'VIEWER';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function isLector() {
|
public static function isLector()
|
||||||
|
{
|
||||||
return self::role() === 'LECTOR';
|
return self::role() === 'LECTOR';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getAccessibleHouseIds() {
|
public static function getAccessibleHouseIds()
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
|
|
||||||
if (self::isAdmin()) {
|
if (self::isAdmin()) {
|
||||||
$result = $db->fetchAll("SELECT id FROM houses");
|
$result = $db->fetchAll("SELECT id FROM houses");
|
||||||
return array_column($result, 'id');
|
return array_column($result, 'id');
|
||||||
} elseif (self::isLector()) {
|
}
|
||||||
|
elseif (self::isLector()) {
|
||||||
$userId = self::id();
|
$userId = self::id();
|
||||||
$result = $db->fetchAll(
|
$result = $db->fetchAll(
|
||||||
"SELECT house_id FROM user_house_permissions WHERE user_id = ?",
|
"SELECT house_id FROM user_house_permissions WHERE user_id = ?",
|
||||||
[$userId]
|
[$userId]
|
||||||
);
|
);
|
||||||
return array_column($result, 'house_id');
|
return array_column($result, 'house_id');
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
$result = $db->fetchAll("SELECT id FROM houses");
|
$result = $db->fetchAll("SELECT id FROM houses");
|
||||||
return array_column($result, 'id');
|
return array_column($result, 'id');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function requireAuth() {
|
public static function canViewHouse($houseId)
|
||||||
|
{
|
||||||
|
if (self::isAdmin()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
$accessibleIds = self::getAccessibleHouseIds();
|
||||||
|
return in_array($houseId, $accessibleIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function requireAuth()
|
||||||
|
{
|
||||||
if (!self::check()) {
|
if (!self::check()) {
|
||||||
header('Location: /login.php');
|
header('Location: /login.php');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function requireAdmin() {
|
public static function requireAdmin()
|
||||||
|
{
|
||||||
self::requireAuth();
|
self::requireAuth();
|
||||||
if (!self::isAdmin()) {
|
if (!self::isAdmin()) {
|
||||||
header('Location: /dashboard.php');
|
header('Location: /dashboard.php');
|
||||||
@@ -82,7 +105,8 @@ class Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function requireCapturist() {
|
public static function requireCapturist()
|
||||||
|
{
|
||||||
self::requireAuth();
|
self::requireAuth();
|
||||||
if (!self::isCapturist()) {
|
if (!self::isCapturist()) {
|
||||||
header('Location: /dashboard.php');
|
header('Location: /dashboard.php');
|
||||||
@@ -90,7 +114,8 @@ class Auth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function login($user) {
|
public static function login($user)
|
||||||
|
{
|
||||||
session_regenerate_id(true);
|
session_regenerate_id(true);
|
||||||
$_SESSION['user_id'] = $user['id'];
|
$_SESSION['user_id'] = $user['id'];
|
||||||
$_SESSION['username'] = $user['username'];
|
$_SESSION['username'] = $user['username'];
|
||||||
@@ -108,14 +133,16 @@ class Auth {
|
|||||||
self::logActivity('login', "Usuario {$user['username']} inició sesión");
|
self::logActivity('login', "Usuario {$user['username']} inició sesión");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function logout() {
|
public static function logout()
|
||||||
|
{
|
||||||
self::logActivity('logout', "Usuario {$_SESSION['username']} cerró sesión");
|
self::logActivity('logout', "Usuario {$_SESSION['username']} cerró sesión");
|
||||||
session_destroy();
|
session_destroy();
|
||||||
header('Location: /login.php');
|
header('Location: /login.php');
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function logActivity($action, $details = '') {
|
public static function logActivity($action, $details = '')
|
||||||
|
{
|
||||||
if (!self::check()) {
|
if (!self::check()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
296
dashboard.php
296
dashboard.php
@@ -310,8 +310,7 @@ switch ($page) {
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
echo json_encode(['success' => false, 'message' => 'Acción no válida']);
|
echo json_encode(['success' => false, 'message' => 'Acción no válida']);
|
||||||
exit;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'pagos':
|
case 'pagos':
|
||||||
$matrix = Payment::getMatrix($year);
|
$matrix = Payment::getMatrix($year);
|
||||||
@@ -638,6 +637,15 @@ switch ($page) {
|
|||||||
|
|
||||||
$conceptDebtors = Report::getConceptDebtorsFiltered($filteredHouses, $filteredConcepts);
|
$conceptDebtors = Report::getConceptDebtorsFiltered($filteredHouses, $filteredConcepts);
|
||||||
}
|
}
|
||||||
|
elseif ($reportType == 'electricity-debtors') {
|
||||||
|
$filters = [
|
||||||
|
'year' => $_GET['filter_year'] ?? null,
|
||||||
|
'periods' => isset($_GET['filter_periods']) ? explode(',', $_GET['filter_periods']) : null,
|
||||||
|
'house_id' => $_GET['filter_house'] ?? null,
|
||||||
|
'accessible_house_ids' => $accessibleHouseIds
|
||||||
|
];
|
||||||
|
$electricityDebtors = Report::getElectricityDebtors($filters);
|
||||||
|
}
|
||||||
|
|
||||||
$view = 'reports/index';
|
$view = 'reports/index';
|
||||||
break;
|
break;
|
||||||
@@ -672,135 +680,7 @@ switch ($page) {
|
|||||||
$concepts = CollectionConcept::all(true);
|
$concepts = CollectionConcept::all(true);
|
||||||
$view = 'import/index';
|
$view = 'import/index';
|
||||||
break;
|
break;
|
||||||
case 'concept_view_actions': // Nuevo case para acciones AJAX de concept_view
|
|
||||||
if (isset($_GET['action'])) {
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
$userId = Auth::id(); // Obtener el ID del usuario actual
|
|
||||||
|
|
||||||
switch ($_GET['action']) {
|
|
||||||
case 'initialize_concept_payments':
|
|
||||||
$conceptId = $_GET['concept_id'] ?? 0;
|
|
||||||
if (!$conceptId) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'ID de concepto no proporcionado']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if (!Auth::isCapturist()) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Permiso denegado']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Se requiere el modelo House para CollectionPayment::initializePayments
|
|
||||||
require_once __DIR__ . '/models/House.php';
|
|
||||||
$result = CollectionPayment::initializePayments($conceptId, $userId);
|
|
||||||
if ($result) {
|
|
||||||
Auth::logActivity('initialize_concept_payments', 'Pagos de concepto inicializados: ID ' . $conceptId);
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Pagos inicializados exitosamente']);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Error al inicializar pagos']);
|
|
||||||
}
|
|
||||||
exit;
|
|
||||||
|
|
||||||
case 'save_concept_payment':
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
if ($input) {
|
|
||||||
$conceptId = $input['concept_id'] ?? 0;
|
|
||||||
$houseId = $input['house_id'] ?? 0;
|
|
||||||
$amount = $input['amount'] ?? 0;
|
|
||||||
$paymentDate = $input['payment_date'] ?? null;
|
|
||||||
|
|
||||||
if (!$conceptId || !$houseId || !is_numeric($amount)) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Datos de pago incompletos o inválidos']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if (!Auth::isCapturist()) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Permiso denegado']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = CollectionPayment::update($conceptId, $houseId, $amount, $userId, 'Pago actualizado', $paymentDate);
|
|
||||||
if ($result) {
|
|
||||||
Auth::logActivity('save_concept_payment', 'Pago de concepto guardado: Concepto ' . $conceptId . ', Casa ' . $houseId . ', Monto ' . $amount);
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Pago guardado exitosamente']);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Error al guardar pago']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Datos inválidos']);
|
|
||||||
}
|
|
||||||
exit;
|
|
||||||
|
|
||||||
case 'save_all_concept_payments':
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
$input = json_decode(file_get_contents('php://input'), true);
|
|
||||||
if ($input) {
|
|
||||||
$conceptId = $input['concept_id'] ?? 0;
|
|
||||||
$payments = $input['payments'] ?? [];
|
|
||||||
|
|
||||||
if (!$conceptId || !is_array($payments)) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Datos de pago incompletos o inválidos']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
if (!Auth::isCapturist()) {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Permiso denegado']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
|
|
||||||
$savedCount = 0;
|
|
||||||
$errorCount = 0;
|
|
||||||
|
|
||||||
foreach ($payments as $payment) {
|
|
||||||
$houseId = $payment['house_id'] ?? 0;
|
|
||||||
$amount = $payment['amount'] ?? 0;
|
|
||||||
$paymentDate = $payment['payment_date'] ?? null;
|
|
||||||
|
|
||||||
if (!$houseId) {
|
|
||||||
$errorCount++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
$result = CollectionPayment::update($conceptId, $houseId, $amount, $userId, 'Pago actualizado', $paymentDate);
|
|
||||||
if ($result) {
|
|
||||||
$savedCount++;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$errorCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($savedCount > 0) {
|
|
||||||
Auth::logActivity('save_all_concept_payments', 'Múltiples pagos de concepto guardados: Concepto ' . $conceptId . ', ' . $savedCount . ' pagos guardados');
|
|
||||||
if ($errorCount > 0) {
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Se guardaron ' . $savedCount . ' pagos. Hubo ' . $errorCount . ' errores.']);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
echo json_encode(['success' => true, 'message' => 'Se guardaron ' . $savedCount . ' pagos exitosamente']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'No se pudo guardar ningún pago']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Datos inválidos']);
|
|
||||||
}
|
|
||||||
exit;
|
|
||||||
|
|
||||||
default:
|
|
||||||
echo json_encode(['success' => false, 'message' => 'Acción no válida para la vista de concepto']);
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'charts_export':
|
case 'charts_export':
|
||||||
// Exportación de gráficos a PDF usando TCPDF (igual que otras exportaciones)
|
// Exportación de gráficos a PDF usando TCPDF (igual que otras exportaciones)
|
||||||
@@ -946,8 +826,8 @@ switch ($page) {
|
|||||||
'balance' => 'Balance_General',
|
'balance' => 'Balance_General',
|
||||||
'expenses' => 'Gastos_por_Categoria',
|
'expenses' => 'Gastos_por_Categoria',
|
||||||
'water-debtors' => 'Deudores_de_Agua',
|
'water-debtors' => 'Deudores_de_Agua',
|
||||||
'concept-debtors' => 'Deudores_de_Conceptos',
|
'concepts' => 'Conceptos_Especiales',
|
||||||
'concepts' => 'Conceptos_Especiales'
|
'electricity-debtors' => 'Deudores_de_Luz'
|
||||||
];
|
];
|
||||||
$reportName = $reportNames[$reportType] ?? ucfirst($reportType);
|
$reportName = $reportNames[$reportType] ?? ucfirst($reportType);
|
||||||
|
|
||||||
@@ -996,6 +876,20 @@ switch ($page) {
|
|||||||
$waterDebtors = Report::getWaterDebtors($filters);
|
$waterDebtors = Report::getWaterDebtors($filters);
|
||||||
include __DIR__ . '/views/reports/pdf_water_debtors.php';
|
include __DIR__ . '/views/reports/pdf_water_debtors.php';
|
||||||
break;
|
break;
|
||||||
|
case 'electricity-debtors':
|
||||||
|
$filters = [
|
||||||
|
'year' => $_GET['filter_year'] ?? null,
|
||||||
|
'periods' => $_GET['filter_periods'] ?? null,
|
||||||
|
'house_id' => $_GET['filter_house'] ?? null,
|
||||||
|
'accessible_house_ids' => $accessibleHouseIds
|
||||||
|
];
|
||||||
|
if ($filters['periods'] && !is_array($filters['periods'])) {
|
||||||
|
$filters['periods'] = explode(',', $filters['periods']);
|
||||||
|
}
|
||||||
|
require_once __DIR__ . '/models/Report.php';
|
||||||
|
$electricityDebtors = Report::getElectricityDebtors($filters);
|
||||||
|
include __DIR__ . '/views/reports/pdf_electricity_debtors.php';
|
||||||
|
break;
|
||||||
case 'concept-debtors':
|
case 'concept-debtors':
|
||||||
// Requerimos el modelo Report
|
// Requerimos el modelo Report
|
||||||
require_once __DIR__ . '/models/Report.php';
|
require_once __DIR__ . '/models/Report.php';
|
||||||
@@ -1293,7 +1187,8 @@ switch ($page) {
|
|||||||
$targetUserId = $_GET['user_id'] ?? 0;
|
$targetUserId = $_GET['user_id'] ?? 0;
|
||||||
$userHouses = UserPermission::getUserHouseIds($targetUserId);
|
$userHouses = UserPermission::getUserHouseIds($targetUserId);
|
||||||
echo json_encode(['success' => true, 'houses' => array_map(function ($id) {
|
echo json_encode(['success' => true, 'houses' => array_map(function ($id) {
|
||||||
return ['id' => $id]; }, $userHouses)]);
|
return ['id' => $id];
|
||||||
|
}, $userHouses)]);
|
||||||
exit;
|
exit;
|
||||||
|
|
||||||
case 'create':
|
case 'create':
|
||||||
@@ -1538,6 +1433,141 @@ switch ($page) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'luz_camara':
|
||||||
|
$year = $_GET['year'] ?? date('Y');
|
||||||
|
require_once __DIR__ . '/models/ElectricityPayment.php';
|
||||||
|
require_once __DIR__ . '/models/ElectricityBill.php';
|
||||||
|
|
||||||
|
$matrix = ElectricityPayment::getMatrix($year);
|
||||||
|
$electricityBills = ElectricityBill::getYear($year);
|
||||||
|
|
||||||
|
if (isset($_GET['action']) && $_GET['action'] == 'export_electricity_pdf') {
|
||||||
|
date_default_timezone_set('America/Mexico_City');
|
||||||
|
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('Reporte de Pagos Luz Cámara ' . $year);
|
||||||
|
$pdf->SetSubject('Pagos de Luz Cámara');
|
||||||
|
|
||||||
|
$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, 'Condominio IBIZA - Reporte Luz Cámara ' . $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', '', 9);
|
||||||
|
$pdf->AddPage();
|
||||||
|
|
||||||
|
// Filtrar periodos seleccionados si existen
|
||||||
|
$periods = ElectricityBill::getPeriods();
|
||||||
|
$selectedPeriods = $_GET['periods'] ?? [];
|
||||||
|
if (!empty($selectedPeriods)) {
|
||||||
|
$filteredPeriods = [];
|
||||||
|
foreach ($periods as $p) {
|
||||||
|
if (in_array($p, $selectedPeriods)) {
|
||||||
|
$filteredPeriods[] = $p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$periods = $filteredPeriods;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Datos ya cargados arriba ($matrix, $electricityBills)
|
||||||
|
$houses = House::all();
|
||||||
|
|
||||||
|
ob_start();
|
||||||
|
include __DIR__ . '/views/electricity/pdf_template.php';
|
||||||
|
$html = ob_get_clean();
|
||||||
|
|
||||||
|
$pdf->writeHTML($html, true, false, true, false, '');
|
||||||
|
$pdf->Output('Pagos_Luz_Camara_' . $year . '.pdf', 'D');
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$view = 'electricity/index';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'luz_camara_actions':
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/models/ElectricityPayment.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (!$input || !isset($input['changes'])) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Auth::isCapturist()) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Permiso denegado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = Auth::id();
|
||||||
|
$result = ElectricityPayment::updateBatch($input['changes'], $userId);
|
||||||
|
|
||||||
|
if ($result['success']) {
|
||||||
|
$details = "Actualización masiva de pagos luz: " . $result['count'] . " cambios";
|
||||||
|
Auth::logActivity('save_electricity_payment_batch', $details);
|
||||||
|
echo json_encode($result);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echo json_encode($result);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
|
||||||
|
case 'luz_camara_config':
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
require_once __DIR__ . '/models/ElectricityBill.php';
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
if (!$input) {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Auth::isCapturist()) { // O isAdmin, dependiendo de la política
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Permiso denegado']);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$userId = Auth::id();
|
||||||
|
$id = ElectricityBill::save($input, $userId);
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
Auth::logActivity('save_electricity_config', "Configuración luz actualizada: " . $input['period'] . " " . $input['year']);
|
||||||
|
echo json_encode(['success' => true, 'id' => $id]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
echo json_encode(['success' => false, 'message' => 'Error al guardar configuración']);
|
||||||
|
}
|
||||||
|
exit;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$stats = Report::getDashboardStats($year);
|
$stats = Report::getDashboardStats($year);
|
||||||
$recentActivity = ActivityLog::all(15);
|
$recentActivity = ActivityLog::all(15);
|
||||||
|
|||||||
1215
database/schema.sql
1215
database/schema.sql
File diff suppressed because it is too large
Load Diff
0
migrations/add_payment_indexes.sql
Normal file → Executable file
0
migrations/add_payment_indexes.sql
Normal file → Executable file
48
migrations/create_electricity_tables.sql
Executable file
48
migrations/create_electricity_tables.sql
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
-- =====================================================
|
||||||
|
-- Módulo de Pagos de Luz - Cámara
|
||||||
|
-- Fecha: 2026-02-14
|
||||||
|
-- Propósito: Crear tablas para gestión de pagos bimestrales de luz
|
||||||
|
-- =====================================================
|
||||||
|
|
||||||
|
-- Tabla de configuración bimestral de luz
|
||||||
|
CREATE TABLE IF NOT EXISTS electricity_bills (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
year INT NOT NULL,
|
||||||
|
period VARCHAR(20) NOT NULL COMMENT 'Ene-Feb, Mar-Abr, May-Jun, Jul-Ago, Sep-Oct, Nov-Dic',
|
||||||
|
total_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT 'Monto total del recibo CFE',
|
||||||
|
amount_per_house DECIMAL(10,2) DEFAULT 0.00 COMMENT 'Monto sugerido por casa',
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
UNIQUE KEY unique_period (year, period),
|
||||||
|
INDEX idx_year (year)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Configuración bimestral de pagos de luz';
|
||||||
|
|
||||||
|
-- Tabla de pagos de luz por casa
|
||||||
|
CREATE TABLE IF NOT EXISTS electricity_payments (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
house_id INT NOT NULL,
|
||||||
|
year INT NOT NULL,
|
||||||
|
period VARCHAR(20) NOT NULL COMMENT 'Ene-Feb, Mar-Abr, May-Jun, Jul-Ago, Sep-Oct, Nov-Dic',
|
||||||
|
amount DECIMAL(10,2) DEFAULT 0.00,
|
||||||
|
payment_date DATETIME,
|
||||||
|
notes TEXT,
|
||||||
|
created_by INT,
|
||||||
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (house_id) REFERENCES houses(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY (created_by) REFERENCES users(id) ON DELETE SET NULL,
|
||||||
|
UNIQUE KEY unique_payment (house_id, year, period)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Pagos de luz por casa';
|
||||||
|
|
||||||
|
-- Índices para optimización de queries
|
||||||
|
CREATE INDEX idx_electricity_payments_year_period ON electricity_payments(year, period);
|
||||||
|
CREATE INDEX idx_electricity_payments_house_year ON electricity_payments(house_id, year);
|
||||||
|
|
||||||
|
-- Verificar tablas creadas
|
||||||
|
SELECT 'Tablas creadas exitosamente:' as 'Status';
|
||||||
|
SHOW TABLES LIKE 'electricity%';
|
||||||
|
|
||||||
|
-- Verificar índices
|
||||||
|
SELECT 'Índices de electricity_payments:' as 'Verificación';
|
||||||
|
SHOW INDEX FROM electricity_payments WHERE Key_name LIKE 'idx_%';
|
||||||
93
models/ElectricityBill.php
Executable file
93
models/ElectricityBill.php
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ElectricityBill
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Obtener configuraciones de todos los periodos de un año
|
||||||
|
*/
|
||||||
|
public static function getYear($year)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$bills = $db->fetchAll(
|
||||||
|
"SELECT * FROM electricity_bills WHERE year = ? ORDER BY FIELD(period, 'Ene-Feb', 'Mar-Abr', 'May-Jun', 'Jul-Ago', 'Sep-Oct', 'Nov-Dic')",
|
||||||
|
[$year]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Organizar por periodo
|
||||||
|
$result = [];
|
||||||
|
foreach ($bills as $bill) {
|
||||||
|
$result[$bill['period']] = $bill;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Guardar o actualizar configuración de un periodo
|
||||||
|
*/
|
||||||
|
public static function save($data, $userId)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$id = $data['id'] ?? null;
|
||||||
|
$year = $data['year'];
|
||||||
|
$period = $data['period'];
|
||||||
|
$totalAmount = $data['total_amount'] ?? 0;
|
||||||
|
$amountPerHouse = $data['amount_per_house'] ?? 0;
|
||||||
|
$notes = $data['notes'] ?? '';
|
||||||
|
|
||||||
|
if ($id) {
|
||||||
|
// Actualizar existente
|
||||||
|
$db->execute(
|
||||||
|
"UPDATE electricity_bills
|
||||||
|
SET total_amount = ?, amount_per_house = ?, notes = ?, updated_at = NOW()
|
||||||
|
WHERE id = ?",
|
||||||
|
[$totalAmount, $amountPerHouse, $notes, $id]
|
||||||
|
);
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Insertar nuevo o actualizar si existe
|
||||||
|
$db->execute(
|
||||||
|
"INSERT INTO electricity_bills (year, period, total_amount, amount_per_house, notes)
|
||||||
|
VALUES (?, ?, ?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
total_amount = VALUES(total_amount),
|
||||||
|
amount_per_house = VALUES(amount_per_house),
|
||||||
|
notes = VALUES(notes),
|
||||||
|
updated_at = NOW()",
|
||||||
|
[$year, $period, $totalAmount, $amountPerHouse, $notes]
|
||||||
|
);
|
||||||
|
return $db->lastInsertId();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener periodos bimestrales
|
||||||
|
*/
|
||||||
|
public static function getPeriods()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Ene-Feb',
|
||||||
|
'Mar-Abr',
|
||||||
|
'May-Jun',
|
||||||
|
'Jul-Ago',
|
||||||
|
'Sep-Oct',
|
||||||
|
'Nov-Dic'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener configuración de un periodo específico
|
||||||
|
*/
|
||||||
|
public static function getByYearPeriod($year, $period)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
return $db->fetchOne(
|
||||||
|
"SELECT * FROM electricity_bills WHERE year = ? AND period = ?",
|
||||||
|
[$year, $period]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
191
models/ElectricityPayment.php
Executable file
191
models/ElectricityPayment.php
Executable file
@@ -0,0 +1,191 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
class ElectricityPayment
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Obtener matriz completa de pagos para un año
|
||||||
|
* OPTIMIZADO: Una sola query para todos los periodos
|
||||||
|
*/
|
||||||
|
public static function getMatrix($year)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$houses = $db->fetchAll(
|
||||||
|
"SELECT h.id, h.number, h.status, h.consumption_only, h.owner_name
|
||||||
|
FROM houses h
|
||||||
|
ORDER BY CAST(h.number AS UNSIGNED)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$periods = ElectricityBill::getPeriods();
|
||||||
|
|
||||||
|
// OPTIMIZADO: Una sola query en lugar de 6 queries separadas
|
||||||
|
$allPayments = $db->fetchAll(
|
||||||
|
"SELECT house_id, period, amount, payment_date
|
||||||
|
FROM electricity_payments
|
||||||
|
WHERE year = ?
|
||||||
|
ORDER BY FIELD(period, 'Ene-Feb', 'Mar-Abr', 'May-Jun', 'Jul-Ago', 'Sep-Oct', 'Nov-Dic')",
|
||||||
|
[$year]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Organizar pagos por periodo
|
||||||
|
$payments = [];
|
||||||
|
foreach ($periods as $period) {
|
||||||
|
$payments[$period] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($allPayments as $p) {
|
||||||
|
$payments[$p['period']][$p['house_id']] = $p;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['houses' => $houses, 'payments' => $payments, 'periods' => $periods];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actualizar un solo pago
|
||||||
|
*/
|
||||||
|
public static function update($houseId, $year, $period, $amount, $userId, $notes = null)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
$existing = $db->fetchOne(
|
||||||
|
"SELECT id FROM electricity_payments WHERE house_id = ? AND year = ? AND period = ?",
|
||||||
|
[$houseId, $year, $period]
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($amount == 0 && $existing) {
|
||||||
|
$db->execute(
|
||||||
|
"DELETE FROM electricity_payments WHERE id = ?",
|
||||||
|
[$existing['id']]
|
||||||
|
);
|
||||||
|
return ['success' => true, 'deleted' => true];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($existing) {
|
||||||
|
$db->execute(
|
||||||
|
"UPDATE electricity_payments SET amount = ?, payment_date = NOW(), notes = ?, created_by = ? WHERE id = ?",
|
||||||
|
[$amount, $notes, $userId, $existing['id']]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$db->execute(
|
||||||
|
"INSERT INTO electricity_payments (house_id, year, period, amount, payment_date, notes, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, NOW(), ?, ?)",
|
||||||
|
[$houseId, $year, $period, $amount, $notes, $userId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['success' => true, 'deleted' => false];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actualizar múltiples pagos en batch con transacción
|
||||||
|
* OPTIMIZADO: Similar a Payment::updateBatch()
|
||||||
|
*/
|
||||||
|
public static function updateBatch($changes, $userId)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$pdo = $db->getPDO();
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pdo->beginTransaction();
|
||||||
|
|
||||||
|
$updateCount = 0;
|
||||||
|
$deleteCount = 0;
|
||||||
|
|
||||||
|
// Preparar statements una sola vez (reutilización)
|
||||||
|
$insertStmt = $pdo->prepare(
|
||||||
|
"INSERT INTO electricity_payments (house_id, year, period, amount, payment_date, created_by)
|
||||||
|
VALUES (?, ?, ?, ?, NOW(), ?)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
amount = VALUES(amount),
|
||||||
|
payment_date = VALUES(payment_date),
|
||||||
|
created_by = VALUES(created_by)"
|
||||||
|
);
|
||||||
|
|
||||||
|
$deleteStmt = $pdo->prepare(
|
||||||
|
"DELETE FROM electricity_payments WHERE house_id = ? AND year = ? AND period = ?"
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($changes as $change) {
|
||||||
|
// Validar que tenemos los datos mínimos
|
||||||
|
if (!isset($change['house_id'], $change['year'], $change['period'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount = isset($change['amount']) ? (float)$change['amount'] : 0;
|
||||||
|
|
||||||
|
if ($amount == 0) {
|
||||||
|
// Eliminar si el monto es 0
|
||||||
|
$deleteStmt->execute([
|
||||||
|
$change['house_id'],
|
||||||
|
$change['year'],
|
||||||
|
$change['period']
|
||||||
|
]);
|
||||||
|
$deleteCount++;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Insertar o actualizar
|
||||||
|
$insertStmt->execute([
|
||||||
|
$change['house_id'],
|
||||||
|
$change['year'],
|
||||||
|
$change['period'],
|
||||||
|
$amount,
|
||||||
|
$userId
|
||||||
|
]);
|
||||||
|
$updateCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'success' => true,
|
||||||
|
'count' => $updateCount + $deleteCount,
|
||||||
|
'updated' => $updateCount,
|
||||||
|
'deleted' => $deleteCount
|
||||||
|
];
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (Exception $e) {
|
||||||
|
$pdo->rollback();
|
||||||
|
error_log("Error en ElectricityPayment::updateBatch: " . $e->getMessage());
|
||||||
|
return [
|
||||||
|
'success' => false,
|
||||||
|
'error' => $e->getMessage()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener pagos de una casa específica
|
||||||
|
*/
|
||||||
|
public static function getByHouse($houseId, $year = null)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
|
||||||
|
if ($year) {
|
||||||
|
return $db->fetchAll(
|
||||||
|
"SELECT * FROM electricity_payments WHERE house_id = ? AND year = ? ORDER BY FIELD(period, 'Ene-Feb', 'Mar-Abr', 'May-Jun', 'Jul-Ago', 'Sep-Oct', 'Nov-Dic')",
|
||||||
|
[$houseId, $year]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $db->fetchAll(
|
||||||
|
"SELECT * FROM electricity_payments WHERE house_id = ? ORDER BY year DESC, FIELD(period, 'Ene-Feb', 'Mar-Abr', 'May-Jun', 'Jul-Ago', 'Sep-Oct', 'Nov-Dic') DESC",
|
||||||
|
[$houseId]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtener total de pagos de un año
|
||||||
|
*/
|
||||||
|
public static function getTotalByYear($year)
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
$result = $db->fetchOne(
|
||||||
|
"SELECT COALESCE(SUM(amount), 0) as total FROM electricity_payments WHERE year = ?",
|
||||||
|
[$year]
|
||||||
|
);
|
||||||
|
return $result['total'] ?? 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
class Report {
|
class Report
|
||||||
public static function getGeneralBalance($startDate = null, $endDate = null) {
|
{
|
||||||
|
public static function getGeneralBalance($startDate = null, $endDate = null)
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
|
|
||||||
$whereClause = '';
|
$whereClause = '';
|
||||||
@@ -19,7 +21,8 @@ class Report {
|
|||||||
|
|
||||||
if ($whereClause) {
|
if ($whereClause) {
|
||||||
$whereClause .= " AND cp.house_id IN ($placeholders)";
|
$whereClause .= " AND cp.house_id IN ($placeholders)";
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
$whereClause = " WHERE cp.house_id IN ($placeholders)";
|
$whereClause = " WHERE cp.house_id IN ($placeholders)";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +69,8 @@ class Report {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getConceptDetailsByYear($year = null) {
|
public static function getConceptDetailsByYear($year = null)
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
$concepts = $db->fetchAll(
|
$concepts = $db->fetchAll(
|
||||||
"SELECT c.id, c.name, c.amount_per_house, c.concept_date, c.description
|
"SELECT c.id, c.name, c.amount_per_house, c.concept_date, c.description
|
||||||
@@ -152,7 +156,8 @@ class Report {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getHouseStatement($houseId, $year = null) {
|
public static function getHouseStatement($houseId, $year = null)
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
$house = House::findById($houseId);
|
$house = House::findById($houseId);
|
||||||
|
|
||||||
@@ -184,7 +189,8 @@ class Report {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getPaymentsByYear($year) {
|
public static function getPaymentsByYear($year)
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
$months = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
|
$months = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
|
||||||
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
|
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
|
||||||
@@ -206,7 +212,8 @@ class Report {
|
|||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getExpensesByCategory($startDate = null, $endDate = null) {
|
public static function getExpensesByCategory($startDate = null, $endDate = null)
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
|
|
||||||
$sql = "SELECT category, COALESCE(SUM(amount), 0) as total
|
$sql = "SELECT category, COALESCE(SUM(amount), 0) as total
|
||||||
@@ -224,7 +231,8 @@ class Report {
|
|||||||
return $db->fetchAll($sql, $params);
|
return $db->fetchAll($sql, $params);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getCollectionReport($conceptId) {
|
public static function getCollectionReport($conceptId)
|
||||||
|
{
|
||||||
$concept = CollectionConcept::findById($conceptId);
|
$concept = CollectionConcept::findById($conceptId);
|
||||||
$status = CollectionConcept::getCollectionStatus($conceptId);
|
$status = CollectionConcept::getCollectionStatus($conceptId);
|
||||||
$payments = CollectionConcept::getPaymentsByConcept($conceptId);
|
$payments = CollectionConcept::getPaymentsByConcept($conceptId);
|
||||||
@@ -236,7 +244,8 @@ class Report {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getDashboardStats($year = null, $accessibleHouseIds = []) {
|
public static function getDashboardStats($year = null, $accessibleHouseIds = [])
|
||||||
|
{
|
||||||
$year = $year ?? date('Y');
|
$year = $year ?? date('Y');
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
|
|
||||||
@@ -263,7 +272,8 @@ class Report {
|
|||||||
|
|
||||||
$totalExpenses = 0;
|
$totalExpenses = 0;
|
||||||
$balance = $conceptPayments;
|
$balance = $conceptPayments;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
$totalHouses = House::countAll();
|
$totalHouses = House::countAll();
|
||||||
$activeHouses = House::countActive();
|
$activeHouses = House::countActive();
|
||||||
|
|
||||||
@@ -304,7 +314,8 @@ class Report {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getWaterDebtors($filters = []) {
|
public static function getWaterDebtors($filters = [])
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
$allMonths = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
|
$allMonths = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
|
||||||
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
|
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
|
||||||
@@ -338,7 +349,8 @@ class Report {
|
|||||||
|
|
||||||
if ($year) {
|
if ($year) {
|
||||||
$yearsToCheck = [$year];
|
$yearsToCheck = [$year];
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
$years = $db->fetchAll("SELECT DISTINCT year FROM payments ORDER BY year");
|
$years = $db->fetchAll("SELECT DISTINCT year FROM payments ORDER BY year");
|
||||||
$yearsToCheck = array_column($years, 'year');
|
$yearsToCheck = array_column($years, 'year');
|
||||||
}
|
}
|
||||||
@@ -404,7 +416,8 @@ class Report {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getConceptDebtors($accessibleHouseIds = []) {
|
public static function getConceptDebtors($accessibleHouseIds = [])
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
$concepts = $db->fetchAll(
|
$concepts = $db->fetchAll(
|
||||||
"SELECT c.id, c.name, c.amount_per_house
|
"SELECT c.id, c.name, c.amount_per_house
|
||||||
@@ -483,7 +496,8 @@ class Report {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getConceptDebtorsFiltered($houseIds, $conceptIds = null) {
|
public static function getConceptDebtorsFiltered($houseIds, $conceptIds = null)
|
||||||
|
{
|
||||||
$db = Database::getInstance();
|
$db = Database::getInstance();
|
||||||
|
|
||||||
$whereConditions = [];
|
$whereConditions = [];
|
||||||
@@ -578,4 +592,123 @@ class Report {
|
|||||||
'total_due' => $grandTotal
|
'total_due' => $grandTotal
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function getElectricityDebtors($filters = [])
|
||||||
|
{
|
||||||
|
$db = Database::getInstance();
|
||||||
|
require_once __DIR__ . '/ElectricityBill.php';
|
||||||
|
require_once __DIR__ . '/ElectricityPayment.php';
|
||||||
|
|
||||||
|
$year = $filters['year'] ?? null;
|
||||||
|
$periods = $filters['periods'] ?? ElectricityBill::getPeriods();
|
||||||
|
$houseId = $filters['house_id'] ?? null;
|
||||||
|
$accessibleHouseIds = $filters['accessible_house_ids'] ?? [];
|
||||||
|
|
||||||
|
// 1. Obtener Casas Activas
|
||||||
|
$whereHouse = '';
|
||||||
|
$houseParams = [];
|
||||||
|
|
||||||
|
if ($houseId) {
|
||||||
|
$whereHouse = "AND h.id = ?";
|
||||||
|
$houseParams = [$houseId];
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = "SELECT h.id, h.number, h.owner_name, h.status
|
||||||
|
FROM houses h
|
||||||
|
WHERE h.status = 'activa' $whereHouse";
|
||||||
|
|
||||||
|
if (!empty($accessibleHouseIds) && !Auth::isAdmin()) {
|
||||||
|
$placeholders = str_repeat('?,', count($accessibleHouseIds) - 1) . '?';
|
||||||
|
$sql .= " AND h.id IN ($placeholders)";
|
||||||
|
$houseParams = array_merge($houseParams, $accessibleHouseIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql .= " ORDER BY CAST(h.number AS UNSIGNED)";
|
||||||
|
$houses = $db->fetchAll($sql, $houseParams);
|
||||||
|
|
||||||
|
// 2. Determinar Años a revisar
|
||||||
|
if ($year) {
|
||||||
|
$yearsToCheck = [$year];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Revisar años donde hay configuración de recibos
|
||||||
|
$years = $db->fetchAll("SELECT DISTINCT year FROM electricity_bills ORDER BY year DESC");
|
||||||
|
$yearsToCheck = array_column($years, 'year');
|
||||||
|
if (empty($yearsToCheck))
|
||||||
|
$yearsToCheck = [date('Y')];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Calcular Deudas
|
||||||
|
$debtors = [];
|
||||||
|
$grandTotalExpected = 0;
|
||||||
|
$grandTotalPaid = 0;
|
||||||
|
|
||||||
|
foreach ($houses as $house) {
|
||||||
|
$totalExpected = 0;
|
||||||
|
$totalPaid = 0;
|
||||||
|
$periodDetails = [];
|
||||||
|
|
||||||
|
foreach ($yearsToCheck as $yr) {
|
||||||
|
// Obtener configuración del año para optimizar (evitar queries por periodo)
|
||||||
|
$billsConfig = ElectricityBill::getYear($yr);
|
||||||
|
|
||||||
|
foreach ($periods as $period) {
|
||||||
|
$config = $billsConfig[$period] ?? null;
|
||||||
|
|
||||||
|
// Solo cobrar si hay configuración y monto > 0
|
||||||
|
if (!$config || $config['amount_per_house'] <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$expected = $config['amount_per_house'];
|
||||||
|
|
||||||
|
// Obtener pago
|
||||||
|
$payment = $db->fetchOne(
|
||||||
|
"SELECT amount FROM electricity_payments WHERE house_id = ? AND year = ? AND period = ?",
|
||||||
|
[$house['id'], $yr, $period]
|
||||||
|
);
|
||||||
|
$paid = $payment['amount'] ?? 0;
|
||||||
|
$due = $expected - $paid;
|
||||||
|
|
||||||
|
$totalExpected += $expected;
|
||||||
|
$totalPaid += $paid;
|
||||||
|
|
||||||
|
if ($due > 0.01) { // Tolerancia pequeña a flotantes
|
||||||
|
$periodDetails[] = [
|
||||||
|
'year' => $yr,
|
||||||
|
'period' => $period,
|
||||||
|
'expected' => $expected,
|
||||||
|
'paid' => $paid,
|
||||||
|
'due' => $due
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$houseTotalDue = $totalExpected - $totalPaid;
|
||||||
|
|
||||||
|
if ($houseTotalDue > 0.01) {
|
||||||
|
$debtors[] = [
|
||||||
|
'house_id' => $house['id'],
|
||||||
|
'house_number' => $house['number'],
|
||||||
|
'owner_name' => $house['owner_name'],
|
||||||
|
'periods_due' => $periodDetails,
|
||||||
|
'total_due' => $houseTotalDue
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$grandTotalExpected += $totalExpected;
|
||||||
|
$grandTotalPaid += $totalPaid;
|
||||||
|
}
|
||||||
|
|
||||||
|
$grandTotalDue = $grandTotalExpected - $grandTotalPaid;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'debtors' => $debtors,
|
||||||
|
'total_due' => $grandTotalDue,
|
||||||
|
'total_expected' => $grandTotalExpected,
|
||||||
|
'total_paid' => $grandTotalPaid,
|
||||||
|
'filters' => $filters
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
600
views/electricity/index.php
Executable file
600
views/electricity/index.php
Executable file
@@ -0,0 +1,600 @@
|
|||||||
|
<?php
|
||||||
|
// views/electricity/index.php
|
||||||
|
$periods = ElectricityBill::getPeriods();
|
||||||
|
?>
|
||||||
|
|
||||||
|
<div class="row mb-4">
|
||||||
|
<div class="col-12">
|
||||||
|
<h2><i class="bi bi-lightbulb"></i> Pagos de Luz - Cámara</h2>
|
||||||
|
<p class="text-muted">Concentrado de pagos bimestrales por casa</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-4 d-flex justify-content-between align-items-center flex-wrap gap-2">
|
||||||
|
<div class="d-flex gap-3 align-items-center flex-wrap">
|
||||||
|
<div>
|
||||||
|
<label for="yearSelect" class="form-label me-2">Año:</label>
|
||||||
|
<select id="yearSelect" class="form-select d-inline-block" style="width: auto;"
|
||||||
|
onchange="window.location.href='dashboard.php?page=luz_camara&year='+this.value">
|
||||||
|
<?php for ($y = 2024; $y <= date('Y') + 1; $y++): ?>
|
||||||
|
<option value="<?= $y?>" <?= $y == $year ? 'selected' : ''?>>
|
||||||
|
<?= $y?>
|
||||||
|
</option>
|
||||||
|
<?php
|
||||||
|
endfor; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="btn-group">
|
||||||
|
<button type="button" class="btn btn-outline-primary dropdown-toggle" data-bs-toggle="dropdown">
|
||||||
|
<i class="bi bi-download"></i> Exportar
|
||||||
|
</button>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportPDF()">PDF</a></li>
|
||||||
|
<li><a class="dropdown-item" href="#" onclick="exportCSV()">CSV</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<?php if (Auth::isCapturist()): ?>
|
||||||
|
<button onclick="saveChanges()" id="btnSaveTop" class="btn btn-warning position-relative" disabled>
|
||||||
|
<i class="bi bi-save"></i> Guardar Cambios
|
||||||
|
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger"
|
||||||
|
id="changesBadge" style="display: none;">
|
||||||
|
0
|
||||||
|
<span class="visually-hidden">cambios pendientes</span>
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Configuración de Recibos Bimestrales -->
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-body">
|
||||||
|
<h5 class="card-title mb-3"><i class="bi bi-gear"></i> Configuración de Recibos (CFE)</h5>
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label small text-muted">Periodo</label>
|
||||||
|
<select id="configPeriod" class="form-select form-select-sm" onchange="loadConfig()">
|
||||||
|
<?php foreach ($periods as $period): ?>
|
||||||
|
<option value="<?= $period?>">
|
||||||
|
<?= $period?>
|
||||||
|
</option>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label small text-muted">Total CFE</label>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text">$</span>
|
||||||
|
<input type="number" id="configTotal" class="form-control" step="0.01" <?=!Auth::isCapturist()
|
||||||
|
? 'disabled' : ''?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<label class="form-label small text-muted">Por Casa</label>
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text">$</span>
|
||||||
|
<input type="number" id="configPerHouse" class="form-control" step="0.01" <?=!Auth::isCapturist()
|
||||||
|
? 'disabled' : ''?>>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label small text-muted">Notas</label>
|
||||||
|
<input type="text" id="configNotes" class="form-control form-control-sm" placeholder="Opcional..."
|
||||||
|
<?=!Auth::isCapturist() ? 'disabled' : ''?>>
|
||||||
|
</div>
|
||||||
|
<?php if (Auth::isCapturist()): ?>
|
||||||
|
<div class="col-md-2 d-flex align-items-end">
|
||||||
|
<button class="btn btn-success btn-sm w-100" onclick="saveConfig()">
|
||||||
|
<i class="bi bi-check2"></i> Guardar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tabla de Pagos -->
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-bordered table-hover mb-0" id="electricityTable">
|
||||||
|
<thead class="table-dark text-center sticky-top">
|
||||||
|
<tr>
|
||||||
|
<th style="width: 80px;">Casa</th>
|
||||||
|
<th style="width: 100px;">Estado</th>
|
||||||
|
<?php foreach ($periods as $period):
|
||||||
|
$config = $electricityBills[$period] ?? [];
|
||||||
|
$amountPerHouse = $config['amount_per_house'] ?? 0;
|
||||||
|
?>
|
||||||
|
<th>
|
||||||
|
<?= $period?><br>
|
||||||
|
<small class="text-white" style="font-weight: normal;">$
|
||||||
|
<?= number_format($amountPerHouse, 2)?>
|
||||||
|
</small>
|
||||||
|
</th>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
<th>Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
$periodTotals = array_fill_keys($periods, 0);
|
||||||
|
$grandTotal = 0;
|
||||||
|
|
||||||
|
foreach ($matrix['houses'] as $house):
|
||||||
|
// Filtrar visibilidad para usuarios normales
|
||||||
|
if (!Auth::canViewHouse($house['id']))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$houseTotal = 0;
|
||||||
|
?>
|
||||||
|
<tr data-house-id="<?= $house['id']?>" data-house-number="<?= $house['number']?>">
|
||||||
|
<td class="text-center fw-bold">
|
||||||
|
<?= $house['number']?>
|
||||||
|
</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<span class="badge <?= $house['status'] == 'activa' ? 'paid' : 'inactive'?>">
|
||||||
|
<?= ucfirst($house['status'])?>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<?php foreach ($periods as $period):
|
||||||
|
$payment = $matrix['payments'][$period][$house['id']] ?? null;
|
||||||
|
$amount = $payment['amount'] ?? 0;
|
||||||
|
$periodTotals[$period] += $amount;
|
||||||
|
$houseTotal += $amount;
|
||||||
|
|
||||||
|
$config = $electricityBills[$period] ?? [];
|
||||||
|
$expected = $config['amount_per_house'] ?? 0;
|
||||||
|
|
||||||
|
$cellClass = '';
|
||||||
|
$cellText = '-';
|
||||||
|
|
||||||
|
if ($amount > 0) {
|
||||||
|
if ($expected > 0 && $amount >= $expected) {
|
||||||
|
$cellClass = 'paid'; // Paid
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$cellClass = 'partial'; // Partial
|
||||||
|
}
|
||||||
|
$cellText = '$' . number_format($amount, 2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($expected > 0) {
|
||||||
|
$cellClass = 'pending'; // Unpaid
|
||||||
|
$cellText = '-';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$cellClass = ''; // No config
|
||||||
|
$cellText = '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Si está deshabitada y no hay pago, gris
|
||||||
|
if ($house['status'] == 'deshabitada' && $amount == 0) {
|
||||||
|
$cellClass = 'inactive';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$isEditable = Auth::isCapturist() && $house['status'] == 'activa';
|
||||||
|
?>
|
||||||
|
<td class="payment-cell text-center <?= $cellClass?>" data-house-id="<?= $house['id']?>"
|
||||||
|
data-period="<?= $period?>" data-amount="<?= $amount?>" data-expected="<?= $expected?>"
|
||||||
|
<?= $isEditable ? 'contenteditable="true"' : ''?>>
|
||||||
|
<?= $cellText?>
|
||||||
|
</td>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
|
||||||
|
<td class="text-end fw-bold table-active">
|
||||||
|
$
|
||||||
|
<?= number_format($houseTotal, 2)?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php $grandTotal += $houseTotal;
|
||||||
|
endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="fw-bold">
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-end">Totales:</td>
|
||||||
|
<?php foreach ($periods as $period): ?>
|
||||||
|
<td class="text-center">$
|
||||||
|
<?= number_format($periodTotals[$period], 2)?>
|
||||||
|
</td>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
<td class="text-end">$
|
||||||
|
<?= number_format($grandTotal, 2)?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php if (Auth::isCapturist()): ?>
|
||||||
|
<div class="d-flex justify-content-end mb-4 no-print mt-3">
|
||||||
|
<button onclick="saveChanges()" id="btnSaveBottom" class="btn btn-warning" disabled>
|
||||||
|
<i class="bi bi-save"></i> Guardar Cambios
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
|
||||||
|
<div class="row mt-3 no-print">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="alert alert-info mb-0 py-2">
|
||||||
|
<strong><i class="bi bi-info-circle"></i> Instrucciones:</strong>
|
||||||
|
<ul class="mb-0 mt-1 list-inline">
|
||||||
|
<li class="list-inline-item"><span class="badge paid">Verde</span> = Pagado</li>
|
||||||
|
<li class="list-inline-item"><span class="badge partial">Amarillo</span> = Parcial</li>
|
||||||
|
<li class="list-inline-item"><span class="badge pending">Rojo</span> = Pendiente</li>
|
||||||
|
<li class="list-inline-item"><span class="badge inactive">Gris</span> = Inactivo/N.A.</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal Export PDF -->
|
||||||
|
<div class="modal fade" id="exportPdfModal" tabindex="-1">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Exportar a PDF</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<h6>Selecciona los periodos a exportar:</h6>
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input" type="checkbox" id="selectAllPeriods" checked>
|
||||||
|
<label class="form-check-label" for="selectAllPeriods">Todos</label>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="row">
|
||||||
|
<?php foreach ($periods as $i => $period): ?>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="form-check">
|
||||||
|
<input class="form-check-input period-checkbox" type="checkbox" value="<?= $period?>"
|
||||||
|
id="period_<?= $i?>" checked>
|
||||||
|
<label class="form-check-label" for="period_<?= $i?>">
|
||||||
|
<?= $period?>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancelar</button>
|
||||||
|
<button type="button" class="btn btn-success" onclick="generatePDF()">
|
||||||
|
<i class="bi bi-file-earmark-pdf"></i> Generar PDF
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Datos de configuración iniciales
|
||||||
|
const electricityConfig = <?= json_encode($electricityBills)?>;
|
||||||
|
let pendingChanges = {};
|
||||||
|
|
||||||
|
// Cargar configuración al cambiar select
|
||||||
|
function loadConfig() {
|
||||||
|
const period = document.getElementById('configPeriod').value;
|
||||||
|
const data = electricityConfig[period] || {};
|
||||||
|
|
||||||
|
document.getElementById('configTotal').value = data.total_amount || '';
|
||||||
|
document.getElementById('configPerHouse').value = data.amount_per_house || '';
|
||||||
|
document.getElementById('configNotes').value = data.notes || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inicializar configuración
|
||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
loadConfig();
|
||||||
|
setupEditableCells();
|
||||||
|
|
||||||
|
// Listener para modal
|
||||||
|
document.getElementById('selectAllPeriods').addEventListener('change', function () {
|
||||||
|
document.querySelectorAll('.period-checkbox').forEach(cb => {
|
||||||
|
cb.checked = this.checked;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupEditableCells() {
|
||||||
|
document.querySelectorAll('.payment-cell[contenteditable="true"]').forEach(cell => {
|
||||||
|
cell.addEventListener('focus', function () {
|
||||||
|
this.classList.add('editing');
|
||||||
|
const text = this.textContent.trim();
|
||||||
|
// Limpiar si es guión, moneda o 0 al enfocar para facilitar edición
|
||||||
|
if (text === '-' || text.startsWith('$') || parseAmount(text) === 0) {
|
||||||
|
this.textContent = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cell.addEventListener('blur', function () {
|
||||||
|
this.classList.remove('editing');
|
||||||
|
let newText = this.textContent.trim();
|
||||||
|
let newVal = parseAmount(newText);
|
||||||
|
|
||||||
|
// Formatear display
|
||||||
|
if (newVal === 0) {
|
||||||
|
const expected = parseFloat(this.dataset.expected);
|
||||||
|
if (expected > 0) {
|
||||||
|
this.textContent = '-'; // Pendiente
|
||||||
|
} else {
|
||||||
|
this.textContent = '-'; // Nada
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.textContent = '$' + newVal.toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackChange(this, newVal);
|
||||||
|
});
|
||||||
|
|
||||||
|
cell.addEventListener('keydown', function (e) {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
this.blur();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAmount(text) {
|
||||||
|
if (text === '-' || !text) return 0;
|
||||||
|
return parseFloat(text.replace(/[^0-9.-]+/g, '')) || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
<?php if (Auth::isCapturist()): ?>
|
||||||
|
// Guardar configuración
|
||||||
|
async function saveConfig() {
|
||||||
|
const period = document.getElementById('configPeriod').value;
|
||||||
|
const total = document.getElementById('configTotal').value;
|
||||||
|
const perHouse = document.getElementById('configPerHouse').value;
|
||||||
|
const notes = document.getElementById('configNotes').value;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('dashboard.php?page=luz_camara_config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
year: <?= $year?>,
|
||||||
|
period: period,
|
||||||
|
total_amount: total,
|
||||||
|
amount_per_house: perHouse,
|
||||||
|
notes: notes
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
// Actualizar objeto local
|
||||||
|
electricityConfig[period] = {
|
||||||
|
year: <?= $year?>,
|
||||||
|
period: period,
|
||||||
|
total_amount: total,
|
||||||
|
amount_per_house: perHouse,
|
||||||
|
notes: notes
|
||||||
|
};
|
||||||
|
alert('Configuración guardada exitosamente');
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Error al guardar: ' + result.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error de conexión al guardar configuración');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tracking de cambios
|
||||||
|
function trackChange(cell, newVal) {
|
||||||
|
const houseId = cell.dataset.houseId;
|
||||||
|
const period = cell.dataset.period;
|
||||||
|
const key = `${houseId}_${period}`;
|
||||||
|
const original = parseFloat(cell.dataset.amount);
|
||||||
|
|
||||||
|
// Si el valor cambió respecto al original cargado
|
||||||
|
if (newVal !== original) {
|
||||||
|
pendingChanges[key] = {
|
||||||
|
house_id: houseId,
|
||||||
|
year: <?= $year?>,
|
||||||
|
period: period,
|
||||||
|
amount: newVal
|
||||||
|
};
|
||||||
|
cell.classList.add('table-info');
|
||||||
|
cell.style.fontWeight = 'bold';
|
||||||
|
} else {
|
||||||
|
delete pendingChanges[key];
|
||||||
|
cell.classList.remove('table-info');
|
||||||
|
cell.style.fontWeight = 'normal';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSaveButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSaveButton() {
|
||||||
|
const count = Object.keys(pendingChanges).length;
|
||||||
|
const btnTop = document.getElementById('btnSaveTop');
|
||||||
|
const btnBottom = document.getElementById('btnSaveBottom');
|
||||||
|
const badge = document.getElementById('changesBadge');
|
||||||
|
|
||||||
|
if (btnTop) {
|
||||||
|
btnTop.disabled = count === 0;
|
||||||
|
// Duplicar comportamiento: mostrar texto con conteo
|
||||||
|
if (count > 0) {
|
||||||
|
btnTop.innerHTML = `<i class="bi bi-save"></i> Guardar ${count} Cambios`;
|
||||||
|
btnTop.classList.remove('btn-outline-secondary');
|
||||||
|
btnTop.classList.add('btn-warning');
|
||||||
|
} else {
|
||||||
|
btnTop.innerHTML = `<i class="bi bi-save"></i> Guardar Cambios`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (btnBottom) {
|
||||||
|
btnBottom.disabled = count === 0;
|
||||||
|
btnBottom.innerHTML = `<i class="bi bi-save"></i> Guardar ${count} Cambios`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alerta de navegación si hay cambios
|
||||||
|
if (count > 0) {
|
||||||
|
window.onbeforeunload = () => "Tienes cambios sin guardar. ¿Seguro que quieres salir?";
|
||||||
|
} else {
|
||||||
|
window.onbeforeunload = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveChanges() {
|
||||||
|
if (Object.keys(pendingChanges).length === 0) return;
|
||||||
|
|
||||||
|
const changes = Object.values(pendingChanges);
|
||||||
|
const btnTop = document.getElementById('btnSaveTop');
|
||||||
|
const btnBottom = document.getElementById('btnSaveBottom');
|
||||||
|
|
||||||
|
const originalTextTop = btnTop ? btnTop.innerHTML : '';
|
||||||
|
const originalTextBottom = btnBottom ? btnBottom.innerHTML : '';
|
||||||
|
|
||||||
|
if (btnTop) {
|
||||||
|
btnTop.disabled = true;
|
||||||
|
btnTop.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Guardando...';
|
||||||
|
}
|
||||||
|
if (btnBottom) {
|
||||||
|
btnBottom.disabled = true;
|
||||||
|
btnBottom.innerHTML = '<span class="spinner-border spinner-border-sm"></span> Guardando...';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('dashboard.php?page=luz_camara_actions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ changes: changes })
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
window.onbeforeunload = null;
|
||||||
|
alert('Cambios guardados exitosamente');
|
||||||
|
location.reload();
|
||||||
|
} else {
|
||||||
|
alert('Error al guardar: ' + (result.message || result.error));
|
||||||
|
if (btnTop) {
|
||||||
|
btnTop.disabled = false;
|
||||||
|
btnTop.innerHTML = originalTextTop;
|
||||||
|
}
|
||||||
|
if (btnBottom) {
|
||||||
|
btnBottom.disabled = false;
|
||||||
|
btnBottom.innerHTML = originalTextBottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
alert('Error de conexión');
|
||||||
|
if (btnTop) {
|
||||||
|
btnTop.disabled = false;
|
||||||
|
btnTop.innerHTML = originalTextTop;
|
||||||
|
}
|
||||||
|
if (btnBottom) {
|
||||||
|
btnBottom.disabled = false;
|
||||||
|
btnBottom.innerHTML = originalTextBottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
|
||||||
|
function exportPDF() {
|
||||||
|
const modal = new bootstrap.Modal(document.getElementById('exportPdfModal'));
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
function generatePDF() {
|
||||||
|
const checkboxes = document.querySelectorAll('.period-checkbox:checked');
|
||||||
|
const selectedPeriods = Array.from(checkboxes).map(cb => cb.value);
|
||||||
|
|
||||||
|
let url = 'dashboard.php?page=luz_camara&action=export_electricity_pdf&year=<?= $year?>';
|
||||||
|
selectedPeriods.forEach(period => {
|
||||||
|
url += `&periods[]=${encodeURIComponent(period)}`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Cerrar el modal antes de redirigir
|
||||||
|
const exportPdfModal = bootstrap.Modal.getInstance(document.getElementById('exportPdfModal'));
|
||||||
|
if (exportPdfModal) {
|
||||||
|
exportPdfModal.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirigir al usuario para iniciar la descarga del PDF
|
||||||
|
window.location.href = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportCSV() {
|
||||||
|
let csv = [];
|
||||||
|
const rows = document.querySelectorAll("#electricityTable tr");
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
const cols = row.querySelectorAll("td,th");
|
||||||
|
const rowData = [];
|
||||||
|
for (const col of cols) {
|
||||||
|
rowData.push('"' + col.innerText.replace(/(\r\n|\n|\r)/gm, " ").trim() + '"');
|
||||||
|
}
|
||||||
|
csv.push(rowData.join(","));
|
||||||
|
}
|
||||||
|
|
||||||
|
const csvFile = new Blob([csv.join("\n")], { type: "text/csv" });
|
||||||
|
const downloadLink = document.createElement("a");
|
||||||
|
downloadLink.download = "Pagos_Luz_Camara_<?= $year?>.csv";
|
||||||
|
downloadLink.href = window.URL.createObjectURL(csvFile);
|
||||||
|
downloadLink.style.display = "none";
|
||||||
|
document.body.appendChild(downloadLink);
|
||||||
|
downloadLink.click();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.payment-cell {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-cell:focus {
|
||||||
|
outline: 2px solid #0d6efd;
|
||||||
|
background-color: #fff !important;
|
||||||
|
color: #000;
|
||||||
|
z-index: 5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
|
||||||
|
.btn-group,
|
||||||
|
#btnSaveTop,
|
||||||
|
#btnSaveBottom,
|
||||||
|
.card-header,
|
||||||
|
.form-label,
|
||||||
|
.input-group,
|
||||||
|
.no-print {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
border: none !important;
|
||||||
|
shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
border: 1px solid #000;
|
||||||
|
color: #000 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
153
views/electricity/pdf_template.php
Normal file
153
views/electricity/pdf_template.php
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14pt;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-date {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 8pt;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-success {
|
||||||
|
color: green;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="print-title">Concentrado de Pagos de Luz - Cámara -
|
||||||
|
<?= $year?>
|
||||||
|
</div>
|
||||||
|
<div class="print-date">Fecha de generación:
|
||||||
|
<?= date('d/m/Y H:i')?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="10%">Casa</th>
|
||||||
|
<th width="10%">Estado</th>
|
||||||
|
<?php foreach ($periods as $period):
|
||||||
|
$config = $electricityBills[$period] ?? [];
|
||||||
|
$amountPerHouse = $config['amount_per_house'] ?? 0;
|
||||||
|
?>
|
||||||
|
<th>
|
||||||
|
<?= $period?><br><small>$
|
||||||
|
<?= number_format($amountPerHouse, 2)?>
|
||||||
|
</small>
|
||||||
|
</th>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
<th width="15%">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php
|
||||||
|
$grandTotal = 0;
|
||||||
|
$periodTotals = array_fill_keys($periods, 0);
|
||||||
|
|
||||||
|
foreach ($matrix['houses'] as $house):
|
||||||
|
// Filtrar solo casas permitidas (aunque el controlador ya debió filtrar)
|
||||||
|
if (!Auth::canViewHouse($house['id']))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
$houseTotal = 0;
|
||||||
|
?>
|
||||||
|
<tr>
|
||||||
|
<td><strong>
|
||||||
|
<?= $house['number']?>
|
||||||
|
</strong></td>
|
||||||
|
<td>
|
||||||
|
<?= $house['status'] == 'activa' ? 'Activa' : 'Deshabitada'?>
|
||||||
|
</td>
|
||||||
|
<?php foreach ($periods as $period):
|
||||||
|
$payment = $matrix['payments'][$period][$house['id']] ?? null;
|
||||||
|
$amount = $payment['amount'] ?? 0;
|
||||||
|
$periodTotals[$period] += $amount;
|
||||||
|
$houseTotal += $amount;
|
||||||
|
|
||||||
|
$config = $electricityBills[$period] ?? [];
|
||||||
|
$expected = $config['amount_per_house'] ?? 0;
|
||||||
|
|
||||||
|
$bg_color = '#FFFFFF';
|
||||||
|
// Lógica de colores idéntica a la vista web para consistencia
|
||||||
|
if ($amount > 0) {
|
||||||
|
if ($expected > 0 && $amount >= $expected) {
|
||||||
|
$bg_color = '#d4edda'; // Verde (paid)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$bg_color = '#fff3cd'; // Amarillo (partial)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if ($expected > 0) {
|
||||||
|
$bg_color = '#f8d7da'; // Rojo (pending)
|
||||||
|
}
|
||||||
|
elseif ($house['status'] == 'deshabitada') {
|
||||||
|
$bg_color = '#e2e3e5'; // Gris (inactive)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<td style="background-color: <?= $bg_color?>;">
|
||||||
|
<?= $amount > 0 ? '$' . number_format($amount, 2) : '-'?>
|
||||||
|
</td>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
|
||||||
|
<td><strong>$
|
||||||
|
<?= number_format($houseTotal, 2)?>
|
||||||
|
</strong></td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
$grandTotal += $houseTotal;
|
||||||
|
endforeach;
|
||||||
|
?>
|
||||||
|
<tr style="background-color: #bee5eb;">
|
||||||
|
<td colspan="2" style="text-align: right; font-weight: bold;">TOTALES:</td>
|
||||||
|
<?php foreach ($periods as $period): ?>
|
||||||
|
<td style="text-align: center; font-weight: bold;">
|
||||||
|
$
|
||||||
|
<?= number_format($periodTotals[$period], 2)?>
|
||||||
|
</td>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
<td style="text-align: center; font-weight: bold;">$
|
||||||
|
<?= number_format($grandTotal, 2)?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="margin-top: 20px; font-size: 8pt; page-break-inside: avoid;">
|
||||||
|
<strong>Leyenda:</strong>
|
||||||
|
<span style="background-color: #d4edda; padding: 2px 8px; margin: 2px; border: 1px solid #ccc;">Verde =
|
||||||
|
Pagado</span>
|
||||||
|
<span style="background-color: #fff3cd; padding: 2px 8px; margin: 2px; border: 1px solid #ccc;">Amarillo =
|
||||||
|
Parcial</span>
|
||||||
|
<span style="background-color: #f8d7da; padding: 2px 8px; margin: 2px; border: 1px solid #ccc;">Rojo =
|
||||||
|
Pendiente</span>
|
||||||
|
<span style="background-color: #e2e3e5; padding: 2px 8px; margin: 2px; border: 1px solid #ccc;">Gris =
|
||||||
|
Inactivo</span>
|
||||||
|
</div>
|
||||||
@@ -1,17 +1,19 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="es">
|
<html lang="es">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>IBIZA CEA - Sistema de Gestión</title>
|
<title>IBIZA CEA - Sistema de Gestión</title>
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css" rel="stylesheet">
|
||||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🏠</text></svg>">
|
<link rel="icon"
|
||||||
<link href="<?= SITE_URL ?>/assets/css/theme.css" rel="stylesheet">
|
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🏠</text></svg>">
|
||||||
|
<link href="<?= SITE_URL?>/assets/css/theme.css" rel="stylesheet">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Prevenir FOUC (Flash of Unstyled Content)
|
// Prevenir FOUC (Flash of Unstyled Content)
|
||||||
(function() {
|
(function () {
|
||||||
const theme = localStorage.getItem('theme') || 'light';
|
const theme = localStorage.getItem('theme') || 'light';
|
||||||
if (theme === 'dark') {
|
if (theme === 'dark') {
|
||||||
document.documentElement.classList.add('dark-mode');
|
document.documentElement.classList.add('dark-mode');
|
||||||
@@ -21,6 +23,7 @@
|
|||||||
|
|
||||||
|
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<?php if (Auth::check()): ?>
|
<?php if (Auth::check()): ?>
|
||||||
<nav class="navbar navbar-expand-lg navbar-dark bg-primary sticky-top">
|
<nav class="navbar navbar-expand-lg navbar-dark bg-primary sticky-top">
|
||||||
@@ -34,42 +37,54 @@
|
|||||||
<div class="collapse navbar-collapse" id="navbarNav">
|
<div class="collapse navbar-collapse" id="navbarNav">
|
||||||
<ul class="navbar-nav me-auto">
|
<ul class="navbar-nav me-auto">
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $page == 'dashboard' ? 'active' : '' ?>" href="/dashboard.php?page=dashboard">
|
<a class="nav-link <?= $page == 'dashboard' ? 'active' : ''?>"
|
||||||
|
href="/dashboard.php?page=dashboard">
|
||||||
<i class="bi bi-speedometer2"></i> Dashboard
|
<i class="bi bi-speedometer2"></i> Dashboard
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $page == 'pagos' ? 'active' : '' ?>" href="/dashboard.php?page=pagos">
|
<a class="nav-link <?= $page == 'pagos' ? 'active' : ''?>" href="/dashboard.php?page=pagos">
|
||||||
<i class="bi bi-droplet-fill"></i> Pagos de Agua
|
<i class="bi bi-droplet-fill"></i> Pagos de Agua
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $page == 'casas' ? 'active' : '' ?>" href="/dashboard.php?page=casas">
|
<a class="nav-link <?= $page == 'luz_camara' ? 'active' : ''?>"
|
||||||
|
href="/dashboard.php?page=luz_camara">
|
||||||
|
<i class="bi bi-lightbulb-fill"></i> Luz Cámara
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link <?= $page == 'casas' ? 'active' : ''?>" href="/dashboard.php?page=casas">
|
||||||
<i class="bi bi-building"></i> Casas
|
<i class="bi bi-building"></i> Casas
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $page == 'finanzas' ? 'active' : '' ?>" href="/dashboard.php?page=finanzas">
|
<a class="nav-link <?= $page == 'finanzas' ? 'active' : ''?>"
|
||||||
|
href="/dashboard.php?page=finanzas">
|
||||||
<i class="bi bi-cash-coin"></i> Finanzas
|
<i class="bi bi-cash-coin"></i> Finanzas
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $page == 'graficos' ? 'active' : '' ?>" href="/dashboard.php?page=graficos">
|
<a class="nav-link <?= $page == 'graficos' ? 'active' : ''?>"
|
||||||
|
href="/dashboard.php?page=graficos">
|
||||||
<i class="bi bi-bar-chart-line-fill"></i> Gráficos
|
<i class="bi bi-bar-chart-line-fill"></i> Gráficos
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $page == 'reportes' ? 'active' : '' ?>" href="/dashboard.php?page=reportes">
|
<a class="nav-link <?= $page == 'reportes' ? 'active' : ''?>"
|
||||||
|
href="/dashboard.php?page=reportes">
|
||||||
<i class="bi bi-file-earmark-bar-graph"></i> Reportes
|
<i class="bi bi-file-earmark-bar-graph"></i> Reportes
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php if (Auth::isAdmin()): ?>
|
<?php if (Auth::isAdmin()): ?>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link <?= $page == 'importar' ? 'active' : '' ?>" href="/dashboard.php?page=importar">
|
<a class="nav-link <?= $page == 'importar' ? 'active' : ''?>"
|
||||||
|
href="/dashboard.php?page=importar">
|
||||||
<i class="bi bi-file-earmark-arrow-up"></i> Importar
|
<i class="bi bi-file-earmark-arrow-up"></i> Importar
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<?php endif; ?>
|
<?php
|
||||||
|
endif; ?>
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
<li class="nav-item d-flex align-items-center">
|
<li class="nav-item d-flex align-items-center">
|
||||||
@@ -78,55 +93,80 @@
|
|||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button" data-bs-toggle="dropdown">
|
<a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
|
||||||
|
data-bs-toggle="dropdown">
|
||||||
<i class="bi bi-person-circle"></i>
|
<i class="bi bi-person-circle"></i>
|
||||||
<?= htmlspecialchars(Auth::user()['first_name'] ?? 'Usuario') ?>
|
<?= htmlspecialchars(Auth::user()['first_name'] ?? 'Usuario')?>
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu dropdown-menu-end">
|
<ul class="dropdown-menu dropdown-menu-end">
|
||||||
<?php if (Auth::isAdmin()): ?>
|
<?php if (Auth::isAdmin()): ?>
|
||||||
<li><a class="dropdown-item" href="/dashboard.php?page=usuarios"><i class="bi bi-people"></i> Usuarios</a></li>
|
<li><a class="dropdown-item" href="/dashboard.php?page=usuarios"><i
|
||||||
<li><a class="dropdown-item" href="/dashboard.php?page=configurar"><i class="bi bi-gear"></i> Configurar</a></li>
|
class="bi bi-people"></i> Usuarios</a></li>
|
||||||
<li><hr class="dropdown-divider"></li>
|
<li><a class="dropdown-item" href="/dashboard.php?page=configurar"><i
|
||||||
<?php endif; ?>
|
class="bi bi-gear"></i> Configurar</a></li>
|
||||||
|
<li>
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
<?php if (Auth::isAdmin()): ?>
|
<?php if (Auth::isAdmin()): ?>
|
||||||
<li><span class="dropdown-item text-muted small"><i class="bi bi-server"></i> <?= DB_HOST ?>:<?= DB_PORT ?></span></li>
|
<li><span class="dropdown-item text-muted small"><i class="bi bi-server"></i>
|
||||||
<li><span class="dropdown-item text-muted small"><i class="bi bi-database"></i> DB: <?= DB_NAME ?></span></li>
|
<?= DB_HOST?>:
|
||||||
<li><hr class="dropdown-divider"></li>
|
<?= DB_PORT?>
|
||||||
<?php endif; ?>
|
</span></li>
|
||||||
<li><a class="dropdown-item" href="/dashboard.php?page=profile"><i class="bi bi-person"></i> Perfil</a></li>
|
<li><span class="dropdown-item text-muted small"><i class="bi bi-database"></i> DB:
|
||||||
<li><hr class="dropdown-divider"></li>
|
<?= DB_NAME?>
|
||||||
<li><a class="dropdown-item text-danger" href="/logout.php"><i class="bi bi-box-arrow-right"></i> Cerrar Sesión</a></li>
|
</span></li>
|
||||||
|
<li>
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
<li><a class="dropdown-item" href="/dashboard.php?page=profile"><i class="bi bi-person"></i>
|
||||||
|
Perfil</a></li>
|
||||||
|
<li>
|
||||||
|
<hr class="dropdown-divider">
|
||||||
|
</li>
|
||||||
|
<li><a class="dropdown-item text-danger" href="/logout.php"><i
|
||||||
|
class="bi bi-box-arrow-right"></i> Cerrar Sesión</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<?php endif; ?>
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
|
||||||
<div class="container-fluid py-4">
|
<div class="container-fluid py-4">
|
||||||
<?php
|
<?php
|
||||||
$viewPath = __DIR__ . '/../' . $view . '.php';
|
$viewPath = __DIR__ . '/../' . $view . '.php';
|
||||||
if (isset($view) && file_exists($viewPath)):
|
if (isset($view) && file_exists($viewPath)):
|
||||||
?>
|
?>
|
||||||
<?php include $viewPath; ?>
|
<?php include $viewPath; ?>
|
||||||
<?php else: ?>
|
<?php
|
||||||
|
else: ?>
|
||||||
<div class="alert alert-danger">
|
<div class="alert alert-danger">
|
||||||
Vista no encontrada: <?= htmlspecialchars($view ?? '') ?><br>
|
Vista no encontrada:
|
||||||
Ruta: <?= htmlspecialchars($viewPath ?? '') ?><br>
|
<?= htmlspecialchars($view ?? '')?><br>
|
||||||
Existe: <?= isset($view) && file_exists($viewPath) ? 'Sí' : 'No' ?>
|
Ruta:
|
||||||
|
<?= htmlspecialchars($viewPath ?? '')?><br>
|
||||||
|
Existe:
|
||||||
|
<?= isset($view) && file_exists($viewPath) ? 'Sí' : 'No'?>
|
||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php
|
||||||
|
endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
<script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script>
|
||||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
<script src="<?= SITE_URL ?>/assets/js/theme.js"></script>
|
<script src="<?= SITE_URL?>/assets/js/theme.js"></script>
|
||||||
<footer class="footer mt-auto py-3">
|
<footer class="footer mt-auto py-3">
|
||||||
<div class="container-fluid text-center">
|
<div class="container-fluid text-center">
|
||||||
<span class="text-muted">Condominio IBIZA - Derechos reservados Miguel Pons casa 11</span>
|
<span class="text-muted">Condominio IBIZA - Derechos reservados Miguel Pons casa 11</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -19,6 +19,9 @@
|
|||||||
<a href="/dashboard.php?page=reportes&type=concepts" class="btn btn-outline-info <?= ($_GET['type'] ?? '') == 'concepts' ? 'active' : '' ?>">
|
<a href="/dashboard.php?page=reportes&type=concepts" class="btn btn-outline-info <?= ($_GET['type'] ?? '') == 'concepts' ? 'active' : '' ?>">
|
||||||
<i class="bi bi-collection"></i> Conceptos Especiales
|
<i class="bi bi-collection"></i> Conceptos Especiales
|
||||||
</a>
|
</a>
|
||||||
|
<a href="/dashboard.php?page=reportes&type=electricity-debtors" class="btn btn-outline-warning <?= ($_GET['type'] ?? '') == 'electricity-debtors' ? 'active' : '' ?>">
|
||||||
|
<i class="bi bi-lightbulb-fill"></i> Deudores Luz
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -745,6 +748,233 @@ function exportConceptsCSV() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<?php elseif ($reportType == 'electricity-debtors' && isset($electricityDebtors)): ?>
|
||||||
|
<?php
|
||||||
|
$hasFilters = !empty($electricityDebtors['filters']['year']) || !empty($electricityDebtors['filters']['periods']) || !empty($electricityDebtors['filters']['house_id']);
|
||||||
|
$filterText = [];
|
||||||
|
if (!empty($electricityDebtors['filters']['year'])) {
|
||||||
|
$filterText[] = "Año: " . $electricityDebtors['filters']['year'];
|
||||||
|
}
|
||||||
|
if (!empty($electricityDebtors['filters']['periods'])) {
|
||||||
|
$filterText[] = "Periodos: " . implode(', ', $electricityDebtors['filters']['periods']);
|
||||||
|
}
|
||||||
|
if (!empty($electricityDebtors['filters']['house_id'])) {
|
||||||
|
require_once __DIR__ . '/../../models/House.php';
|
||||||
|
$house = House::findById($electricityDebtors['filters']['house_id']);
|
||||||
|
$filterText[] = "Casa: " . ($house['number'] ?? 'N/A');
|
||||||
|
}
|
||||||
|
?>
|
||||||
|
<div class="card mb-4">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title mb-0">
|
||||||
|
<i class="bi bi-funnel"></i> Filtros de Deudores de Luz
|
||||||
|
<?php if ($hasFilters): ?>
|
||||||
|
<span class="badge bg-warning text-dark ms-2"><?= implode(' | ', $filterText) ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</h5>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-secondary" data-bs-toggle="collapse" data-bs-target="#filtersCollapse">
|
||||||
|
<i class="bi bi-chevron-down"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="collapse <?php echo $hasFilters ? '' : 'show'; ?>" id="filtersCollapse">
|
||||||
|
<div class="card-body">
|
||||||
|
<form id="electricityDebtorsFilter">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Año</label>
|
||||||
|
<select name="filter_year" class="form-select">
|
||||||
|
<option value="">Todos los años</option>
|
||||||
|
<?php for ($y = 2024; $y <= date('Y') + 1; $y++): ?>
|
||||||
|
<option value="<?= $y ?>" <?= ($_GET['filter_year'] ?? '') == $y ? 'selected' : '' ?>><?= $y ?></option>
|
||||||
|
<?php endfor; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3">
|
||||||
|
<label class="form-label">Casa</label>
|
||||||
|
<select name="filter_house" class="form-select">
|
||||||
|
<option value="">Todas las casas</option>
|
||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../models/House.php';
|
||||||
|
$allHouses = House::getAccessible();
|
||||||
|
foreach ($allHouses as $h): ?>
|
||||||
|
<option value="<?= $h['id'] ?>" <?= ($_GET['filter_house'] ?? '') == $h['id'] ? 'selected' : '' ?>><?= $h['number'] ?> - <?= htmlspecialchars($h['owner_name'] ?? '') ?></option>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Periodos</label>
|
||||||
|
<div class="d-flex flex-wrap gap-2">
|
||||||
|
<?php
|
||||||
|
require_once __DIR__ . '/../../models/ElectricityBill.php';
|
||||||
|
$allPeriods = ElectricityBill::getPeriods();
|
||||||
|
$selectedPeriods = explode(',', $_GET['filter_periods'] ?? '');
|
||||||
|
foreach ($allPeriods as $p): ?>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" name="filter_periods[]" value="<?= $p ?>"
|
||||||
|
class="form-check-input period-checkbox"
|
||||||
|
id="period_<?= str_replace(' ', '', $p) ?>"
|
||||||
|
<?= in_array($p, $selectedPeriods) ? 'checked' : '' ?>>
|
||||||
|
<label class="form-check-label" for="period_<?= str_replace(' ', '', $p) ?>"><?= $p ?></label>
|
||||||
|
</div>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col-12">
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="bi bi-search"></i> Aplicar Filtros
|
||||||
|
</button>
|
||||||
|
<a href="/dashboard.php?page=reportes&type=electricity-debtors" class="btn btn-outline-secondary">
|
||||||
|
<i class="bi bi-x-circle"></i> Limpiar Filtros
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
document.getElementById('electricityDebtorsFilter').addEventListener('submit', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const formData = new FormData(this);
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
if (formData.get('filter_year')) {
|
||||||
|
params.append('filter_year', formData.get('filter_year'));
|
||||||
|
}
|
||||||
|
if (formData.get('filter_house')) {
|
||||||
|
params.append('filter_house', formData.get('filter_house'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedPeriods = formData.getAll('filter_periods[]');
|
||||||
|
if (selectedPeriods.length > 0) {
|
||||||
|
params.append('filter_periods', selectedPeriods.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.location.href = '/dashboard.php?page=reportes&type=electricity-debtors&' + params.toString();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="row g-4 mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-warning">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="text-muted">Total Adeudado (Luz)</h6>
|
||||||
|
<h3 class="text-warning">$<?= number_format($electricityDebtors['total_due'], 2) ?></h3>
|
||||||
|
<small class="text-muted">Total general de deudas</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-info">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="text-muted">Total Esperado</h6>
|
||||||
|
<h3 class="text-info">$<?= number_format($electricityDebtors['total_expected'], 2) ?></h3>
|
||||||
|
<small class="text-muted">Total configurado</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="card border-success">
|
||||||
|
<div class="card-body">
|
||||||
|
<h6 class="text-muted">Total Pagado</h6>
|
||||||
|
<h3 class="text-success">$<?= number_format($electricityDebtors['total_paid'], 2) ?></h3>
|
||||||
|
<small class="text-muted">Total recaudado</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<h5 class="card-title mb-0"><i class="bi bi-exclamation-triangle"></i> Deudores de Luz - Cámara</h5>
|
||||||
|
<button onclick="exportElectricityDebtorsPDF()" class="btn btn-outline-warning btn-sm">
|
||||||
|
<i class="bi bi-file-earmark-pdf"></i> Exportar PDF
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<?php if (empty($electricityDebtors['debtors'])): ?>
|
||||||
|
<p class="text-muted">No hay deudores registrados</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm table-bordered">
|
||||||
|
<thead class="table-warning">
|
||||||
|
<tr>
|
||||||
|
<th>Casa</th>
|
||||||
|
<th>Propietario</th>
|
||||||
|
<th>Periodos Adeudados</th>
|
||||||
|
<th>Total Debe</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($electricityDebtors['debtors'] as $debtor): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong><?= $debtor['house_number'] ?></strong></td>
|
||||||
|
<td><?= htmlspecialchars($debtor['owner_name'] ?? '-') ?></td>
|
||||||
|
<td>
|
||||||
|
<table class="table table-sm mb-0">
|
||||||
|
<?php foreach ($debtor['periods_due'] as $period): ?>
|
||||||
|
<tr>
|
||||||
|
<td><?= $period['year'] ?> - <?= $period['period'] ?></td>
|
||||||
|
<td class="text-end">$<?= number_format($period['due'], 2) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td class="text-end fw-bold text-danger">$<?= number_format($debtor['total_due'], 2) ?></td>
|
||||||
|
</tr>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th colspan="3" class="text-end">TOTAL GENERAL:</th>
|
||||||
|
<th class="text-end">$<?= number_format($electricityDebtors['total_due'], 2) ?></th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function exportElectricityDebtorsPDF() {
|
||||||
|
const form = document.getElementById('electricityDebtorsFilter');
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
params.append('action', 'export_pdf_report');
|
||||||
|
params.append('type', 'electricity-debtors');
|
||||||
|
|
||||||
|
if (form) {
|
||||||
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
if (formData.get('filter_year')) {
|
||||||
|
params.append('filter_year', formData.get('filter_year'));
|
||||||
|
}
|
||||||
|
if (formData.get('filter_house')) {
|
||||||
|
params.append('filter_house', formData.get('filter_house'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedPeriods = formData.getAll('filter_periods[]');
|
||||||
|
if (selectedPeriods.length > 0) {
|
||||||
|
params.append('filter_periods', selectedPeriods.join(','));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Fallback si no hay formulario (por ejemplo si se ocultó), usar parámetros de URL actual
|
||||||
|
const currentParams = new URLSearchParams(window.location.search);
|
||||||
|
if (currentParams.get('filter_year')) params.append('filter_year', currentParams.get('filter_year'));
|
||||||
|
if (currentParams.get('filter_house')) params.append('filter_house', currentParams.get('filter_house'));
|
||||||
|
if (currentParams.get('filter_periods')) params.append('filter_periods', currentParams.get('filter_periods'));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open('/dashboard.php?page=reportes_actions&' + params.toString(), '_blank');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
|
|||||||
123
views/reports/pdf_electricity_debtors.php
Executable file
123
views/reports/pdf_electricity_debtors.php
Executable file
@@ -0,0 +1,123 @@
|
|||||||
|
<style>
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 8pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #000;
|
||||||
|
padding: 4px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14pt;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-date {
|
||||||
|
text-align: right;
|
||||||
|
font-size: 8pt;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-warning {
|
||||||
|
color: orange;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="print-title">Condominio IBIZA-Cto Sierra Morena 152 - Reporte de Deudores de Luz - Energía Cámara</div>
|
||||||
|
<div class="print-date">Fecha de generación:
|
||||||
|
<?= date('d/m/Y H:i')?>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$hasFilters = !empty($electricityDebtors['filters']['year']) || !empty($electricityDebtors['filters']['periods']) || !empty($electricityDebtors['filters']['house_id']);
|
||||||
|
$filterText = [];
|
||||||
|
if (!empty($electricityDebtors['filters']['year'])) {
|
||||||
|
$filterText[] = "Año: " . $electricityDebtors['filters']['year'];
|
||||||
|
}
|
||||||
|
if (!empty($electricityDebtors['filters']['periods'])) {
|
||||||
|
$filterText[] = "Periodos: " . implode(', ', $electricityDebtors['filters']['periods']);
|
||||||
|
}
|
||||||
|
if (!empty($electricityDebtors['filters']['house_id'])) {
|
||||||
|
require_once __DIR__ . '/../../models/House.php';
|
||||||
|
$house = House::findById($electricityDebtors['filters']['house_id']);
|
||||||
|
$filterText[] = "Casa: " . ($house['number'] ?? 'N/A');
|
||||||
|
}
|
||||||
|
if ($hasFilters):
|
||||||
|
?>
|
||||||
|
<div style="font-size: 9pt; margin-bottom: 10px;">
|
||||||
|
<strong>Filtros aplicados:</strong>
|
||||||
|
<?= implode(' | ', $filterText)?>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
|
|
||||||
|
<?php if (empty($electricityDebtors['debtors'])): ?>
|
||||||
|
<p>No hay deudores de luz registrados con los filtros actuales.</p>
|
||||||
|
<?php
|
||||||
|
else: ?>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr style="background-color: #ffc107;">
|
||||||
|
<th>Casa</th>
|
||||||
|
<th>Propietario</th>
|
||||||
|
<th>Periodos Adeudados</th>
|
||||||
|
<th>Total Debe</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<?php foreach ($electricityDebtors['debtors'] as $debtor): ?>
|
||||||
|
<tr>
|
||||||
|
<td><strong>
|
||||||
|
<?= $debtor['house_number']?>
|
||||||
|
</strong></td>
|
||||||
|
<td>
|
||||||
|
<?= htmlspecialchars($debtor['owner_name'] ?? '-')?>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<table style="width:100%; border: none;">
|
||||||
|
<?php foreach ($debtor['periods_due'] as $period): ?>
|
||||||
|
<tr>
|
||||||
|
<td style="border: none; text-align: left;">
|
||||||
|
<?= $period['year']?> -
|
||||||
|
<?= $period['period']?>
|
||||||
|
</td>
|
||||||
|
<td style="border: none; text-align: right;">$
|
||||||
|
<?= number_format($period['due'], 2)?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
<td class="text-end fw-bold text-danger">$
|
||||||
|
<?= number_format($debtor['total_due'], 2)?>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<?php
|
||||||
|
endforeach; ?>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr style="background-color: #343a40; color: #fff;">
|
||||||
|
<th colspan="3" style="text-align: right;">TOTAL GENERAL:</th>
|
||||||
|
<th style="text-align: right;">$
|
||||||
|
<?= number_format($electricityDebtors['total_due'], 2)?>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
<?php
|
||||||
|
endif; ?>
|
||||||
Reference in New Issue
Block a user