(function(){

    const __pageBindings = [];
    const on = (target, eventName, handler, options) => {
        if (!target || typeof target.addEventListener !== 'function') return;
        target.addEventListener(eventName, handler, options || false);
        __pageBindings.push([target, eventName, handler, options || false]);
    };
    window.ICY_PAGE_CLEANUPS = window.ICY_PAGE_CLEANUPS || [];
    window.ICY_PAGE_CLEANUPS.push(() => {
        __pageBindings.forEach(([target, eventName, handler, options]) => {
            try { target.removeEventListener(eventName, handler, options); } catch (_) {}
        });
    });
    const state = { env: 'production', rows: [], options: {}, selected: new Set(), paymentJustSaved: false, currentId: null, bulkSelection: [], bulkRequestKey: null, paging: window.IcyBillingTable.createPaging('10') };
    const $ = (id) => document.getElementById(id);
    const esc = (value) => String(value == null ? '' : value).replace(/[&<>'"]/g, (char) => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#39;','"':'&quot;'}[char]));
    const money = (value) => '$' + Math.round(Number(value || 0)).toLocaleString('es-CL');
    const currencyMoney = (value, currency) => {
        const code = String(currency || 'CLP').toUpperCase();
        if (code === 'CLP') return money(value);
        return new Intl.NumberFormat('es-CL', {
            style: 'currency', currency: code, minimumFractionDigits: 2, maximumFractionDigits: 2
        }).format(Number(value || 0));
    };
    const dateText = (value) => {
        if (!value) return '-';
        const parts = String(value).slice(0, 10).split('-');
        return parts.length === 3 ? `${parts[2]}-${parts[1]}-${parts[0]}` : String(value);
    };
    const today = () => new Date().toISOString().slice(0,10);
    const refreshIcons = () => { if (window.lucide && typeof window.lucide.createIcons === 'function') window.lucide.createIcons(); };
    const paymentRequestKey = (prefix) => {
        const random = (window.crypto && typeof window.crypto.randomUUID === 'function')
            ? window.crypto.randomUUID()
            : (Date.now().toString(36) + '-' + Math.random().toString(36).slice(2) + '-' + Math.random().toString(36).slice(2));
        return String(prefix || 'payment') + ':' + random;
    };

    function statusLabel(status){
        return ({por_cobrar:'Por cobrar',atrasado:'Atrasado',parcial:'Parcial',pagado:'Pagado',incobrable:'Incobrable',anulado:'Anulado',no_facturado:'No facturado'})[status] || status || 'Por cobrar';
    }
    function statusOf(row){ return row.collection_status || row.payment_status || 'por_cobrar'; }
    function grossBalanceOf(row){ return Number(row.balance_amount != null ? row.balance_amount : row.total_amount || 0); }
    function netBalanceOf(row){ return Number(row.net_balance_amount != null ? row.net_balance_amount : row.net_amount_clp != null ? row.net_amount_clp : row.net_amount || 0); }
    function isCollectable(row){ return Number(row.collection_id || 0) > 0 && grossBalanceOf(row) > 0.5 && !['pagado','incobrable','anulado'].includes(statusOf(row)); }
    function clpAmount(row, field){
        const clpField = `${field}_clp`;
        if (row && row[clpField] != null && row[clpField] !== '') return Number(row[clpField] || 0);
        const amount = Number(row && row[field] != null ? row[field] : 0);
        const currency = String(row && (row.collection_currency_code || row.currency_code) || 'CLP').toUpperCase();
        if (currency === 'CLP') return amount;
        const rate = Number(row && (row.collection_exchange_rate_value || row.exchange_rate_value) || 0);
        return rate > 0 ? amount * rate : 0;
    }
    function receivableBreakdown(row){
        const rawNet = Math.max(0, clpAmount(row, 'net_amount'));
        const total = Math.max(0, Number(row.total_amount_clp != null ? row.total_amount_clp : clpAmount(row, 'total_amount')));
        const exemptDocument = Number(row.is_tax_exempt || 0) === 1
            || String(row.account_document_group || '') === 'invoice_exempt'
            || Number(row.document_type_id || 0) === 32;
        const reportedIva = Math.max(0, clpAmount(row, 'taxes_amount'));
        const exempt = exemptDocument ? (rawNet > 0 ? rawNet : total) : 0;
        const net = exemptDocument ? 0 : rawNet;
        const iva = exemptDocument ? 0 : (reportedIva > 0 ? reportedIva : Math.max(0, total - net));
        const other = Math.max(0, total - net - exempt - iva);
        const financial = Number(row.is_financial_document || 0) === 1;
        const unpaid = financial ? Math.min(total, Math.max(0, grossBalanceOf(row))) : 0;
        const paid = financial ? Math.min(total, Math.max(0, Number(row.paid_amount != null ? row.paid_amount : total - unpaid))) : 0;
        return {net, exempt, iva, other, total, paid, unpaid};
    }

    async function jsonFetch(url, options){
        const response = await fetch(url, Object.assign({headers:{'Accept':'application/json'}}, options || {}));
        const data = await response.json().catch(() => null);
        if (!response.ok || !data || data.success === false) throw new Error(data && data.message ? data.message : 'Error al procesar la solicitud.');
        return data;
    }

    function params(){
        const p = new URLSearchParams();
        state.env = $('receivablesEnv').value || 'production';
        p.set('env', state.env);
        p.set('status', $('receivablesStatus').value || 'open');
        p.set('search', $('receivablesSearch').value || '');
        p.set('date_from', $('receivablesFrom').value || '');
        p.set('date_to', $('receivablesTo').value || '');
        p.set('limit', '1000');
        return p;
    }

    const documentGroups = [
        {key:'invoice_affected',title:'Facturas afectas',description:'Facturas electrónicas emitidas que generan cuenta por cobrar.'},
        {key:'invoice_exempt',title:'Facturas exentas',description:'Facturas no afectas o exentas emitidas que generan cuenta por cobrar.'}
    ];
    function groupKey(row){
        return Number(row.is_tax_exempt || 0) === 1
            || String(row.account_document_group || '') === 'invoice_exempt'
            || Number(row.document_type_id || 0) === 32
            ? 'invoice_exempt'
            : 'invoice_affected';
    }
    function sortValue(row, column){
        const amounts = ['net','exempt','iva','other','total','paid','unpaid'].includes(column) ? receivableBreakdown(row) : null;
        if (amounts) return amounts[column];
        if (column === 'issue_date') return String(row.issue_date || '').slice(0,10);
        if (column === 'folio') return row.document_number || '';
        if (column === 'customer') return row.customer_name || row.receiver_legal_name || '';
        if (column === 'tax_id') return row.customer_tax_id || row.receiver_tax_id_code || '';
        if (column === 'due_date') return String(row.due_date || row.payment_due_date || '').slice(0,10);
        if (column === 'status') return statusLabel(statusOf(row));
        return row[column] == null ? '' : row[column];
    }
    function sortHead(label,type,column,group,className=''){
        const sort = window.IcyBillingTable.sortState(state.paging,group.key);
        const ariaSort = sort && sort.column === column ? sort.direction : 'none';
        return `<th class="${esc(className)}" aria-sort="${esc(ariaSort)}"><button type="button" class="icy-table-sort-button" data-sort-type="${esc(type)}" data-billing-sort-key="${esc(column)}"><span>${esc(label)}</span><span class="icy-table-sort-indicator" aria-hidden="true"></span></button></th>`;
    }
    function receivableActions(row){
        const id = esc(row.id);
        return `<details class="billing-row-actions" data-billing-row-actions data-close-outside>
            <summary aria-label="Acciones de la cuenta por cobrar ${esc(row.document_number || '')}"><i data-lucide="file-text"></i><i data-lucide="chevron-down"></i></summary>
            <div class="billing-row-actions__panel">
                <button type="button" data-receivable-detail="${id}"><i data-lucide="eye"></i>Ver detalle</button>
                <a target="_blank" rel="noopener" href="/modules/billing/receivable_file.php?id=${encodeURIComponent(row.id)}&env=${encodeURIComponent(state.env)}&type=pdf"><i data-lucide="file-down"></i>PDF</a>
                <a target="_blank" rel="noopener" href="/modules/billing/receivable_file.php?id=${encodeURIComponent(row.id)}&env=${encodeURIComponent(state.env)}&type=xml"><i data-lucide="code-xml"></i>XML</a>
                ${isCollectable(row) ? `<button type="button" data-receivable-collect="${id}"><i data-lucide="banknote-arrow-down"></i>Registrar cobro</button>` : ''}
            </div>
        </details>`;
    }
    function receivableTotals(rows){
        return rows.reduce((totals,row)=>{
            const values=receivableBreakdown(row);
            Object.keys(totals).forEach(key=>{totals[key]+=Number(values[key]||0);});
            return totals;
        },{net:0,exempt:0,iva:0,other:0,total:0,paid:0,unpaid:0});
    }
    function rowHtml(row){
        const id=String(row.id),status=statusOf(row),grossBalance=grossBalanceOf(row),netBalance=netBalanceOf(row),collectable=isCollectable(row),checked=state.selected.has(id)?'checked':'';
        const financial=Number(row.is_financial_document||0)===1;
        const statusText=financial?statusLabel(status):'No genera CxC';
        const customerName=row.customer_name||row.receiver_legal_name||'Sin cliente';
        const documentName=row.document_label||row.document_type_taxbureau||row.document_type_id||'DTE';
        const dueDate=financial?(row.due_date||row.payment_due_date||''):'';
        const centerName=financial?(row.finance_center_name||'Sin centro'):'';
        const currencyCode=String(row.collection_currency_code||row.currency_code||'CLP').toUpperCase();
        const isForeign=currencyCode!=='CLP';
        const grossTotalClp=Number(row.collection_total!=null?row.collection_total:(row.total_amount_clp!=null?row.total_amount_clp:row.total_amount||0));
        const netTotalClp=Number(row.collection_net_total!=null?row.collection_net_total:(row.net_amount_clp!=null?row.net_amount_clp:row.net_amount||0));
        const foreignNetTotal=Number(row.foreign_net_total!=null?row.foreign_net_total:row.net_amount||0);
        const totalDisplay=isForeign?currencyMoney(foreignNetTotal,currencyCode):money(netTotalClp);
        const totalContext=financial?`Bruto ${money(grossTotalClp)}`:'Informativo';
        const amounts=receivableBreakdown(row);
        return `<tr><td class="col-select"><input type="checkbox" class="receivable-select" data-receivable-id="${esc(id)}" ${checked} ${collectable?'':'disabled'}></td>
            <td class="col-date crm-mobile-hidden" data-sort-value="${esc(String(row.issue_date||'').slice(0,10))}">${esc(dateText(row.issue_date))}</td>
            <td class="col-document crm-mobile-primary" data-sort-value="${esc(row.document_number||'')}"><div class="crm-ui-cell-stack"><strong>${esc(row.document_number||'-')}</strong><small>${esc(documentName)}</small></div></td>
            <td class="col-counterparty crm-mobile-primary" data-sort-value="${esc(customerName)}"><div class="crm-ui-cell-stack"><strong>${esc(customerName)}</strong><small>${esc(row.customer_tax_id||row.receiver_tax_id_code||'Sin RUT')} · Folio ${esc(row.document_number||'-')}</small></div></td>
            <td class="crm-mobile-hidden" data-sort-value="${esc(row.customer_tax_id||row.receiver_tax_id_code||'')}">${esc(row.customer_tax_id||row.receiver_tax_id_code||'-')}</td>
            <td class="receivables-number crm-mobile-hidden" data-sort-value="${amounts.net}">${money(amounts.net)}</td>
            <td class="receivables-number crm-mobile-hidden" data-sort-value="${amounts.exempt}">${money(amounts.exempt)}</td>
            <td class="receivables-number crm-mobile-hidden" data-sort-value="${amounts.iva}">${money(amounts.iva)}</td>
            <td class="receivables-number crm-mobile-hidden" data-sort-value="${amounts.other}">${money(amounts.other)}</td>
            <td class="col-total receivables-number crm-mobile-hidden" data-sort-value="${amounts.total}"><strong>${money(amounts.total)}</strong></td>
            <td class="receivables-number crm-mobile-hidden" data-sort-value="${amounts.paid}">${money(amounts.paid)}</td>
            <td class="col-balance receivables-number crm-mobile-metric" data-sort-value="${amounts.unpaid}"><strong>${financial?money(amounts.unpaid):'—'}</strong></td>
            <td class="col-due crm-mobile-hidden" data-sort-value="${esc(String(dueDate).slice(0,10))}">${financial?esc(dateText(dueDate)):'—'}</td>
            <td class="col-status crm-mobile-hidden" data-sort-value="${esc(statusText)}"><span class="receivables-status ${financial?esc(status):'no_aplica'}">${esc(statusText)}</span></td>
            <td class="col-action crm-ui-action-cell">${receivableActions(row)}</td></tr>`;
    }
    function groupTable(rows,group){
        const orderedRows=window.IcyBillingTable.sortedRows(rows,state.paging,group.key,sortValue);
        const page=window.IcyBillingTable.pageFor(orderedRows,state.paging,group.key);
        const totals=receivableTotals(orderedRows);
        const body=page.rows.length?page.rows.map(rowHtml).join(''):'<tr class="empty-state-row"><td colspan="15" class="receivables-muted">Sin documentos en esta categoría.</td></tr>';
        const headers=[
            `<th class="col-select"><input type="checkbox" class="receivables-select-all-group" data-group="${esc(group.key)}" aria-label="Seleccionar todos los documentos cobrables de ${esc(group.title)}"></th>`,
            sortHead('Emisión','date','issue_date',group,'col-date crm-mobile-hidden'),
            sortHead('Folio','text','folio',group,'col-document crm-mobile-primary'),
            sortHead('Cliente','text','customer',group,'col-counterparty crm-mobile-primary'),
            sortHead('RUT','text','tax_id',group,'crm-mobile-hidden'),
            sortHead('Neto','number','net',group,'crm-mobile-hidden'),
            sortHead('Exento','number','exempt',group,'crm-mobile-hidden'),
            sortHead('IVA','number','iva',group,'crm-mobile-hidden'),
            sortHead('Otros','number','other',group,'crm-mobile-hidden'),
            sortHead('Total','number','total',group,'col-total crm-mobile-hidden'),
            sortHead('Pagado','number','paid',group,'crm-mobile-hidden'),
            sortHead('Impago','number','unpaid',group,'col-balance crm-mobile-metric'),
            sortHead('Vence','date','due_date',group,'col-due crm-mobile-hidden'),
            sortHead('Estado','text','status',group,'col-status crm-mobile-hidden'),
            '<th class="col-action crm-ui-action-cell">Acciones</th>'
        ].join('');
        const footer=`<tfoot><tr><th colspan="5">Total ${orderedRows.length.toLocaleString('es-CL')} documentos</th><td>${money(totals.net)}</td><td>${money(totals.exempt)}</td><td>${money(totals.iva)}</td><td>${money(totals.other)}</td><td>${money(totals.total)}</td><td>${money(totals.paid)}</td><td>${money(totals.unpaid)}</td><td colspan="3"></td></tr></tfoot>`;
        return `<section class="card billing-document-group crm-ui-table-card" data-document-group="${esc(group.key)}"><header class="billing-document-group-header"><div><h3>${esc(group.title)}</h3><p>${esc(group.description)}</p></div><span class="billing-document-group-count">${orderedRows.length}</span></header>${window.IcyBillingTable.controls(page,group.title)}<div class="billing-document-group-scroll crm-ui-table-scroll billing-reference-table-scroll"><table class="table receivables-table billing-responsive-table billing-crm-table crm-ui-table crm-ui-mobile-table billing-reference-table" data-billing-group="${esc(group.key)}"><thead><tr>${headers}</tr></thead><tbody>${body}</tbody>${footer}</table></div></section>`;
    }
    async function loadReceivables(){
        $('receivablesGroups').innerHTML='<section class="card billing-document-group"><div class="billing-document-loading">Cargando documentos...</div></section>';
        const data=await jsonFetch('/modules/billing/receivables_list_ajax.php?'+params().toString()); state.rows=data.data.rows||[]; state.options=data.data.options||{}; state.selected.clear(); window.IcyBillingTable.resetPaging(state.paging); renderIndicators(); renderRows();
    }
    function renderIndicators(){
        const overdue=state.rows.filter(row=>statusOf(row)==='atrasado').reduce((sum,row)=>sum+netBalanceOf(row),0);
        const partial=state.rows.filter(row=>statusOf(row)==='parcial').length;
        const unlinked=state.rows.filter(row=>!Number(row.crm_customer_id||0)).length;
        $('receivablesIndicators').innerHTML=`<span class="billing-table-indicator ${overdue>0?'is-danger':'is-neutral'}"><i data-lucide="triangle-alert"></i>Vencido neto <strong>${money(overdue)}</strong></span><span class="billing-table-indicator ${partial>0?'is-warning':'is-neutral'}"><i data-lucide="circle-dot"></i>Parciales <strong>${partial.toLocaleString('es-CL')}</strong></span><span class="billing-table-indicator ${unlinked>0?'is-warning':'is-neutral'}"><i data-lucide="user-round-x"></i>Sin cliente <strong>${unlinked.toLocaleString('es-CL')}</strong></span>`;
        refreshIcons();
    }
    function renderRows(){ $('receivablesGroups').innerHTML=documentGroups.map(group=>groupTable(state.rows.filter(row=>groupKey(row)===group.key),group)).join(''); updateSelectionUi(); refreshIcons(); }

    function selectedRows(){ return state.rows.filter(row => state.selected.has(String(row.id)) && isCollectable(row)); }
    function updateSelectionUi(){
        const rows = selectedRows();
        const total = rows.reduce((sum,row) => sum + grossBalanceOf(row), 0);
        $('receivablesSelectedCount').textContent = `${rows.length} seleccionados`;
        $('receivablesSelectedTotal').textContent = `Cobro bruto seleccionado: ${money(total)}`;
        $('bulkCollectionBtn').disabled = rows.length === 0;
        $('clearReceivablesSelectionBtn').disabled = rows.length === 0;
        document.querySelectorAll('.receivables-select-all-group').forEach(box => { const eligible=state.rows.filter(row=>groupKey(row)===box.dataset.group && isCollectable(row)); const selected=eligible.filter(row=>state.selected.has(String(row.id))); box.checked=eligible.length>0 && selected.length===eligible.length; box.indeterminate=selected.length>0 && selected.length<eligible.length; box.disabled=eligible.length===0; });
    }

    function optionHtml(items, valueKey, labelKey, selectedValue, placeholder){
        const out = [`<option value="">${esc(placeholder || 'Seleccionar')}</option>`];
        (items || []).forEach(item => {
            const value = String(item[valueKey] == null ? '' : item[valueKey]);
            out.push(`<option value="${esc(value)}" ${String(selectedValue || '') === value ? 'selected' : ''}>${esc(item[labelKey] || value)}</option>`);
        });
        return out.join('');
    }

    async function openDetail(id){
        state.currentId = String(id);
        $('receivableModal').style.display = 'flex';
        $('receivableModal').classList.add('show');
        $('receivableModal').setAttribute('aria-hidden', 'false');
        document.body.classList.add('is-modal-open');
        $('receivableModalTitle').textContent = 'Cargando documento...';
        $('receivableModalSubtitle').textContent = '';
        $('receivableModalBody').innerHTML = '<p>Cargando...</p>';
        $('receivableModalFooter').innerHTML = '<div class="modal-footer-actions-right"><button type="button" class="btn btn-outline" id="closeReceivableFooterBtn"><i data-lucide="x"></i><span>Cerrar</span></button></div>';
        refreshIcons();
        const data = await jsonFetch('/modules/billing/receivable_detail_ajax.php?id=' + encodeURIComponent(id) + '&env=' + encodeURIComponent(state.env));
        state.paymentJustSaved = false;
        renderDetail(data.document, data.payments || [], data.options || {});
    }

    function renderDetail(doc, payments, options){
        state.currentDocument = doc;
        state.currentPayments = payments || [];
        state.options = options || state.options;
        const status = statusOf(doc);
        const grossBalance = grossBalanceOf(doc);
        const netBalance = netBalanceOf(doc);
        const isFinancialDocument = Number(doc.is_financial_document || 0) === 1;
        if (!isFinancialDocument) {
            $('receivableModalTitle').textContent = (doc.document_label || 'Documento emitido') + ' ' + (doc.document_number || '-');
            $('receivableModalSubtitle').textContent = `${doc.customer_name || doc.receiver_legal_name || 'Cliente sin vincular'} · ${state.env}`;
            const fileActions = `<a class="btn btn-secondary" target="_blank" rel="noopener" href="/modules/billing/receivable_file.php?id=${encodeURIComponent(doc.id)}&env=${encodeURIComponent(state.env)}&type=pdf">Ver PDF</a><a class="btn btn-secondary" target="_blank" rel="noopener" href="/modules/billing/receivable_file.php?id=${encodeURIComponent(doc.id)}&env=${encodeURIComponent(state.env)}&type=xml">Ver XML</a>`;
            $('receivableModalBody').innerHTML = `<div class="billing-alert billing-alert-info"><strong>Documento informativo.</strong><br>Las notas, guías y otros DTE no generan una cuenta por cobrar independiente.</div><div class="receivables-grid crm-ui-form-grid"><section class="receivables-section crm-ui-section"><h3>Documento</h3><p><strong>Tipo:</strong> ${esc(doc.document_label || doc.document_type_taxbureau || doc.document_type_id || '-')}</p><p><strong>Folio:</strong> ${esc(doc.document_number || '-')}</p><p><strong>Fecha:</strong> ${esc(dateText(doc.issue_date))}</p><p><strong>Total:</strong> ${money(doc.total_amount)}</p>${fileActions}</section><section class="receivables-section crm-ui-section"><h3>Cliente</h3><p><strong>Razón social:</strong> ${esc(doc.customer_name || doc.receiver_legal_name || '-')}</p><p><strong>RUT:</strong> ${esc(doc.customer_tax_id || doc.receiver_tax_id_code || '-')}</p><p><strong>Cotización:</strong> ${esc(doc.quote_number || '-')}</p></section></div>`;
            $('receivableModalFooter').innerHTML = '<div class="modal-footer-actions-right"><button type="button" class="btn btn-outline" id="closeReceivableFooterBtn"><i data-lucide="x"></i><span>Cerrar</span></button></div>';
            refreshIcons(); return;
        }
        const currencyCode = String(doc.collection_currency_code || doc.currency_code || 'CLP').toUpperCase();
        const exchangeRate = Number(doc.collection_exchange_rate_value || doc.exchange_rate_value || 1);
        const isForeign = currencyCode !== 'CLP' && exchangeRate > 0;
        const clpGrossTotal = Number(doc.collection_total != null ? doc.collection_total : doc.total_amount || 0);
        const clpGrossPaid = Number(doc.paid_amount || 0);
        const clpNetTotal = Number(doc.collection_net_total != null ? doc.collection_net_total : doc.net_amount_clp != null ? doc.net_amount_clp : doc.net_amount || 0);
        const clpNetPaid = Number(doc.paid_net_amount != null ? doc.paid_net_amount : Math.max(clpNetTotal-netBalance,0));
        const foreignNetTotal = isForeign ? Number(doc.foreign_net_total != null ? doc.foreign_net_total : clpNetTotal / exchangeRate) : clpNetTotal;
        const foreignNetPaid = isForeign ? Number(doc.foreign_paid_net_amount != null ? doc.foreign_paid_net_amount : clpNetPaid / exchangeRate) : clpNetPaid;
        const foreignNetBalance = isForeign ? Number(doc.foreign_net_balance_amount != null ? doc.foreign_net_balance_amount : netBalance / exchangeRate) : netBalance;
        const exchangeRateText = new Intl.NumberFormat('es-CL', {minimumFractionDigits: 2, maximumFractionDigits: 6}).format(exchangeRate);
        const hasCollection = Number(doc.collection_id || 0) > 0;
        const collectable = grossBalance > 0.5 && !['pagado','incobrable','anulado'].includes(status);
        const canRegisterPayment = collectable && hasCollection;
        const canVoidPayments = Boolean(options.can_void_payments);
        const canRepairOrigin = Boolean(options.can_repair_origin);
        const canReopenReceivable = Boolean(options.can_reopen_receivable);
        const originLinked = Number(doc.crm_quote_id || 0) > 0;
        const activePaymentCount = payments.filter(p => Number(p.is_voided || 0) !== 1).length;
        $('receivableModalTitle').textContent = 'Factura emitida ' + (doc.document_number || '-');
        $('receivableModalSubtitle').textContent = `${doc.customer_name || doc.receiver_legal_name || 'Cliente sin vincular'} · ${statusLabel(status)} · ${state.env}`;

        const financeCenters = optionHtml(options.finance_centers, 'id', 'center_name', doc.effective_finance_center_id || doc.finance_center_id, 'Sin centro de ingreso');
        const methods = optionHtml(options.payment_methods, 'id', 'method_name', '', 'Seleccionar método');
        const accounts = optionHtml(options.cash_accounts, 'id', 'account_name', '', 'Seleccionar caja/cuenta');
        const fileActions = `<a class="btn btn-secondary" target="_blank" rel="noopener" href="/modules/billing/receivable_file.php?id=${encodeURIComponent(doc.id)}&env=${encodeURIComponent(state.env)}&type=pdf">Ver PDF</a><a class="btn btn-secondary" target="_blank" rel="noopener" href="/modules/billing/receivable_file.php?id=${encodeURIComponent(doc.id)}&env=${encodeURIComponent(state.env)}&type=xml">Ver XML</a>`;

        const historyRows = payments.length ? payments.map(p => {
            const isVoided = Number(p.is_voided || 0) === 1;
            const paymentCurrency = String(p.currency_code || 'CLP').toUpperCase();
            const paymentForeign = paymentCurrency !== 'CLP' && p.foreign_amount != null;
            const paymentAmount = paymentForeign
                ? `<strong>${currencyMoney(Number(p.foreign_amount || 0), paymentCurrency)}</strong><small style="display:block;color:var(--text-muted);">Equiv. CLP ${money(p.amount)}</small>`
                : `<strong>${money(p.amount)}</strong>`;
            const voidDetail = isVoided
                ? `<span class="receivables-status anulado">Anulado</span><small style="display:block;color:var(--text-muted);">${esc(p.void_reason || 'Sin motivo informado')} · ${esc(dateText(p.voided_at))}</small>`
                : esc(p.notes || '-');
            const paymentAction = !isVoided && canVoidPayments
                ? `<button type="button" class="btn btn-outline" data-void-receivable-payment="${esc(p.id)}" title="Anular este pago"><i data-lucide="undo-2"></i><span>Anular</span></button>`
                : '';
            return `<tr${isVoided ? ' style="opacity:.62;"' : ''}>
            <td>${esc(dateText(p.payment_date))}</td>
            <td>${paymentAmount}</td>
            <td>${esc(p.method_name || p.payment_method || '-')}</td>
            <td>${esc(p.account_name || '-')}</td>
            <td>${esc(p.external_reference || '-')}</td>
            <td>${voidDetail}</td>
            <td>${paymentAction}</td>
        </tr>`;
        }).join('') : '<tr><td colspan="7">Sin pagos registrados.</td></tr>';

        const originRecovery = !originLinked
            ? `<div class="billing-alert billing-alert-warning"><strong>Factura sin vínculo de origen.</strong><br><span>La factura existe en FACTO, pero no está asociada a su cotización ni oportunidad.</span>${canRepairOrigin ? '<div style="margin-top:10px;"><button type="button" class="btn btn-primary" id="repairReceivableOriginBtn"><i data-lucide="link"></i><span>Reparar vínculo de origen</span></button></div>' : ''}</div>`
            : '';

        $('receivableModalBody').innerHTML = `
            ${originRecovery}
            <div class="receivables-summary crm-ui-summary-grid">
                <div class="receivables-summary-card crm-ui-summary-card"><span>Total neto${isForeign ? ' ' + esc(currencyCode) : ''}</span><strong>${isForeign ? currencyMoney(foreignNetTotal,currencyCode) : money(clpNetTotal)}</strong><small>Factura bruta ${money(clpGrossTotal)}</small></div>
                <div class="receivables-summary-card crm-ui-summary-card"><span>Pagado neto equivalente${isForeign ? ' ' + esc(currencyCode) : ''}</span><strong>${isForeign ? currencyMoney(foreignNetPaid,currencyCode) : money(clpNetPaid)}</strong><small>Cobrado bruto ${money(clpGrossPaid)}</small></div>
                <div class="receivables-summary-card crm-ui-summary-card"><span>Saldo neto${isForeign ? ' ' + esc(currencyCode) : ''}</span><strong>${isForeign ? currencyMoney(foreignNetBalance,currencyCode) : money(netBalance)}</strong><small>Por cobrar bruto ${money(grossBalance)}</small></div>
                <div class="receivables-summary-card crm-ui-summary-card"><span>Estado</span><strong>${esc(statusLabel(status))}</strong></div>
            </div>
            <div class="receivables-grid crm-ui-form-grid">
                <section class="receivables-section crm-ui-section">
                    <h3>Documento y cliente</h3>
                    <div class="receivables-field crm-ui-field"><label>Cliente</label><div class="receivables-readonly">${esc(doc.customer_name || doc.receiver_legal_name || 'Cliente sin vincular')}</div></div>
                    <div class="receivables-field crm-ui-field"><label>RUT</label><div class="receivables-readonly">${esc(doc.customer_tax_id || doc.receiver_tax_id_code || '-')}</div></div>
                    <div class="receivables-field crm-ui-field"><label>Cotización</label><div class="receivables-readonly">${esc(doc.quote_number || '-')}</div></div>
                    <div class="receivables-field crm-ui-field"><label>Fecha emisión</label><div class="receivables-readonly">${esc(dateText(doc.issue_date))}</div></div>
                    <div class="receivables-file-actions">${fileActions || '<span class="receivables-muted">PDF/XML no disponibles localmente.</span>'}</div>
                </section>
                <section class="receivables-section crm-ui-section">
                    <h3>Cuenta por cobrar</h3>
                    ${hasCollection ? '' : '<div class="billing-alert billing-alert-info"><strong>Cuenta por cobrar aún no creada.</strong><br><span>Revisa el centro de ingreso y usa la acción explícita del pie para materializarla.</span></div>'}
                    <form id="receivableAdjustmentsForm">
                        <div class="receivables-field crm-ui-field"><label>Condición de pago cliente</label><div class="receivables-readonly">${esc(doc.customer_payment_terms || (doc.facto_payment_term ? doc.facto_payment_term + ' días' : 'Contado'))}</div></div>
                        <div class="receivables-field crm-ui-field"><label>Fecha vencimiento calculada</label><div class="receivables-readonly">${esc(dateText(doc.due_date || doc.payment_due_date))}</div></div>
                        ${isForeign ? `<div class="receivables-field crm-ui-field"><label>Moneda documento</label><div class="receivables-readonly">${esc(currencyCode)}</div></div><div class="receivables-field crm-ui-field"><label>Tipo de cambio a CLP</label><div class="receivables-readonly">1 ${esc(currencyCode)} = $${esc(exchangeRateText)} CLP</div><small>${esc(doc.collection_exchange_rate_source||doc.exchange_rate_source||'FINDIC_BCCH')} · ${esc(dateText(doc.collection_exchange_rate_date||doc.exchange_rate_date||doc.issue_date))}${String(doc.collection_exchange_rate_status||doc.exchange_rate_status||'validated')==='manual'?' · Manual autorizado':''}</small></div>` : ''}
                        <div class="receivables-field crm-ui-field"><label>Centro de ingreso</label><select name="finance_center_id">${financeCenters}</select></div>
                        <div class="receivables-field crm-ui-field"><label>Nota interna</label><textarea name="notes" placeholder="Observación financiera">${esc(doc.collection_notes || '')}</textarea></div>
                    </form>
                </section>
            </div>
            <section class="receivables-section crm-ui-section" style="margin-top:12px;">
                <h3>Registrar pago recibido</h3>
                ${canRegisterPayment ? `<form id="receivablePaymentForm" class="receivables-grid crm-ui-form-grid"><input type="hidden" name="idempotency_key" value="${esc(paymentRequestKey('cxc'))}"><input type="hidden" name="expected_collection_id" value="${esc(doc.collection_id || '')}"><input type="hidden" name="expected_document_id" value="${esc(doc.document_id || '')}"><input type="hidden" name="expected_balance" value="${esc(grossBalance.toFixed(2))}">
                    <div>
                        <div class="receivables-field crm-ui-field"><label>Fecha efectiva del cobro</label><input type="date" name="payment_date" value="${today()}" required><small>Usa la fecha real del abono, no la fecha de registro.</small></div>
                        <div class="receivables-field crm-ui-field"><label>Monto recibido bruto${isForeign ? ' (CLP)' : ''}</label><input type="number" name="amount" min="1" max="${esc(grossBalance)}" step="0.01" value="${esc(grossBalance)}" required><small>El aging se actualizará en neto proporcional al cobro.</small></div>
                        <div class="receivables-field crm-ui-field"><label>Método de pago</label><select name="payment_method_id" required>${methods}</select></div>
                    </div>
                    <div>
                        <div class="receivables-field crm-ui-field"><label>Caja / cuenta de ingreso</label><select name="cash_account_id" required>${accounts}</select></div>
                        <div class="receivables-field crm-ui-field"><label>Referencia verificable</label><input type="text" name="external_reference" minlength="6" maxlength="120" placeholder="N° operación o referencia interna única" ${state.env === 'production' ? 'required' : ''}><small>No usar NA, N/A, sin referencia o no aplica.</small></div>
                        <div class="receivables-field crm-ui-field"><label>Nota de pago</label><textarea name="notes" placeholder="Observación del pago"></textarea></div>
                    </div>
                </form>` : (hasCollection ? '<p class="receivables-muted">Este documento no tiene saldo cobrable.</p>' : '<p class="receivables-muted">Crea primero la cuenta por cobrar para habilitar el registro de pagos.</p>')}
            </section>
            <section class="receivables-section crm-ui-section" style="margin-top:12px;">
                <h3>Historial de pagos</h3>
                <div class="receivables-history-wrap crm-ui-inner-table-wrap"><table class="receivables-history crm-ui-inner-table"><thead><tr><th>Fecha</th><th>Monto${isForeign ? ' CLP' : ''}</th><th>Método</th><th>Cuenta</th><th>Referencia</th><th>Nota / estado</th><th>Acción</th></tr></thead><tbody>${historyRows}</tbody></table></div>
            </section>`;

        const primaryAdjustmentAction = hasCollection
            ? '<button type="button" class="btn btn-outline" id="saveReceivableAdjustmentsBtn"><i data-lucide="save"></i><span>Guardar ajustes</span></button>'
            : '<button type="button" class="btn btn-success" id="saveReceivableAdjustmentsBtn" data-materialize-collection="1"><i data-lucide="landmark"></i><span>Crear cuenta por cobrar</span></button>';
        const reopenAction = hasCollection && canReopenReceivable && activePaymentCount === 0 && ['pagado','parcial','incobrable'].includes(status)
            ? '<button type="button" class="btn btn-outline" id="reopenReceivableBtn"><i data-lucide="rotate-ccw"></i><span>Reabrir saldo</span></button>'
            : '';
        const collectionActions = state.paymentJustSaved
            ? (canRegisterPayment ? '<button type="button" class="btn btn-success" id="registerAnotherReceivablePaymentBtn"><i data-lucide="plus-circle"></i><span>Registrar otro cobro</span></button>' : '<span class="billing-save-confirmation"><i data-lucide="circle-check"></i> Cobro guardado</span>')
            : `${reopenAction}${primaryAdjustmentAction}${canRegisterPayment ? '<button type="button" class="btn btn-success" id="registerReceivablePaymentBtn"><i data-lucide="badge-dollar-sign"></i><span>Registrar cobro</span></button>' : ''}`;
        $('receivableModalFooter').innerHTML = `
            <div class="modal-footer-actions-left">
                <button type="button" class="btn btn-outline" id="closeReceivableFooterBtn"><i data-lucide="x"></i><span>Cerrar</span></button>
            </div>
            <div class="modal-footer-actions-right">${collectionActions}</div>`;
        refreshIcons();
    }

    async function saveAdjustments(){
        const form = $('receivableAdjustmentsForm');
        if (!form) return;
        const materialize = !state.currentDocument || Number(state.currentDocument.collection_id || 0) <= 0;
        if (materialize) {
            const accepted = window.confirm('Se creará una cuenta por cobrar productiva para este documento. Esta acción no registra pagos. ¿Continuar?');
            if (!accepted) return;
        }
        const fd = new FormData(form);
        fd.append('id', state.currentId);
        fd.append('environment', state.env);
        fd.append('materialize_collection', materialize ? '1' : '0');
        await jsonFetch('/modules/billing/receivable_update_ajax.php', {method:'POST', body:fd});
        await openDetail(state.currentId);
        await loadReceivables();
    }

    async function registerPayment(){
        const form = $('receivablePaymentForm');
        if (!form || !form.reportValidity()) return;
        const amount = Number(form.amount.value || 0);
        const reference = String(form.external_reference.value || '').trim();
        const paymentDate = form.payment_date.value || '';
        const methodLabel = form.payment_method_id.options[form.payment_method_id.selectedIndex]?.text || '-';
        const accountLabel = form.cash_account_id.options[form.cash_account_id.selectedIndex]?.text || '-';
        const accepted = window.confirm(`Registrar cobro productivo por ${money(amount)} para el folio ${state.currentDocument.document_number || '-'}?\nFecha efectiva: ${paymentDate}\nMétodo: ${methodLabel}\nCuenta: ${accountLabel}\nReferencia: ${reference || 'sin referencia'}\nEsta acción modificará saldos y no se puede repetir con otra clave.`);
        if (!accepted) return;
        const button = $('registerReceivablePaymentBtn');
        if (button) button.disabled = true;
        try {
            const fd = new FormData(form);
            fd.append('id', state.currentId);
            fd.append('environment', state.env);
            const data = await jsonFetch('/modules/billing/receivable_register_payment_ajax.php', {method:'POST', body:fd});
            state.paymentJustSaved = true;
            renderDetail(data.document, data.payments || [], data.options || state.options);
            await loadReceivables();
        } finally {
            if (button && document.body.contains(button)) button.disabled = false;
        }
    }

    async function voidPayment(paymentId){
        const reason = window.prompt('Indica el motivo de la anulación. El pago conservará su historial, pero dejará de afectar caja y cobranza:');
        if (reason === null) return;
        if (String(reason).trim().length < 8) throw new Error('El motivo debe tener al menos 8 caracteres.');
        const accepted = window.confirm('¿Anular este pago y recalcular la factura como saldo pendiente? Esta acción quedará auditada.');
        if (!accepted) return;
        const fd = new FormData();
        fd.append('payment_id', String(paymentId));
        fd.append('environment', state.env);
        fd.append('reason', String(reason).trim());
        const data = await jsonFetch('/modules/billing/receivable_void_payment_ajax.php', {method:'POST', body:fd});
        renderDetail(data.document, data.payments || [], state.options || {});
        await loadReceivables();
        window.alert(data.message || 'Pago anulado y saldo recalculado.');
    }

    async function repairOrigin(){
        const accepted = window.confirm('Se buscará el intento original y se vinculará esta factura con su cotización y oportunidad. No se emitirá otro documento ni se consumirá un folio. ¿Continuar?');
        if (!accepted) return;
        const button = $('repairReceivableOriginBtn');
        if (button) button.disabled = true;
        try {
            const executeRepair = async quoteNumber => {
                const fd = new FormData();
                fd.append('id', state.currentId);
                if (quoteNumber) fd.append('quote_number', quoteNumber);
                return jsonFetch('/modules/billing/receivable_repair_origin_ajax.php', {method:'POST', body:fd});
            };
            let data;
            try {
                data = await executeRepair('');
            } catch (automaticError) {
                const suggestedQuote = String(state.currentDocument?.quote_number || '').trim();
                const quoteNumber = window.prompt('No fue posible identificar el registro sólo por document_id. Escribe la cotización de origen para validarla por RUT, fecha y total:', suggestedQuote || 'COT-2026-1493');
                if (quoteNumber === null) return;
                if (!/^COT-\d{4}-\d+$/i.test(String(quoteNumber).trim())) {
                    throw new Error('Escribe un número de cotización válido, por ejemplo COT-2026-1493.');
                }
                data = await executeRepair(String(quoteNumber).trim().toUpperCase());
            }
            await openDetail(state.currentId);
            await loadReceivables();
            window.alert((data.result && data.result.message) || 'Vínculo de origen reparado.');
        } finally {
            if (button && document.body.contains(button)) button.disabled = false;
        }
    }

    async function reopenReceivable(){
        const reason = window.prompt('Indica el motivo para reabrir la factura como saldo pendiente:');
        if (reason === null) return;
        if (String(reason).trim().length < 8) throw new Error('El motivo debe tener al menos 8 caracteres.');
        const accepted = window.confirm('La cuenta se recalculará desde sus pagos activos. Si está vencida quedará como atrasada; en caso contrario, por cobrar. ¿Continuar?');
        if (!accepted) return;
        const fd = new FormData();
        fd.append('id', state.currentId);
        fd.append('environment', state.env);
        fd.append('reason', String(reason).trim());
        const data = await jsonFetch('/modules/billing/receivable_reopen_ajax.php', {method:'POST', body:fd});
        renderDetail(data.document, data.payments || [], state.options || {});
        await loadReceivables();
        window.alert(data.message || 'Cuenta reabierta.');
    }

    function openBulk(){
        const rows = selectedRows();
        if (!rows.length) return;
        const total = rows.reduce((sum,row) => sum + grossBalanceOf(row), 0);
        state.bulkRequestKey = paymentRequestKey('receivable-bulk');
        state.bulkSelection = rows.map(row => ({
            id: Number(row.id),
            collection_id: Number(row.collection_id),
            document_id: String(row.document_id || ''),
            expected_balance: Number(grossBalanceOf(row).toFixed(2)),
            due_date: String(row.due_date || row.payment_due_date || row.issue_date || '9999-12-31'),
            folio: String(row.document_number || '')
        }));
        $('receivableBulkModal').style.display = 'flex';
        $('receivableBulkModal').classList.add('show');
        $('receivableBulkModal').setAttribute('aria-hidden', 'false');
        document.body.classList.add('is-modal-open');
        $('receivableBulkSubtitle').textContent = `${rows.length} documentos · cobro bruto seleccionado ${money(total)}`;
        const methods = optionHtml(state.options.payment_methods, 'id', 'method_name', '', 'Seleccionar método');
        const accounts = optionHtml(state.options.cash_accounts, 'id', 'account_name', '', 'Seleccionar caja/cuenta');
        const referenceRequired = state.env === 'production' ? 'required minlength="6"' : '';
        $('receivableBulkBody').innerHTML = `<form id="receivableBulkForm">
            <div class="receivables-grid crm-ui-form-grid">
                <div>
                    <div class="receivables-field crm-ui-field"><label>Fecha efectiva</label><input type="date" name="payment_date" value="${today()}" max="${today()}" required></div>
                    <div class="receivables-field crm-ui-field"><label>Método de pago</label><select name="payment_method_id" required>${methods}</select></div>
                    <div class="receivables-field crm-ui-field"><label>Caja / cuenta de ingreso</label><select name="cash_account_id" required>${accounts}</select></div>
                </div>
                <div>
                    <div class="receivables-field crm-ui-field"><label><input type="checkbox" name="full_payment" value="1" checked> Cobrar saldo completo seleccionado</label></div>
                    <div class="receivables-field crm-ui-field"><label>Monto recibido bruto</label><input type="number" name="amount" min="0.01" max="${esc(total)}" step="0.01" value="${esc(total)}" required></div>
                    <div class="receivables-field crm-ui-field"><label>Referencia verificable</label><input type="text" name="external_reference" maxlength="120" ${referenceRequired} placeholder="N.º operación, voucher o referencia interna"></div>
                    <div class="receivables-field crm-ui-field"><label>Nota</label><textarea name="notes"></textarea></div>
                </div>
            </div>
            <p class="receivables-muted">El cobro bruto se distribuirá por fecha de vencimiento. Los saldos de cobranza se mostrarán en neto proporcional. Si algún saldo cambia, el lote completo se rechazará.</p>
        </form>`;
        $('receivableBulkFooter').innerHTML = '<div class="modal-footer-actions-left"><button type="button" class="btn btn-outline" id="cancelReceivableBulkBtn"><i data-lucide="x"></i><span>Cancelar</span></button></div><div class="modal-footer-actions-right"><button type="button" class="btn btn-primary" id="confirmReceivableBulkBtn"><i data-lucide="badge-dollar-sign"></i><span>Registrar cobro</span></button></div>';
        const form = $('receivableBulkForm');
        const amount = form.amount;
        const full = form.full_payment;
        const sync = () => {
            if (full.checked) { amount.value = total.toFixed(2); amount.disabled = true; }
            else { amount.disabled = false; }
        };
        form.querySelectorAll('input,select').forEach(el => el.addEventListener('change', sync));
        sync();
        refreshIcons();
    }

    async function submitBulk(){
        const form = $('receivableBulkForm');
        if (!form || !form.reportValidity() || !state.bulkSelection.length) return;
        const total = state.bulkSelection.reduce((sum,row) => sum + Number(row.expected_balance || 0), 0);
        const amount = form.full_payment.checked ? total : Number(form.amount.value || 0);
        const paymentDate = form.payment_date.value || '';
        const reference = String(form.external_reference.value || '').trim();
        const methodLabel = form.payment_method_id.options[form.payment_method_id.selectedIndex]?.text || '-';
        const accountLabel = form.cash_account_id.options[form.cash_account_id.selectedIndex]?.text || '-';
        const accepted = window.confirm(`Registrar cobro masivo para ${state.bulkSelection.length} documentos por ${money(amount)}?\nFecha efectiva: ${paymentDate}\nMétodo: ${methodLabel}\nCuenta: ${accountLabel}\nReferencia: ${reference || 'sin referencia'}\nEl lote es atómico: si un saldo cambió, no se registrará ningún cobro.`);
        if (!accepted) return;
        const button = $('confirmReceivableBulkBtn');
        if (button) button.disabled = true;
        try {
            const fd = new FormData(form);
            if (form.full_payment.checked) fd.set('amount', total.toFixed(2));
            fd.append('environment', state.env);
            fd.append('ids', JSON.stringify(state.bulkSelection.map(row => row.id)));
            fd.append('selection_snapshot', JSON.stringify(state.bulkSelection));
            fd.append('idempotency_key', state.bulkRequestKey);
            if (!form.full_payment.checked) fd.set('full_payment', '0');
            const data = await jsonFetch('/modules/billing/receivable_bulk_payment_ajax.php', {method:'POST', body:fd});
            renderBulkResult(data.result);
            state.selected.clear();
            state.bulkSelection = [];
            await loadReceivables();
        } finally {
            if (button && document.body.contains(button)) button.disabled = false;
        }
    }

    function renderBulkResult(result){
        $('receivableBulkSubtitle').textContent = `Batch #${result.batch_id} · aplicado ${money(result.amount_allocated)}`;
        $('receivableBulkBody').innerHTML = `
            <div class="receivables-summary crm-ui-summary-grid">
                <div class="receivables-summary-card crm-ui-summary-card"><span>Monto informado</span><strong>${money(result.amount_total)}</strong></div>
                <div class="receivables-summary-card crm-ui-summary-card"><span>Monto aplicado</span><strong>${money(result.amount_allocated)}</strong></div>
                <div class="receivables-summary-card crm-ui-summary-card"><span>No asignado</span><strong>${money(result.unallocated)}</strong></div>
                <div class="receivables-summary-card crm-ui-summary-card"><span>Documentos</span><strong>${(result.processed || []).length}</strong></div>
            </div>
            <div class="receivables-bulk-result crm-ui-inner-table-wrap"><table><thead><tr><th>Cliente</th><th>Folio</th><th>Aplicado bruto</th><th>Saldo bruto posterior</th><th>Estado</th></tr></thead><tbody>${(result.processed || []).map(row => `<tr><td>${esc(row.customer || '-')}</td><td>${esc(row.folio || '-')}</td><td><strong>${money(row.amount_applied)}</strong></td><td>${money(row.balance_after)}</td><td>${esc(statusLabel(row.status_after))}</td></tr>`).join('')}</tbody></table></div>`;
        $('receivableBulkFooter').innerHTML = '<div class="modal-footer-actions-right"><button type="button" class="btn btn-primary" id="closeReceivableBulkResultBtn"><i data-lucide="check"></i><span>Cerrar</span></button></div>';
        refreshIcons();
    }

    function syncModalBodyLock(){
        const hasOpenModal = Array.from(document.querySelectorAll('.product-modal-backdrop.show')).some((modal) => modal.getAttribute('aria-hidden') !== 'true');
        document.body.classList.toggle('is-modal-open', hasOpenModal);
    }
    function closeDetail(){ $('receivableModal').style.display = 'none'; $('receivableModal').classList.remove('show'); $('receivableModal').setAttribute('aria-hidden', 'true'); syncModalBodyLock(); }
    function closeBulk(){ $('receivableBulkModal').style.display = 'none'; $('receivableBulkModal').classList.remove('show'); $('receivableBulkModal').setAttribute('aria-hidden', 'true'); syncModalBodyLock(); }

    on(document, 'click', function(event){
        if (event.target === $('receivableBulkModal')) { closeBulk(); return; }
        if (event.target === $('receivableModal')) { closeDetail(); return; }
        const pageButton = event.target.closest('[data-billing-page]');
        if (pageButton && pageButton.closest('#receivablesGroups')) { window.IcyBillingTable.movePage(state.paging,pageButton.dataset.billingPage,pageButton.dataset.direction,state.rows.filter(row=>groupKey(row)===pageButton.dataset.billingPage).length); renderRows(); return; }
        const collectButton = event.target.closest('[data-receivable-collect]');
        if (collectButton) { state.selected.clear(); state.selected.add(String(collectButton.dataset.receivableCollect)); updateSelectionUi(); openBulk(); return; }
        const detail = event.target.closest('[data-receivable-detail]');
        if (detail){ openDetail(detail.getAttribute('data-receivable-detail')).catch(err => alert(err.message)); return; }
        const action = event.target.closest('button, a');
        const actionId = action ? action.id : '';
        const voidPaymentButton = event.target.closest('[data-void-receivable-payment]');
        if (voidPaymentButton){ voidPayment(voidPaymentButton.getAttribute('data-void-receivable-payment')).catch(err => alert(err.message)); return; }
        if (actionId === 'reloadReceivablesBtn'){ loadReceivables().catch(err => alert(err.message)); return; }
        if (['closeReceivableModalBtn','closeReceivableFooterBtn'].includes(actionId)){ closeDetail(); return; }
        if (['closeReceivableBulkBtn','cancelReceivableBulkBtn','closeReceivableBulkResultBtn'].includes(actionId)){ closeBulk(); return; }
        if (actionId === 'saveReceivableAdjustmentsBtn'){ saveAdjustments().catch(err => alert(err.message)); return; }
        if (actionId === 'registerReceivablePaymentBtn'){ registerPayment().catch(err => alert(err.message)); return; }
        if (actionId === 'repairReceivableOriginBtn'){ repairOrigin().catch(err => alert(err.message)); return; }
        if (actionId === 'reopenReceivableBtn'){ reopenReceivable().catch(err => alert(err.message)); return; }
        if (actionId === 'registerAnotherReceivablePaymentBtn'){ state.paymentJustSaved = false; renderDetail(state.currentDocument || {}, state.currentPayments || [], state.options || {}); const amount = document.querySelector('#receivablePaymentForm [name=amount]'); if (amount) { amount.focus(); amount.select(); } return; }
        if (actionId === 'bulkCollectionBtn'){ openBulk(); return; }
        if (actionId === 'confirmReceivableBulkBtn'){ submitBulk().catch(err => alert(err.message)); return; }
        if (actionId === 'clearReceivablesSelectionBtn'){ state.selected.clear(); renderRows(); return; }
    });

    on(document, 'change', function(event){
        const pageSize = event.target.closest('[data-billing-page-size]');
        if (pageSize && pageSize.closest('#receivablesGroups')) { window.IcyBillingTable.setPageSize(state.paging,pageSize.dataset.billingPageSize,pageSize.value); renderRows(); return; }
        if (event.target.classList.contains('receivable-select')){
            const id = String(event.target.getAttribute('data-receivable-id'));
            if (event.target.checked) state.selected.add(id); else state.selected.delete(id);
            updateSelectionUi();
            return;
        }
        if (event.target.classList.contains('receivables-select-all-group')){ const group=event.target.dataset.group; state.rows.forEach(row=>{ if(groupKey(row)!==group||!isCollectable(row))return; if(event.target.checked)state.selected.add(String(row.id));else state.selected.delete(String(row.id)); }); renderRows(); return; }
        if (event.target.name === 'full_payment' && event.target.closest('#receivableBulkForm')){
            const amount = event.target.closest('form').elements.amount;
            amount.disabled = event.target.checked;
        }
    });
    on(document, 'billing-reference-sort', function(event){
        if (!event.target.closest('#receivablesGroups')) return;
        window.IcyBillingTable.toggleSort(state.paging,event.detail.group,event.detail.column,event.detail.type);
        renderRows();
    });

    ['receivablesStatus','receivablesFrom','receivablesTo'].forEach(id => on($(id),'change', () => loadReceivables().catch(err => alert(err.message))));
    on($('receivablesSearch'),'keydown', event => { if (event.key === 'Enter') loadReceivables().catch(err => alert(err.message)); });
    on(document,'keydown', event => {
        if (event.key !== 'Escape') return;
        if ($('receivableBulkModal')?.classList.contains('show')) { closeBulk(); return; }
        if ($('receivableModal')?.classList.contains('show')) closeDetail();
    });
    loadReceivables().then(() => {
        const requestedId = new URLSearchParams(window.location.search).get('document_id');
        if (requestedId) openDetail(requestedId).catch(err => alert(err.message));
    }).catch(err => { $('receivablesGroups').innerHTML = `<section class="card billing-document-group"><div class="billing-document-loading">${esc(err.message)}</div></section>`; });
})();
