- Filtros por casas: selección múltiple con opción 'Todas las casas' - Filtros por conceptos: selección múltiple con opción 'Todos los conceptos' - Estado inicial: todos los filtros marcados por defecto (muestra toda la info) - Exportación PDF: incluye solo datos filtrados según selección - JavaScript interactivo: lógica de checkboxes con estados intermedios - Modelo actualizado: método getConceptDebtorsFiltered para filtrado avanzado - Interfaz intuitiva: scrollable containers para listas largas - Preserva permisos: respeta restricciones de acceso por casas
1431 lines
68 KiB
PHP
Executable File
1431 lines
68 KiB
PHP
Executable File
<?php
|
|
session_start();
|
|
|
|
require_once __DIR__ . '/config/config.php';
|
|
require_once __DIR__ . '/core/Database.php';
|
|
require_once __DIR__ . '/core/Auth.php';
|
|
|
|
Auth::requireAuth();
|
|
|
|
$year = $_GET['year'] ?? date('Y');
|
|
$page = $_GET['page'] ?? 'dashboard';
|
|
|
|
require_once __DIR__ . '/models/House.php';
|
|
require_once __DIR__ . '/models/Payment.php';
|
|
require_once __DIR__ . '/models/Expense.php';
|
|
require_once __DIR__ . '/models/CollectionConcept.php';
|
|
require_once __DIR__ . '/models/Report.php';
|
|
require_once __DIR__ . '/models/ActivityLog.php';
|
|
require_once __DIR__ . '/models/MonthlyBill.php';
|
|
|
|
switch ($page) {
|
|
case 'dashboard':
|
|
$accessibleHouseIds = Auth::getAccessibleHouseIds();
|
|
$stats = Report::getDashboardStats($year, $accessibleHouseIds);
|
|
$distinctUsers = ActivityLog::getDistinctUsers();
|
|
|
|
$currentUserId = Auth::id();
|
|
if (Auth::isAdmin()) {
|
|
if (isset($_GET['user']) && $_GET['user'] != '') {
|
|
$recentActivity = ActivityLog::getByUser((int)$_GET['user'], 100);
|
|
} else {
|
|
$recentActivity = ActivityLog::all(15);
|
|
}
|
|
} elseif (Auth::isCapturist()) {
|
|
$recentActivity = ActivityLog::getByUser($currentUserId, 15);
|
|
}
|
|
// Si no es admin ni capturista, no se carga la actividad reciente
|
|
|
|
$view = 'dashboard/index';
|
|
break;
|
|
case 'search_global':
|
|
header('Content-Type: application/json');
|
|
if (!isset($_GET['q'])) {
|
|
echo json_encode(['success' => false, 'message' => 'Query requerido']);
|
|
exit;
|
|
}
|
|
$query = $_GET['q'];
|
|
$results = House::search($query);
|
|
echo json_encode(['success' => true, 'results' => $results]);
|
|
exit;
|
|
break;
|
|
|
|
case 'activity_logs':
|
|
header('Content-Type: application/json');
|
|
Auth::requireAdmin();
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] == 'clear') {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'DELETE') {
|
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
|
exit;
|
|
}
|
|
ActivityLog::deleteAll();
|
|
Auth::logActivity('clear_history', 'Historial de actividad eliminado');
|
|
echo json_encode(['success' => true, 'message' => 'Historial eliminado']);
|
|
exit;
|
|
}
|
|
break;
|
|
|
|
case 'house_actions':
|
|
header('Content-Type: application/json');
|
|
Auth::requireAdmin();
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] == 'save') {
|
|
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['id'])) {
|
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos']);
|
|
exit;
|
|
}
|
|
|
|
if (House::update($input['id'], $input)) {
|
|
$house = House::findById($input['id']);
|
|
Auth::logActivity('update_house', "Casa {$house['number']} actualizada");
|
|
echo json_encode(['success' => true, 'message' => 'Casa actualizada']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al actualizar']);
|
|
}
|
|
exit;
|
|
}
|
|
break;
|
|
|
|
case 'config_actions':
|
|
header('Content-Type: application/json');
|
|
// Permitir acceso a capturistas para ver gráficos
|
|
if (!Auth::isCapturist()) {
|
|
echo json_encode(['success' => false, 'message' => 'Acceso denegado']);
|
|
exit;
|
|
}
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] == 'get_monthly_data') {
|
|
$year = $_GET['year'] ?? date('Y');
|
|
$monthlyBills = MonthlyBill::getYear($year);
|
|
|
|
$configured = [];
|
|
$realAmounts = [];
|
|
|
|
$months = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio',
|
|
'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
|
|
|
|
foreach ($months as $month) {
|
|
$bill = $monthlyBills[$month] ?? null;
|
|
$configured[$month] = $bill['total_amount'] ?? 0;
|
|
$realAmounts[$month] = $bill['real_amount'] ?? 0;
|
|
}
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'configured' => $configured,
|
|
'realAmounts' => $realAmounts
|
|
]);
|
|
exit;
|
|
|
|
} elseif (isset($_GET['action']) && $_GET['action'] == 'get_payments_data') {
|
|
$year = $_GET['year'] ?? date('Y');
|
|
$matrix = Payment::getMatrix($year);
|
|
$months = $matrix['months'];
|
|
|
|
$monthTotals = [];
|
|
foreach ($months as $month) {
|
|
$monthTotals[$month] = 0;
|
|
foreach ($matrix['payments'][$month] ?? [] as $houseId => $paymentData) {
|
|
$monthTotals[$month] += $paymentData['amount'] ?? 0;
|
|
}
|
|
}
|
|
|
|
echo json_encode([
|
|
'success' => true,
|
|
'payments' => $monthTotals,
|
|
'months' => $months
|
|
]);
|
|
exit;
|
|
}
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] == 'save_monthly_bill') {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
|
exit;
|
|
}
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$id = MonthlyBill::save($input);
|
|
if ($id) {
|
|
// Actualizar montos esperados si aplica logicamente (MonthlyBill::updatePayments no returna nada util pero ejecuta logica)
|
|
MonthlyBill::updatePayments($input['year'], $input['month']);
|
|
Auth::logActivity('update_config', "Configuración actualizada: {$input['month']} {$input['year']}");
|
|
echo json_encode(['success' => true, 'message' => 'Configuración guardada']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al guardar']);
|
|
}
|
|
exit;
|
|
}
|
|
break;
|
|
|
|
case 'import_actions':
|
|
header('Content-Type: application/json');
|
|
Auth::requireAdmin();
|
|
|
|
if (isset($_GET['action'])) {
|
|
if ($_GET['action'] == 'history') {
|
|
$logs = ActivityLog::getByAction('import_data', 20);
|
|
echo json_encode(['success' => true, 'data' => $logs]);
|
|
exit;
|
|
}
|
|
|
|
if ($_GET['action'] == 'import') {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
|
exit;
|
|
}
|
|
|
|
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
|
|
echo json_encode(['success' => false, 'message' => 'Error al subir archivo']);
|
|
exit;
|
|
}
|
|
|
|
$type = $_POST['type'] ?? '';
|
|
$file = $_FILES['file']['tmp_name'];
|
|
$handle = fopen($file, "r");
|
|
if ($handle === FALSE) {
|
|
echo json_encode(['success' => false, 'message' => 'No se pudo abrir el archivo']);
|
|
exit;
|
|
}
|
|
|
|
$header = fgetcsv($handle, 1000, ",");
|
|
$count = 0;
|
|
$errors = 0;
|
|
|
|
try {
|
|
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
|
|
if (count($data) < 2) continue; // Skip empty lines
|
|
|
|
// Map CSV columns based on type
|
|
// This logic should be expanded based on CSV structure for each type
|
|
// For now, implementing basic House import as example and placeholder for others
|
|
// Real implementation would need specific CSV mapping logic here
|
|
|
|
// Since full implementation of all importers inside this switch might be huge,
|
|
// we'd typically delegation to a model method, but for now we follow the pattern.
|
|
|
|
$success = false;
|
|
if ($type == 'houses') {
|
|
// number,status,consumption_only,owner_name,owner_email,owner_phone
|
|
// Check if house exists
|
|
$number = $data[0];
|
|
$house = House::findByNumber($number);
|
|
if ($house) {
|
|
House::update($house['id'], [
|
|
'status' => $data[1],
|
|
'consumption_only' => $data[2],
|
|
'owner_name' => $data[3],
|
|
'owner_email' => $data[4],
|
|
'owner_phone' => $data[5]
|
|
]);
|
|
$success = true;
|
|
}
|
|
} elseif ($type == 'payments') {
|
|
// year,house_number,month,amount,payment_date,payment_method,notes
|
|
// Simplified logic
|
|
$year = $data[0];
|
|
$houseNumber = $data[1];
|
|
$month = $data[2];
|
|
$amount = (float)$data[3];
|
|
$notes = $data[6] ?? '';
|
|
|
|
$house = House::findByNumber($houseNumber);
|
|
if ($house) {
|
|
Payment::update($house['id'], $year, $month, $amount, Auth::id(), $notes);
|
|
$success = true;
|
|
}
|
|
}
|
|
|
|
if ($success) $count++; else $errors++;
|
|
}
|
|
fclose($handle);
|
|
|
|
Auth::logActivity('import_data', "Importación $type: $count registros procesados");
|
|
echo json_encode(['success' => true, 'message' => "Importación completada. $count registros exitosos."]);
|
|
} catch (Exception $e) {
|
|
echo json_encode(['success' => false, 'message' => 'Error procesando CSV: ' . $e->getMessage()]);
|
|
}
|
|
exit;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'pagos_actions':
|
|
header('Content-Type: application/json');
|
|
Auth::requireCapturist(); // Admin o Capturista
|
|
$userId = Auth::id();
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] == 'save_batch') {
|
|
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 || empty($input['changes'])) {
|
|
echo json_encode(['success' => false, 'message' => 'No hay cambios para guardar']);
|
|
exit;
|
|
}
|
|
|
|
$count = 0;
|
|
|
|
foreach ($input['changes'] as $change) {
|
|
// Validar datos mínimos
|
|
if (!isset($change['house_id'], $change['year'], $change['month'])) {
|
|
continue;
|
|
}
|
|
|
|
$amount = isset($change['amount']) ? (float)$change['amount'] : 0;
|
|
|
|
// Usar el modelo Payment para actualizar
|
|
Payment::update(
|
|
$change['house_id'],
|
|
$change['year'],
|
|
$change['month'],
|
|
$amount,
|
|
$userId
|
|
);
|
|
|
|
// Registrar actividad individual por cada cambio
|
|
$details = "Pago actualizado: Casa {$change['house_number']} - {$change['month']} {$change['year']} - $" . number_format($amount, 2);
|
|
Auth::logActivity('save_payment', $details);
|
|
|
|
$count++;
|
|
}
|
|
|
|
if ($count > 0) {
|
|
echo json_encode(['success' => true, 'message' => "Se guardaron $count cambios exitosamente."]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'No se procesó ningún cambio válido.']);
|
|
}
|
|
exit;
|
|
}
|
|
echo json_encode(['success' => false, 'message' => 'Acción no válida']);
|
|
exit;
|
|
break;
|
|
|
|
case 'pagos':
|
|
$matrix = Payment::getMatrix($year);
|
|
$monthlyBills = MonthlyBill::getYear($year);
|
|
$accessibleHouseIds = Auth::getAccessibleHouseIds();
|
|
|
|
$houses = array_filter($matrix['houses'], function($h) use ($accessibleHouseIds) {
|
|
return in_array($h['id'], $accessibleHouseIds);
|
|
});
|
|
|
|
$payments = [];
|
|
foreach ($matrix['payments'] as $month => $monthPayments) {
|
|
$payments[$month] = [];
|
|
foreach ($monthPayments as $houseId => $paymentData) {
|
|
if (in_array($houseId, $accessibleHouseIds)) {
|
|
$payments[$month][$houseId] = $paymentData;
|
|
}
|
|
}
|
|
}
|
|
|
|
$months = $matrix['months'];
|
|
|
|
if (isset($_GET['action']) && $_GET['action'] == 'export_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 de Agua ' . $year);
|
|
$pdf->SetSubject('Pagos de Agua');
|
|
|
|
$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, 'Condominio IBIZA-Cto Sierra Morena 152 - Reporte de Pagos de Agua ' . $year, 'Generado el ' . date('d/m/Y H:i'));
|
|
|
|
$pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
|
|
$pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
|
|
|
|
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
|
|
|
|
$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
|
|
$pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
|
|
$pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
|
|
|
|
$pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
|
|
|
|
$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
|
|
|
|
if (@file_exists(dirname(__FILE__).'/lang/eng.php')) {
|
|
require_once(dirname(__FILE__).'/lang/eng.php');
|
|
$pdf->setLanguageArray($l);
|
|
}
|
|
|
|
$pdf->SetFont('helvetica', '', 9);
|
|
$pdf->AddPage();
|
|
|
|
$selectedMonths = $_GET['months'] ?? [];
|
|
if (!empty($selectedMonths)) {
|
|
$filteredMonths = [];
|
|
foreach($months as $m) {
|
|
if (in_array($m, $selectedMonths)) {
|
|
$filteredMonths[] = $m;
|
|
}
|
|
}
|
|
$months = $filteredMonths;
|
|
}
|
|
|
|
ob_start();
|
|
include __DIR__ . '/views/payments/pdf_template.php';
|
|
$html = ob_get_clean();
|
|
|
|
$pdf->writeHTML($html, true, false, true, false, '');
|
|
|
|
$monthCount = count($months);
|
|
$allMonths = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
|
|
|
|
if ($monthCount == 12) {
|
|
$filename = 'Pagos_IBIZA_' . $year . '.pdf';
|
|
} elseif ($monthCount == 1) {
|
|
$filename = 'Pagos_IBIZA_' . $months[0] . '_' . $year . '.pdf';
|
|
} else {
|
|
$monthNames = implode('_', $months);
|
|
$filename = 'Pagos_IBIZA_' . $monthNames . '_' . $year . '.pdf';
|
|
}
|
|
|
|
$pdf->Output($filename, 'D');
|
|
exit;
|
|
}
|
|
|
|
$view = 'payments/index';
|
|
break;
|
|
case 'casas':
|
|
$houses = House::getAccessible();
|
|
$view = 'houses/index';
|
|
break;
|
|
case 'house_view':
|
|
$houseId = $_GET['id'] ?? 0;
|
|
$accessibleHouseIds = Auth::getAccessibleHouseIds();
|
|
|
|
if (!in_array($houseId, $accessibleHouseIds) && !Auth::isAdmin()) {
|
|
header('Location: /dashboard.php?page=casas');
|
|
exit;
|
|
}
|
|
|
|
$house = House::findById($houseId);
|
|
$waterPayments = Payment::getByHouse($houseId);
|
|
$totalWaterPayments = array_sum(array_column($waterPayments, 'amount'));
|
|
$conceptPayments = CollectionPayment::getByHouse($houseId);
|
|
$totalConceptPayments = array_sum(array_column($conceptPayments, 'amount'));
|
|
$activityLogs = ActivityLog::all(50);
|
|
$view = 'houses/view';
|
|
break;
|
|
case 'finanzas':
|
|
// Determinar si es una solicitud de "API" (GET para datos, POST para guardar/eliminar)
|
|
if (isset($_GET['action'])) {
|
|
header('Content-Type: application/json');
|
|
$userId = Auth::id(); // Obtener el ID del usuario actual
|
|
|
|
switch ($_GET['action']) {
|
|
// Conceptos
|
|
case 'get_concept':
|
|
$conceptId = $_GET['id'] ?? 0;
|
|
$concept = CollectionConcept::findById($conceptId);
|
|
if ($concept) {
|
|
echo json_encode(['success' => true, 'data' => $concept]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Concepto no encontrado']);
|
|
}
|
|
exit;
|
|
|
|
case 'save_concept':
|
|
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) {
|
|
// Validaciones básicas
|
|
if (empty($input['name']) || empty($input['amount_per_house']) || empty($input['concept_date'])) {
|
|
echo json_encode(['success' => false, 'message' => 'Faltan campos requeridos: nombre, monto por casa, fecha.']);
|
|
exit;
|
|
}
|
|
if (!is_numeric($input['amount_per_house']) || $input['amount_per_house'] <= 0) {
|
|
echo json_encode(['success' => false, 'message' => 'Monto por casa debe ser un número positivo.']);
|
|
exit;
|
|
}
|
|
if (isset($input['total_amount']) && !is_numeric($input['total_amount'])) {
|
|
echo json_encode(['success' => false, 'message' => 'Monto total debe ser un número.']);
|
|
exit;
|
|
}
|
|
|
|
$result = CollectionConcept::save($input, $userId);
|
|
if ($result) {
|
|
Auth::logActivity('save_concept', 'Concepto ' . ($input['id'] ? 'editado' : 'creado') . ': ' . $input['name']);
|
|
echo json_encode(['success' => true, 'message' => 'Concepto guardado exitosamente', 'id' => $result]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al guardar concepto']);
|
|
}
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos']);
|
|
}
|
|
exit;
|
|
|
|
case 'delete_concept':
|
|
if (!Auth::isAdmin()) {
|
|
echo json_encode(['success' => false, 'message' => 'Permiso denegado: solo administradores pueden eliminar conceptos.']);
|
|
exit;
|
|
}
|
|
$conceptId = $_GET['id'] ?? 0;
|
|
if (!$conceptId) {
|
|
echo json_encode(['success' => false, 'message' => 'ID de concepto no proporcionado']);
|
|
exit;
|
|
}
|
|
$result = CollectionConcept::delete($conceptId);
|
|
if ($result) {
|
|
Auth::logActivity('delete_concept', 'Concepto eliminado: ID ' . $conceptId);
|
|
echo json_encode(['success' => true, 'message' => 'Concepto eliminado exitosamente']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al eliminar concepto']);
|
|
}
|
|
exit;
|
|
|
|
// Gastos
|
|
case 'get_expense':
|
|
$expenseId = $_GET['id'] ?? 0;
|
|
$expense = Expense::findById($expenseId);
|
|
if ($expense) {
|
|
$allocations = Expense::getConcepts($expenseId); // Obtener asignaciones
|
|
$expense['allocations'] = $allocations; // Añadir asignaciones al gasto
|
|
echo json_encode(['success' => true, 'data' => $expense]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Gasto no encontrado']);
|
|
}
|
|
exit;
|
|
|
|
case 'save_expense':
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
echo json_encode(['success' => false, 'message' => 'Método no permitido']);
|
|
exit;
|
|
}
|
|
|
|
// Datos del formulario
|
|
$data = $_POST;
|
|
$receiptFile = $_FILES['receipt'] ?? null;
|
|
$allocations = json_decode($data['allocations'] ?? '[]', true);
|
|
|
|
// Validaciones básicas
|
|
if (empty($data['description']) || empty($data['amount']) || empty($data['expense_date'])) {
|
|
echo json_encode(['success' => false, 'message' => 'Faltan campos requeridos: descripción, monto, fecha.']);
|
|
exit;
|
|
}
|
|
if (!is_numeric($data['amount']) || $data['amount'] <= 0) {
|
|
echo json_encode(['success' => false, 'message' => 'Monto debe ser un número positivo.']);
|
|
exit;
|
|
}
|
|
|
|
$result = Expense::save($data, $userId, $receiptFile, $allocations);
|
|
if ($result) {
|
|
Auth::logActivity('save_expense', 'Gasto ' . ($data['id'] ? 'editado' : 'creado') . ': ' . $data['description']);
|
|
echo json_encode(['success' => true, 'message' => 'Gasto guardado exitosamente', 'id' => $result]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al guardar gasto. Verifique los logs.']);
|
|
}
|
|
exit;
|
|
|
|
case 'delete_expense':
|
|
if (!Auth::isAdmin()) {
|
|
echo json_encode(['success' => false, 'message' => 'Permiso denegado: solo administradores pueden eliminar gastos.']);
|
|
exit;
|
|
}
|
|
$expenseId = $_GET['id'] ?? 0;
|
|
if (!$expenseId) {
|
|
echo json_encode(['success' => false, 'message' => 'ID de gasto no proporcionado']);
|
|
exit;
|
|
}
|
|
$result = Expense::delete($expenseId);
|
|
if ($result) {
|
|
Auth::logActivity('delete_expense', 'Gasto eliminado: ID ' . $expenseId);
|
|
echo json_encode(['success' => true, 'message' => 'Gasto eliminado exitosamente']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al eliminar gasto']);
|
|
}
|
|
exit;
|
|
|
|
default:
|
|
echo json_encode(['success' => false, 'message' => 'Acción de finanzas no válida']);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
// Lógica original para mostrar la vista (si no es una acción de "API")
|
|
$expenses = Expense::all();
|
|
$concepts = CollectionConcept::all(true);
|
|
$view = 'finance/index';
|
|
break;
|
|
case 'concept_view':
|
|
$conceptId = $_GET['id'] ?? 0;
|
|
$concept = CollectionConcept::findById($conceptId);
|
|
|
|
if (!$concept) {
|
|
die('Error: Concepto no encontrado.');
|
|
}
|
|
|
|
$status = CollectionConcept::getCollectionStatus($conceptId);
|
|
$payments = CollectionConcept::getPaymentsByConcept($conceptId);
|
|
$view = 'finance/concept_view';
|
|
break;
|
|
case 'reportes':
|
|
$reportType = $_GET['type'] ?? 'general';
|
|
$balance = Report::getGeneralBalance();
|
|
$expensesByCategory = Report::getExpensesByCategory();
|
|
$accessibleHouseIds = Auth::getAccessibleHouseIds();
|
|
$filterYear = $_GET['filter_year'] ?? null;
|
|
$conceptDetails = Report::getConceptDetailsByYear($filterYear);
|
|
|
|
if ($reportType == 'water-debtors') {
|
|
$filters = [
|
|
'year' => $_GET['filter_year'] ?? null,
|
|
'months' => $_GET['filter_months'] ?? null,
|
|
'house_id' => $_GET['filter_house'] ?? null,
|
|
'accessible_house_ids' => $accessibleHouseIds
|
|
];
|
|
if ($filters['months'] && !is_array($filters['months'])) {
|
|
$filters['months'] = explode(',', $filters['months']);
|
|
}
|
|
$waterDebtors = Report::getWaterDebtors($filters);
|
|
} elseif ($reportType == 'concept-debtors') {
|
|
// Procesar filtros de casas y conceptos
|
|
$houseFilters = $_GET['filter_houses'] ?? ['all'];
|
|
$conceptFilters = $_GET['filter_concepts'] ?? ['all'];
|
|
|
|
// Determinar casas a filtrar
|
|
if (in_array('all', $houseFilters) || empty($houseFilters)) {
|
|
$filteredHouses = $accessibleHouseIds;
|
|
} else {
|
|
$filteredHouses = array_intersect($houseFilters, $accessibleHouseIds);
|
|
}
|
|
|
|
// Determinar conceptos a filtrar
|
|
if (in_array('all', $conceptFilters) || empty($conceptFilters)) {
|
|
$filteredConcepts = null; // Todos los conceptos
|
|
} else {
|
|
$filteredConcepts = $conceptFilters;
|
|
}
|
|
|
|
$conceptDebtors = Report::getConceptDebtorsFiltered($filteredHouses, $filteredConcepts);
|
|
}
|
|
|
|
$view = 'reports/index';
|
|
break;
|
|
case 'configurar':
|
|
Auth::requireAdmin();
|
|
$months = ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'];
|
|
$monthlyBills = MonthlyBill::getYear($year);
|
|
$view = 'configurar/index';
|
|
break;
|
|
case 'usuarios':
|
|
Auth::requireAdmin();
|
|
$view = 'users/index';
|
|
break;
|
|
case 'graficos':
|
|
$year = $_GET['year'] ?? date('Y');
|
|
$matrix = Payment::getMatrix($year);
|
|
$monthlyBills = MonthlyBill::getYear($year);
|
|
$months = $matrix['months'];
|
|
$monthTotals = [];
|
|
|
|
foreach ($months as $month) {
|
|
$monthTotals[$month] = 0;
|
|
foreach ($matrix['payments'][$month] ?? [] as $houseId => $paymentData) {
|
|
$monthTotals[$month] += $paymentData['amount'] ?? 0;
|
|
}
|
|
}
|
|
|
|
$view = 'charts/index';
|
|
break;
|
|
case 'importar':
|
|
Auth::requireAdmin();
|
|
$concepts = CollectionConcept::all(true);
|
|
$view = 'import/index';
|
|
break;
|
|
case 'concept_view_actions': // Nuevo case para acciones AJAX de concept_view
|
|
if (isset($_GET['action'])) {
|
|
header('Content-Type: application/json');
|
|
$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;
|
|
|
|
default:
|
|
echo json_encode(['success' => false, 'message' => 'Acción no válida para la vista de concepto']);
|
|
exit;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'charts_export':
|
|
// Exportación de gráficos a PDF usando TCPDF (igual que otras exportaciones)
|
|
date_default_timezone_set('America/Mexico_City');
|
|
|
|
// Obtener datos del POST
|
|
$input = json_decode(file_get_contents('php://input'), true);
|
|
$year = $input['year'] ?? date('Y');
|
|
$chartImages = $input['charts'] ?? [];
|
|
$chartData = $input['data'] ?? [];
|
|
|
|
require_once __DIR__ . '/vendor/autoload.php';
|
|
require_once __DIR__ . '/vendor/tecnickcom/tcpdf/tcpdf.php';
|
|
|
|
$pdf = new TCPDF(PDF_PAGE_ORIENTATION, PDF_UNIT, PDF_PAGE_FORMAT, true, 'UTF-8', false);
|
|
|
|
$pdf->SetCreator(PDF_CREATOR);
|
|
$pdf->SetAuthor('Ibiza Condominium');
|
|
$pdf->SetTitle('Gráficos de Pagos de Agua - IBIZA ' . $year);
|
|
$pdf->SetSubject('Análisis Gráfico de Pagos');
|
|
|
|
$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, 'Condominio IBIZA-Cto Sierra Morena 152 - Gráficos de Pagos de Agua ' . $year, 'Generado el ' . date('d/m/Y H:i'));
|
|
|
|
$pdf->setHeaderFont(Array(PDF_FONT_NAME_MAIN, '', PDF_FONT_SIZE_MAIN));
|
|
$pdf->setFooterFont(Array(PDF_FONT_NAME_DATA, '', PDF_FONT_SIZE_DATA));
|
|
|
|
$pdf->SetDefaultMonospacedFont(PDF_FONT_MONOSPACED);
|
|
$pdf->SetMargins(PDF_MARGIN_LEFT, PDF_MARGIN_TOP, PDF_MARGIN_RIGHT);
|
|
$pdf->SetHeaderMargin(PDF_MARGIN_HEADER);
|
|
$pdf->SetFooterMargin(PDF_MARGIN_FOOTER);
|
|
$pdf->SetAutoPageBreak(TRUE, PDF_MARGIN_BOTTOM);
|
|
$pdf->setImageScale(PDF_IMAGE_SCALE_RATIO);
|
|
|
|
if (@file_exists(dirname(__FILE__).'/lang/eng.php')) {
|
|
require_once(dirname(__FILE__).'/lang/eng.php');
|
|
$pdf->setLanguageArray($l);
|
|
}
|
|
|
|
$pdf->SetFont('helvetica', '', 12);
|
|
$pdf->AddPage();
|
|
|
|
// Título principal
|
|
$pdf->SetFont('helvetica', 'B', 18);
|
|
$pdf->Cell(0, 15, 'Gráficos de Pagos de Agua - IBIZA', 0, 1, 'C');
|
|
$pdf->SetFont('helvetica', '', 10);
|
|
$pdf->Cell(0, 8, 'Año: ' . $year . ' - Generado: ' . date('d/m/Y H:i'), 0, 1, 'C');
|
|
$pdf->Ln(10);
|
|
|
|
// Función para agregar gráfico
|
|
function addChartToPDF($pdf, $imageData, $title, $description = '') {
|
|
if ($imageData) {
|
|
$pdf->SetFont('helvetica', 'B', 14);
|
|
$pdf->Cell(0, 10, $title, 0, 1, 'L');
|
|
|
|
if ($description) {
|
|
$pdf->SetFont('helvetica', 'I', 10);
|
|
$pdf->Cell(0, 6, $description, 0, 1, 'L');
|
|
}
|
|
|
|
// Agregar imagen del gráfico
|
|
$pdf->Image('@' . base64_decode(preg_replace('#^data:image/[^;]+;base64,#', '', $imageData)), 15, $pdf->GetY(), 180, 80, 'PNG');
|
|
$pdf->Ln(90);
|
|
}
|
|
}
|
|
|
|
// Página 1: Gráficos principales
|
|
$pdf->SetFont('helvetica', 'B', 16);
|
|
$pdf->Cell(0, 12, 'Análisis Comparativo de Pagos', 0, 1, 'L');
|
|
$pdf->Ln(5);
|
|
|
|
if (isset($chartImages['comparisonChart'])) {
|
|
addChartToPDF($pdf, $chartImages['comparisonChart'],
|
|
'1. Comparación: Pagos Reales vs Monto Configurado',
|
|
'Este gráfico compara los pagos efectivamente recibidos con los montos que fueron configurados para cada mes.');
|
|
}
|
|
|
|
if (isset($chartImages['trendsChart'])) {
|
|
addChartToPDF($pdf, $chartImages['trendsChart'],
|
|
'2. Tendencias de Pagos a lo Largo del Año',
|
|
'Visualiza la evolución mensual de los ingresos por concepto de agua.');
|
|
}
|
|
|
|
if (isset($chartImages['realAmountChart'])) {
|
|
addChartToPDF($pdf, $chartImages['realAmountChart'],
|
|
'3. Comparación: Monto Real del Recibo vs Pagos Efectivos',
|
|
'Compara el costo real del servicio con lo que efectivamente se cobra y paga.');
|
|
}
|
|
|
|
// Nueva página para resumen ejecutivo
|
|
$pdf->AddPage();
|
|
$pdf->SetFont('helvetica', 'B', 16);
|
|
$pdf->Cell(0, 12, 'Resumen Ejecutivo', 0, 1, 'L');
|
|
$pdf->Ln(5);
|
|
|
|
if (isset($chartImages['summaryChart'])) {
|
|
addChartToPDF($pdf, $chartImages['summaryChart'],
|
|
'Distribución General de Cobranza',
|
|
'Vista panorámica del estado de cobranza con métricas clave.');
|
|
}
|
|
|
|
// Estadísticas textuales
|
|
$pdf->SetFont('helvetica', 'B', 12);
|
|
$pdf->Cell(0, 10, 'Métricas de Rendimiento', 0, 1, 'L');
|
|
$pdf->Ln(5);
|
|
|
|
$pdf->SetFont('helvetica', '', 11);
|
|
$totalPayments = array_sum($chartData['payments'] ?? []);
|
|
$totalConfigured = array_sum($chartData['configured'] ?? []);
|
|
$totalReal = array_sum($chartData['realAmounts'] ?? []);
|
|
|
|
$pdf->Cell(80, 8, 'Total Pagos Recibidos:', 0, 0, 'L');
|
|
$pdf->Cell(40, 8, '$ ' . number_format($totalPayments, 2), 0, 1, 'R');
|
|
|
|
$pdf->Cell(80, 8, 'Monto Total Configurado:', 0, 0, 'L');
|
|
$pdf->Cell(40, 8, '$ ' . number_format($totalConfigured, 2), 0, 1, 'R');
|
|
|
|
$pdf->Cell(80, 8, 'Monto Real Total:', 0, 0, 'L');
|
|
$pdf->Cell(40, 8, '$ ' . number_format($totalReal, 2), 0, 1, 'R');
|
|
|
|
if ($totalConfigured > 0) {
|
|
$efficiency = round(($totalPayments / $totalConfigured) * 100, 1);
|
|
$pdf->Cell(80, 8, 'Eficiencia de Cobranza:', 0, 0, 'L');
|
|
$pdf->Cell(40, 8, $efficiency . '%', 0, 1, 'R');
|
|
}
|
|
|
|
// Salida del PDF
|
|
$pdf->Output('Graficos_Pagos_Agua_IBIZA_' . $year . '_' . date('Y-m-d') . '.pdf', 'D');
|
|
exit;
|
|
|
|
case 'reportes_actions': // Nuevo case para acciones de exportación de reportes
|
|
date_default_timezone_set('America/Mexico_City'); // Asegurar zona horaria
|
|
|
|
if (isset($_GET['action'])) {
|
|
switch ($_GET['action']) {
|
|
case 'export_pdf_report':
|
|
// Lógica para exportar PDF
|
|
$reportType = $_GET['type'] ?? 'general';
|
|
$year = $_GET['year'] ?? date('Y');
|
|
|
|
// Mapeo de tipos de reporte a nombres en español
|
|
$reportNames = [
|
|
'balance' => 'Balance_General',
|
|
'expenses' => 'Gastos_por_Categoria',
|
|
'water-debtors' => 'Deudores_de_Agua',
|
|
'concept-debtors' => 'Deudores_de_Conceptos',
|
|
'concepts' => 'Conceptos_Especiales'
|
|
];
|
|
$reportName = $reportNames[$reportType] ?? ucfirst($reportType);
|
|
|
|
// Para conceptos, definir nombre personalizado del archivo PDF
|
|
$pdfFilename = $reportName . '_IBIZA_' . $year . '.pdf';
|
|
|
|
// Incluir TCPDF
|
|
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("Condominio IBIZA-Cto Sierra Morena 152 - " . $reportName . " " . $year);
|
|
$pdf->SetSubject($reportName);
|
|
$pdf->SetHeaderData(PDF_HEADER_LOGO, PDF_HEADER_LOGO_WIDTH, 'Condominio IBIZA-Cto Sierra Morena 152 - ' . $reportName . ' ' . $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);
|
|
$pdf->SetFont('helvetica', '', 9);
|
|
$pdf->AddPage();
|
|
|
|
ob_start();
|
|
// Aquí incluiremos templates específicos para cada tipo de reporte PDF
|
|
$accessibleHouseIds = Auth::getAccessibleHouseIds(); // Necesario para algunos reportes
|
|
// Re-obtener los datos del reporte según el type
|
|
switch ($reportType) {
|
|
case 'water-debtors':
|
|
// Necesitamos los filtros para este reporte
|
|
$filters = [
|
|
'year' => $_GET['filter_year'] ?? null,
|
|
'months' => $_GET['filter_months'] ?? null,
|
|
'house_id' => $_GET['filter_house'] ?? null,
|
|
'accessible_house_ids' => $accessibleHouseIds
|
|
];
|
|
if ($filters['months'] && !is_array($filters['months'])) {
|
|
$filters['months'] = explode(',', $filters['months']);
|
|
}
|
|
// Requerimos el modelo Report
|
|
require_once __DIR__ . '/models/Report.php';
|
|
$waterDebtors = Report::getWaterDebtors($filters);
|
|
include __DIR__ . '/views/reports/pdf_water_debtors.php';
|
|
break;
|
|
case 'concept-debtors':
|
|
// Requerimos el modelo Report
|
|
require_once __DIR__ . '/models/Report.php';
|
|
|
|
// Procesar filtros para exportación
|
|
$houseFilters = $_GET['filter_houses'] ?? ['all'];
|
|
$conceptFilters = $_GET['filter_concepts'] ?? ['all'];
|
|
|
|
// Determinar casas a filtrar para exportación
|
|
if (in_array('all', $houseFilters) || empty($houseFilters)) {
|
|
$filteredHouses = $accessibleHouseIds;
|
|
} else {
|
|
$filteredHouses = array_intersect($houseFilters, $accessibleHouseIds);
|
|
}
|
|
|
|
// Determinar conceptos a filtrar para exportación
|
|
if (in_array('all', $conceptFilters) || empty($conceptFilters)) {
|
|
$filteredConcepts = null; // Todos los conceptos
|
|
} else {
|
|
$filteredConcepts = $conceptFilters;
|
|
}
|
|
|
|
$conceptDebtors = Report::getConceptDebtorsFiltered($filteredHouses, $filteredConcepts);
|
|
include __DIR__ . '/views/reports/pdf_concept_debtors.php';
|
|
break;
|
|
case 'expenses':
|
|
// Requerimos el modelo Expense y Report
|
|
require_once __DIR__ . '/models/Expense.php';
|
|
require_once __DIR__ . '/models/Report.php';
|
|
$expenses = Expense::all(); // Assuming all expenses for now
|
|
$expensesByCategory = Report::getExpensesByCategory();
|
|
include __DIR__ . '/views/reports/pdf_expenses.php';
|
|
break;
|
|
case 'concepts':
|
|
// Requerimos los modelos necesarios
|
|
require_once __DIR__ . '/models/CollectionConcept.php';
|
|
require_once __DIR__ . '/models/Expense.php';
|
|
|
|
$filterConceptId = $_GET['filter_concept'] ?? null;
|
|
$conceptsToExport = [];
|
|
|
|
if ($filterConceptId) {
|
|
$concept = CollectionConcept::findById($filterConceptId);
|
|
if ($concept) {
|
|
$status = CollectionConcept::getCollectionStatus($filterConceptId);
|
|
$payments = CollectionConcept::getPaymentsByConcept($filterConceptId);
|
|
$conceptsToExport[] = [
|
|
'concept' => $concept,
|
|
'status' => $status,
|
|
'payments' => $payments
|
|
];
|
|
$conceptName = preg_replace('/[^a-zA-Z0-9_]/', '_', $concept['name']);
|
|
$pdfFilename = 'Concepto_' . $conceptName . '_IBIZA.pdf';
|
|
}
|
|
} else {
|
|
$allConcepts = CollectionConcept::all(true);
|
|
foreach ($allConcepts as $c) {
|
|
$status = CollectionConcept::getCollectionStatus($c['id']);
|
|
$payments = CollectionConcept::getPaymentsByConcept($c['id']);
|
|
$conceptsToExport[] = [
|
|
'concept' => $c,
|
|
'status' => $status,
|
|
'payments' => $payments
|
|
];
|
|
}
|
|
}
|
|
|
|
include __DIR__ . '/views/reports/pdf_concepts.php';
|
|
break;
|
|
case 'balance':
|
|
default:
|
|
// Requerimos el modelo Report
|
|
require_once __DIR__ . '/models/Report.php';
|
|
$balance = Report::getGeneralBalance();
|
|
$filterYear = $_GET['filter_year'] ?? null;
|
|
$conceptDetails = Report::getConceptDetailsByYear($filterYear);
|
|
include __DIR__ . '/views/reports/pdf_balance.php';
|
|
break;
|
|
}
|
|
$html = ob_get_clean();
|
|
$pdf->writeHTML($html, true, false, true, false, '');
|
|
$pdf->Output($pdfFilename, 'D');
|
|
exit;
|
|
|
|
case 'export_csv_balance':
|
|
Auth::requireAdmin();
|
|
require_once __DIR__ . '/models/Report.php';
|
|
|
|
$filterYear = $_GET['filter_year'] ?? null;
|
|
$balance = Report::getGeneralBalance();
|
|
$conceptDetails = Report::getConceptDetailsByYear($filterYear);
|
|
|
|
header('Content-Type: text/csv');
|
|
$filename = 'Balance_General_IBIZA';
|
|
if ($filterYear) {
|
|
$filename .= '_' . $filterYear;
|
|
}
|
|
header('Content-Disposition: attachment; filename="' . $filename . '.csv"');
|
|
fputs(fopen('php://output', 'w'), $bom = (chr(0xEF) . chr(0xBB) . chr(0xBF)));
|
|
|
|
$output = fopen('php://output', 'w');
|
|
fputcsv($output, ['Condominio IBIZA-Cto Sierra Morena 152 - Balance General']);
|
|
if ($filterYear) {
|
|
fputcsv($output, ['Año: ' . $filterYear]);
|
|
}
|
|
fputcsv($output, ['']); // Blank line
|
|
|
|
fputcsv($output, ['Descripción', 'Monto']);
|
|
fputcsv($output, ['Total Ingresos (Conceptos)', $balance['total_incomes']]);
|
|
fputcsv($output, ['Total Egresos', $balance['total_expenses']]);
|
|
fputcsv($output, ['Balance Neto', $balance['balance']]);
|
|
fputcsv($output, ['']); // Blank line
|
|
|
|
if (!empty($conceptDetails['concepts'])) {
|
|
fputcsv($output, ['Detalle de Conceptos Especiales']);
|
|
fputcsv($output, ['']); // Blank line
|
|
fputcsv($output, ['Concepto', 'Descripción', 'Esperado', 'Recaudado', 'Pendiente', 'Gastos', 'Balance']);
|
|
|
|
foreach ($conceptDetails['concepts'] as $cd) {
|
|
fputcsv($output, [
|
|
$cd['concept']['name'],
|
|
$cd['concept']['description'] ?? '-',
|
|
$cd['expected'],
|
|
$cd['collected'],
|
|
$cd['pending'],
|
|
$cd['expenses'],
|
|
$cd['balance']
|
|
]);
|
|
}
|
|
|
|
fputcsv($output, ['']); // Blank line
|
|
fputcsv($output, [
|
|
'TOTALES',
|
|
'',
|
|
$conceptDetails['totals']['expected'],
|
|
$conceptDetails['totals']['collected'],
|
|
$conceptDetails['totals']['pending'],
|
|
$conceptDetails['totals']['expenses'],
|
|
$conceptDetails['totals']['balance']
|
|
]);
|
|
}
|
|
|
|
fclose($output);
|
|
exit;
|
|
|
|
case 'export_csv_expenses':
|
|
// Lógica para exportar CSV de gastos
|
|
header('Content-Type: text/csv');
|
|
header('Content-Disposition: attachment; filename="Gastos_por_Categoria_IBIZA_' . date('Y') . '.csv"');
|
|
fputs(fopen('php://output', 'w'), $bom = (chr(0xEF) . chr(0xBB) . chr(0xBF))); // BOM for UTF-8 in Excel
|
|
|
|
// Requerimos el modelo Expense
|
|
require_once __DIR__ . '/models/Expense.php';
|
|
$expenses = Expense::all(); // Assuming all expenses for now
|
|
|
|
$output = fopen('php://output', 'w');
|
|
fputcsv($output, ['Condominio IBIZA-Cto Sierra Morena 152 - Gastos ' . date('Y')]);
|
|
fputcsv($output, ['']); // Blank line
|
|
|
|
fputcsv($output, ['Fecha', 'Descripción', 'Categoría', 'Monto']);
|
|
foreach ($expenses as $exp) {
|
|
fputcsv($output, [
|
|
date('d/m/Y', strtotime($exp['expense_date'])),
|
|
$exp['description'],
|
|
$exp['category'] ?? '-',
|
|
$exp['amount']
|
|
]);
|
|
}
|
|
fclose($output);
|
|
exit;
|
|
|
|
case 'export_csv_concepts':
|
|
Auth::requireAdmin();
|
|
require_once __DIR__ . '/models/CollectionConcept.php';
|
|
|
|
$filterConceptId = $_GET['filter_concept'] ?? null;
|
|
$conceptsToExport = [];
|
|
|
|
if ($filterConceptId) {
|
|
$concept = CollectionConcept::findById($filterConceptId);
|
|
if ($concept) {
|
|
$status = CollectionConcept::getCollectionStatus($filterConceptId);
|
|
$payments = CollectionConcept::getPaymentsByConcept($filterConceptId);
|
|
$conceptsToExport[] = [
|
|
'concept' => $concept,
|
|
'status' => $status,
|
|
'payments' => $payments
|
|
];
|
|
}
|
|
} else {
|
|
$allConcepts = CollectionConcept::all(true);
|
|
foreach ($allConcepts as $c) {
|
|
$status = CollectionConcept::getCollectionStatus($c['id']);
|
|
$payments = CollectionConcept::getPaymentsByConcept($c['id']);
|
|
$conceptsToExport[] = [
|
|
'concept' => $c,
|
|
'status' => $status,
|
|
'payments' => $payments
|
|
];
|
|
}
|
|
}
|
|
|
|
header('Content-Type: text/csv');
|
|
|
|
if ($filterConceptId && !empty($conceptsToExport)) {
|
|
$conceptName = $conceptsToExport[0]['concept']['name'];
|
|
$conceptName = preg_replace('/[^a-zA-Z0-9_]/', '_', $conceptName);
|
|
$filename = 'Concepto_' . $conceptName . '_IBIZA';
|
|
} else {
|
|
$filename = 'Conceptos_Especiales_IBIZA';
|
|
}
|
|
|
|
header('Content-Disposition: attachment; filename="' . $filename . '.csv"');
|
|
fputs(fopen('php://output', 'w'), $bom = (chr(0xEF) . chr(0xBB) . chr(0xBF)));
|
|
|
|
$output = fopen('php://output', 'w');
|
|
|
|
foreach ($conceptsToExport as $conceptData) {
|
|
$concept = $conceptData['concept'];
|
|
$status = $conceptData['status'];
|
|
$payments = $conceptData['payments'];
|
|
|
|
fputcsv($output, ['Condominio IBIZA-Cto Sierra Morena 152 - Concepto: ' . htmlspecialchars($concept['name'])]);
|
|
fputcsv($output, ['Fecha: ' . date('d/m/Y', strtotime($concept['concept_date']))]);
|
|
if (!empty($concept['description'])) {
|
|
fputcsv($output, ['Descripción: ' . htmlspecialchars($concept['description'])]);
|
|
}
|
|
fputcsv($output, ['']); // Blank line
|
|
fputcsv($output, ['Monto por Casa:', $concept['amount_per_house']]);
|
|
fputcsv($output, ['Total Recaudado:', $status['total_collected']]);
|
|
fputcsv($output, ['Total Esperado:', $status['total_expected']]);
|
|
fputcsv($output, ['Balance:', $status['total_collected']]);
|
|
fputcsv($output, ['']); // Blank line
|
|
fputcsv($output, ['Casa', 'Propietario', 'Monto Pagado', 'Fecha de Pago']);
|
|
|
|
foreach ($payments as $payment) {
|
|
fputcsv($output, [
|
|
$payment['house_number'],
|
|
$payment['owner_name'] ?? '-',
|
|
$payment['amount'] > 0 ? $payment['amount'] : 0,
|
|
$payment['payment_date'] ? date('d/m/Y', strtotime($payment['payment_date'])) : '-'
|
|
]);
|
|
}
|
|
|
|
fputcsv($output, ['', '', 'Total:', $status['total_collected']]);
|
|
fputcsv($output, ['']); // Blank line para separar conceptos
|
|
}
|
|
|
|
fclose($output);
|
|
exit;
|
|
|
|
default:
|
|
// Si no es una acción de exportación conocida, continuar al default
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'users_actions': // Nuevo case para acciones AJAX de gestión de usuarios
|
|
header('Content-Type: application/json');
|
|
$userId = Auth::id(); // Usuario que realiza la acción
|
|
|
|
// Incluir modelos necesarios
|
|
require_once __DIR__ . '/models/User.php';
|
|
require_once __DIR__ . '/models/UserPermission.php';
|
|
require_once __DIR__ . '/models/House.php';
|
|
|
|
switch ($_GET['action']) {
|
|
case 'list':
|
|
Auth::requireAdmin(); // Solo administradores
|
|
$users = User::all();
|
|
echo json_encode(['success' => true, 'users' => $users]);
|
|
exit;
|
|
|
|
case 'get_user_houses':
|
|
Auth::requireAdmin(); // Solo administradores
|
|
$targetUserId = $_GET['user_id'] ?? 0;
|
|
$userHouses = UserPermission::getUserHouseIds($targetUserId);
|
|
echo json_encode(['success' => true, 'houses' => array_map(function($id) { return ['id' => $id]; }, $userHouses)]);
|
|
exit;
|
|
|
|
case 'create':
|
|
Auth::requireAdmin(); // Solo administradores
|
|
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) {
|
|
// Validaciones básicas
|
|
if (empty($input['username']) || empty($input['email']) || empty($input['password']) || empty($input['first_name']) || empty($input['role'])) {
|
|
echo json_encode(['success' => false, 'message' => 'Faltan campos requeridos para crear usuario.']);
|
|
exit;
|
|
}
|
|
$newUserId = User::create($input);
|
|
if ($newUserId) {
|
|
Auth::logActivity('create_user', 'Usuario creado: ' . $input['username']);
|
|
echo json_encode(['success' => true, 'message' => 'Usuario creado exitosamente', 'user_id' => $newUserId]);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al crear usuario.']);
|
|
}
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos.']);
|
|
}
|
|
exit;
|
|
|
|
case 'update':
|
|
Auth::requireAdmin(); // Solo administradores
|
|
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['id'])) {
|
|
// Validaciones básicas
|
|
if (empty($input['username']) || empty($input['email']) || empty($input['first_name']) || empty($input['role'])) {
|
|
echo json_encode(['success' => false, 'message' => 'Faltan campos requeridos para actualizar usuario.']);
|
|
exit;
|
|
}
|
|
$result = User::update($input['id'], $input);
|
|
if ($result) {
|
|
Auth::logActivity('update_user', 'Usuario actualizado: ID ' . $input['id'] . ' - ' . $input['username']);
|
|
echo json_encode(['success' => true, 'message' => 'Usuario actualizado exitosamente']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al actualizar usuario.']);
|
|
}
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos o ID de usuario no proporcionado.']);
|
|
}
|
|
exit;
|
|
|
|
case 'delete':
|
|
Auth::requireAdmin(); // Solo administradores
|
|
$targetUserId = $_GET['id'] ?? 0;
|
|
if (!$targetUserId) {
|
|
echo json_encode(['success' => false, 'message' => 'ID de usuario no proporcionado.']);
|
|
exit;
|
|
}
|
|
// Prevenir que el admin se auto-elimine
|
|
if ($targetUserId == Auth::id()) {
|
|
echo json_encode(['success' => false, 'message' => 'No puedes eliminar tu propia cuenta de administrador.']);
|
|
exit;
|
|
}
|
|
$result = User::delete($targetUserId); // Esto inactiva al usuario
|
|
// También eliminar permisos de casa si los tiene
|
|
UserPermission::assignHousesToUser($targetUserId, []); // Borrar todas las asignaciones
|
|
if ($result) {
|
|
Auth::logActivity('delete_user', 'Usuario eliminado (inactivado): ID ' . $targetUserId);
|
|
echo json_encode(['success' => true, 'message' => 'Usuario eliminado exitosamente (inactivado).']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al eliminar usuario.']);
|
|
}
|
|
exit;
|
|
|
|
case 'assign_houses':
|
|
Auth::requireAdmin(); // Solo administradores
|
|
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['user_id']) && isset($input['house_ids'])) {
|
|
UserPermission::assignHousesToUser($input['user_id'], $input['house_ids']);
|
|
Auth::logActivity('assign_user_houses', 'Casas asignadas a usuario ID: ' . $input['user_id']);
|
|
echo json_encode(['success' => true, 'message' => 'Permisos de casa actualizados.']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos o incompletos para asignar casas.']);
|
|
}
|
|
exit;
|
|
|
|
case 'update_profile':
|
|
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) {
|
|
// Validaciones básicas
|
|
if (empty($input['email']) || empty($input['first_name'])) {
|
|
echo json_encode(['success' => false, 'message' => 'Faltan campos requeridos: email, nombre.']);
|
|
exit;
|
|
}
|
|
$result = User::updateProfile(Auth::id(), $input);
|
|
if ($result) {
|
|
// Actualizar la sesión si la información personal ha cambiado
|
|
$_SESSION['email'] = $input['email'];
|
|
$_SESSION['first_name'] = $input['first_name'];
|
|
$_SESSION['last_name'] = $input['last_name'];
|
|
|
|
Auth::logActivity('update_profile', 'Perfil de usuario actualizado: ID ' . Auth::id());
|
|
echo json_encode(['success' => true, 'message' => 'Perfil actualizado exitosamente.']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al actualizar perfil.']);
|
|
}
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos.']);
|
|
}
|
|
exit;
|
|
|
|
case 'change_password':
|
|
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) {
|
|
$currentPassword = $input['current_password'] ?? '';
|
|
$newPassword = $input['new_password'] ?? '';
|
|
|
|
if (empty($currentPassword) || empty($newPassword)) {
|
|
echo json_encode(['success' => false, 'message' => 'Contraseña actual y nueva son requeridas.']);
|
|
exit;
|
|
}
|
|
if (strlen($newPassword) < 6) {
|
|
echo json_encode(['success' => false, 'message' => 'La nueva contraseña debe tener al menos 6 caracteres.']);
|
|
exit;
|
|
}
|
|
|
|
if (User::verifyPassword(Auth::id(), $currentPassword)) {
|
|
$result = User::changePassword(Auth::id(), $newPassword);
|
|
if ($result) {
|
|
Auth::logActivity('change_password', 'Contraseña de usuario cambiada: ID ' . Auth::id());
|
|
echo json_encode(['success' => true, 'message' => 'Contraseña cambiada exitosamente.']);
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Error al cambiar contraseña.']);
|
|
}
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Contraseña actual incorrecta.']);
|
|
}
|
|
} else {
|
|
echo json_encode(['success' => false, 'message' => 'Datos inválidos.']);
|
|
}
|
|
exit;
|
|
default:
|
|
echo json_encode(['success' => false, 'message' => 'Acción de usuario no válida.']);
|
|
exit;
|
|
}
|
|
break;
|
|
case 'profile':
|
|
// Vista de perfil de usuario
|
|
$view = 'users/profile';
|
|
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;
|
|
|
|
default:
|
|
echo json_encode(['success' => false, 'message' => 'Acción no válida para la vista de concepto']);
|
|
exit;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
$stats = Report::getDashboardStats($year);
|
|
$recentActivity = ActivityLog::all(15);
|
|
$view = 'dashboard/index';
|
|
break;
|
|
}
|
|
|
|
require_once __DIR__ . '/views/layout/base.php';
|