<?php
require_once '../../core/config.php';
require_once '../../core/session.php';
require_once '../../core/database.php';
require_once '../../core/permissions.php';
require_once '../../core/format.php';
require_once __DIR__ . '/../customers/_customer_quick_fields.php';

require_login();
require_permission('module.crm');

$page_title = 'Cotizaciones';
$pdo = db();
$hasInvoiceIssueDate = q_col_exists($pdo, 'crm_quotes', 'invoice_issue_date');
$hasInvoiceNumber = q_col_exists($pdo, 'crm_quotes', 'invoice_number');
$hasPaymentDate = q_col_exists($pdo, 'crm_quotes', 'payment_date');
$hasFactoDocumentId = q_col_exists($pdo, 'crm_quotes', 'facto_document_id');
$hasQuoteDeletedAt = q_col_exists($pdo, 'crm_quotes', 'deleted_at');
$hasQuoteDeletedBy = q_col_exists($pdo, 'crm_quotes', 'deleted_by');
$hasQuoteDeletedReason = q_col_exists($pdo, 'crm_quotes', 'deleted_reason');
$hasCustomerDeletedAt = q_col_exists($pdo, 'crm_customers', 'deleted_at');
$showDeletedQuotes = $hasQuoteDeletedAt && isset($_GET['view']) && $_GET['view'] === 'deleted';

$allowedStatuses = array('Borrador', 'Enviada', 'Aceptada', 'Facturada', 'Esperando pago', 'Pagada', 'Rechazada', 'Vencida', 'Anulada');

function q_col_exists($pdo, $table, $column)
{
    static $cache = array();
    $key = $table . '.' . $column;

    if (array_key_exists($key, $cache)) {
        return $cache[$key];
    }

    $stmt = $pdo->prepare("SHOW COLUMNS FROM `" . str_replace('`', '', $table) . "` LIKE ?");
    $stmt->execute(array($column));
    $cache[$key] = (bool)$stmt->fetch(PDO::FETCH_ASSOC);

    return $cache[$key];
}

function q_table_exists($pdo, $table)
{
    static $cache = array();
    $table = str_replace('`', '', (string)$table);

    if (array_key_exists($table, $cache)) {
        return $cache[$table];
    }

    $stmt = $pdo->prepare("SHOW TABLES LIKE ?");
    $stmt->execute(array($table));
    $cache[$table] = (bool)$stmt->fetchColumn();

    return $cache[$table];
}

function q_date($date)
{
    return $date ? date('d-m-Y', strtotime($date)) : '-';
}

function q_compact_quote_number($quoteNumber)
{
    $quoteNumber = (string)$quoteNumber;
    if (preg_match('/^([A-Z]+)-\d{4}-(\d+)$/', $quoteNumber, $m)) {
        return $m[1] . '-' . $m[2];
    }
    return $quoteNumber;
}

function q_badge($status)
{
    if (in_array($status, array('Aceptada', 'Pagada'), true)) return 'badge-success';
    if (in_array($status, array('Enviada', 'Facturada', 'Esperando pago'), true)) return 'badge-warning';
    if (in_array($status, array('Rechazada', 'Vencida', 'Anulada'), true)) return 'badge-error';
    return '';
}

function q_status_options($statuses, $current)
{
    $html = '';

    foreach ($statuses as $status) {
        $selected = $status === $current ? ' selected' : '';
        $html .= '<option value="' . safe_text($status) . '"' . $selected . '>' . safe_text($status) . '</option>';
    }

    return $html;
}

function q_phone($phone)
{
    $phone = preg_replace('/[^0-9]/', '', (string)$phone);
    if ($phone === '') return '';
    if (strlen($phone) === 9) return '56' . $phone;
    if (strlen($phone) === 8) return '569' . $phone;
    return $phone;
}

function q_display_customer($row)
{
    if (!empty($row['legal_name'])) return $row['legal_name'];
    if (!empty($row['commercial_name'])) return $row['commercial_name'];
    return $row['customer_name'] ?? '';
}

function q_split_item($item)
{
    $name = (string)($item['name'] ?? '');
    $sku = '';

    if (preg_match('/\s*\(SKU:\s*([^)]+)\)\s*$/i', $name, $m)) {
        $sku = trim($m[1]);
        $name = preg_replace('/\s*\(SKU:\s*([^)]+)\)\s*$/i', '', $name);
    }

    if ($sku === '' && !empty($item['description'])) {
        if (preg_match('/SKU:\s*(.+)$/i', (string)$item['description'], $m)) {
            $sku = trim($m[1]);
        } else {
            $sku = trim((string)$item['description']);
        }
    }

    $item['display_name'] = trim($name);
    $item['sku'] = $sku;
    $allowedUnits = array('unidad', 'mL', 'L', 'g', 'kg', 'pack', 'caja', 'servicio');
    $item['unit'] = in_array(($item['unit'] ?? 'unidad'), $allowedUnits, true) ? $item['unit'] : 'unidad';
    return $item;
}

$stmt = $pdo->prepare("
    SELECT
        q.*,
        " . ($hasInvoiceIssueDate ? "q.invoice_issue_date" : "NULL AS invoice_issue_date") . ",
        " . ($hasInvoiceNumber ? "q.invoice_number" : "NULL AS invoice_number") . ",
        " . ($hasPaymentDate ? "q.payment_date" : "NULL AS payment_date") . ",
        " . ($hasFactoDocumentId ? "q.facto_document_id" : "NULL AS facto_document_id") . ",
        c.customer_name,
        c.commercial_name,
        c.legal_name,
        c.customer_tax_id,
        c.email,
        c.phone,
        c.address,
        c.district,
        c.city,
        o.title AS opportunity_title,
        " . ($hasQuoteDeletedAt ? "q.deleted_at" : "NULL AS deleted_at") . ",
        " . ($hasQuoteDeletedBy ? "q.deleted_by" : "NULL AS deleted_by") . ",
        " . ($hasQuoteDeletedReason ? "q.deleted_reason" : "NULL AS deleted_reason") . "
    FROM crm_quotes q
    JOIN crm_customers c ON q.customer_id = c.id
    LEFT JOIN crm_opportunities o ON q.opportunity_id = o.id
    " . ($hasQuoteDeletedAt
        ? ($showDeletedQuotes
            ? "WHERE q.deleted_at IS NOT NULL"
            : "WHERE q.deleted_at IS NULL" . ($hasCustomerDeletedAt ? " AND c.deleted_at IS NULL" : ""))
        : ($hasCustomerDeletedAt ? "WHERE c.deleted_at IS NULL" : "")) . "
    ORDER BY q.created_at DESC, q.id DESC
");
$stmt->execute();
$quotes = $stmt->fetchAll(PDO::FETCH_ASSOC);

$quoteIds = array_map(function ($q) {
    return (int)$q['id'];
}, $quotes);

$itemsByQuote = array();

if (!empty($quoteIds)) {
    $ph = implode(',', array_fill(0, count($quoteIds), '?'));
    $stmtItems = $pdo->prepare("SELECT * FROM crm_quote_items WHERE quote_id IN ($ph) ORDER BY id ASC");
    $stmtItems->execute($quoteIds);

    foreach ($stmtItems->fetchAll(PDO::FETCH_ASSOC) as $item) {
        $qid = (int)$item['quote_id'];
        if (!isset($itemsByQuote[$qid])) $itemsByQuote[$qid] = array();
        $itemsByQuote[$qid][] = q_split_item($item);
    }
}


$billingIssueByQuote = array();

if (!empty($quoteIds) && q_table_exists($pdo, 'billing_issue_logs')) {
    $ph = implode(',', array_fill(0, count($quoteIds), '?'));
    $stmtBillingIssues = $pdo->prepare("
        SELECT id, quote_id, environment, status, facto_document_id, document_number, pdf_file_path, xml_file_path, created_at, updated_at
        FROM billing_issue_logs
        WHERE quote_id IN ($ph)
          AND mode = 'issue'
          AND status IN ('success', 'sent', 'pending_reconcile')
        ORDER BY id DESC
    ");
    $stmtBillingIssues->execute($quoteIds);

    foreach ($stmtBillingIssues->fetchAll(PDO::FETCH_ASSOC) as $issueRow) {
        $qid = (int)$issueRow['quote_id'];
        if (!isset($billingIssueByQuote[$qid])) {
            $billingIssueByQuote[$qid] = $issueRow;
        }
    }
}

$billingDraftByQuote = array();

if (!empty($quoteIds) && q_table_exists($pdo, 'billing_issue_logs')) {
    $ph = implode(',', array_fill(0, count($quoteIds), '?'));
    $stmtBillingDrafts = $pdo->prepare("
        SELECT id, quote_id, document_type_key, environment, mode, status, facto_document_id, document_number, pdf_file_path, xml_file_path, request_payload, error_message, created_at, updated_at
        FROM billing_issue_logs
        WHERE quote_id IN ($ph)
          AND mode IN ('dry_run', 'draft_preview')
          AND status IN ('draft', 'validated', 'error', 'blocked')
        ORDER BY updated_at DESC, id DESC
    ");
    $stmtBillingDrafts->execute($quoteIds);

    foreach ($stmtBillingDrafts->fetchAll(PDO::FETCH_ASSOC) as $draftRow) {
        $qid = (int)$draftRow['quote_id'];
        if (!isset($billingDraftByQuote[$qid])) {
            $meta = array();
            if (!empty($draftRow['request_payload'])) {
                $decodedDraft = json_decode($draftRow['request_payload'], true);
                if (is_array($decodedDraft) && !empty($decodedDraft['_icylab_draft']) && is_array($decodedDraft['_icylab_draft'])) {
                    $meta = $decodedDraft['_icylab_draft'];
                } elseif (is_array($decodedDraft)) {
                    if (!empty($decodedDraft['header']['issue_date'])) $meta['issue_date'] = $decodedDraft['header']['issue_date'];
                    if (!empty($decodedDraft['header']['payment_conditions'])) $meta['payment_conditions'] = $decodedDraft['header']['payment_conditions'];
                }
            }
            $draftRow['_meta'] = $meta;
            $billingDraftByQuote[$qid] = $draftRow;
        }
    }
}


$billingPaymentMethodsPayload = array();
if (q_table_exists($pdo, 'billing_payment_methods')) {
    $stmtPayMethods = $pdo->query("SELECT id, method_key, method_name, method_type, is_default, sort_order FROM billing_payment_methods WHERE is_active = 1 ORDER BY sort_order ASC, method_name ASC");
    if ($stmtPayMethods) {
        $billingPaymentMethodsPayload = $stmtPayMethods->fetchAll(PDO::FETCH_ASSOC);
    }
}
if (empty($billingPaymentMethodsPayload)) {
    $billingPaymentMethodsPayload = array(
        array('id' => 0, 'method_key' => 'transferencia', 'method_name' => 'Transferencia bancaria', 'method_type' => 'bank_transfer', 'is_default' => 1, 'sort_order' => 10),
        array('id' => 0, 'method_key' => 'efectivo', 'method_name' => 'Efectivo', 'method_type' => 'cash', 'is_default' => 0, 'sort_order' => 20),
        array('id' => 0, 'method_key' => 'tarjeta_debito', 'method_name' => 'Tarjeta de débito', 'method_type' => 'card', 'is_default' => 0, 'sort_order' => 30),
        array('id' => 0, 'method_key' => 'tarjeta_credito', 'method_name' => 'Tarjeta de crédito', 'method_type' => 'card', 'is_default' => 0, 'sort_order' => 40),
        array('id' => 0, 'method_key' => 'webpay', 'method_name' => 'Webpay / pasarela de pago', 'method_type' => 'gateway', 'is_default' => 0, 'sort_order' => 50),
        array('id' => 0, 'method_key' => 'cheque', 'method_name' => 'Cheque', 'method_type' => 'check', 'is_default' => 0, 'sort_order' => 60),
        array('id' => 0, 'method_key' => 'otro', 'method_name' => 'Otro', 'method_type' => 'other', 'is_default' => 0, 'sort_order' => 90)
    );
}

$billingCashAccountsPayload = array();
if (q_table_exists($pdo, 'billing_cash_accounts')) {
    $stmtCashAccounts = $pdo->query("SELECT id, account_key, account_name, account_type, currency_code, bank_name, account_number, is_default, sort_order FROM billing_cash_accounts WHERE is_active = 1 ORDER BY sort_order ASC, account_name ASC");
    if ($stmtCashAccounts) {
        $billingCashAccountsPayload = $stmtCashAccounts->fetchAll(PDO::FETCH_ASSOC);
    }
}
if (empty($billingCashAccountsPayload)) {
    $billingCashAccountsPayload = array(
        array('id' => 0, 'account_key' => 'banco_cuenta_corriente', 'account_name' => 'Banco / cuenta corriente', 'account_type' => 'bank', 'currency_code' => 'CLP', 'bank_name' => null, 'account_number' => null, 'is_default' => 1, 'sort_order' => 10),
        array('id' => 0, 'account_key' => 'caja_efectivo', 'account_name' => 'Caja efectivo', 'account_type' => 'cash', 'currency_code' => 'CLP', 'bank_name' => null, 'account_number' => null, 'is_default' => 0, 'sort_order' => 20),
        array('id' => 0, 'account_key' => 'pasarela_webpay', 'account_name' => 'Pasarela Webpay', 'account_type' => 'gateway', 'currency_code' => 'CLP', 'bank_name' => null, 'account_number' => null, 'is_default' => 0, 'sort_order' => 30),
        array('id' => 0, 'account_key' => 'otra_cuenta', 'account_name' => 'Otra cuenta/caja', 'account_type' => 'other', 'currency_code' => 'CLP', 'bank_name' => null, 'account_number' => null, 'is_default' => 0, 'sort_order' => 90)
    );
}

$stmtOpps = $pdo->query("
    SELECT
        o.id,
        o.customer_id,
        o.title,
        o.customer_name AS opportunity_customer_name,
        o.stage,
        c.customer_name,
        c.commercial_name,
        c.legal_name,
        c.customer_tax_id,
        c.email,
        c.phone,
        c.address,
        c.district,
        c.city
    FROM crm_opportunities o
    JOIN crm_customers c ON c.id = o.customer_id
    WHERE o.deleted_at IS NULL
      AND c.deleted_at IS NULL
    ORDER BY o.updated_at DESC, o.created_at DESC
    LIMIT 400
");
$opportunities = $stmtOpps->fetchAll(PDO::FETCH_ASSOC);

$quotesPayload = array();

foreach ($quotes as $q) {
    $qid = (int)$q['id'];
    $quotesPayload[] = array(
        'id' => $qid,
        'opportunity_id' => (int)($q['opportunity_id'] ?? 0),
        'customer_id' => (int)$q['customer_id'],
        'quote_number' => $q['quote_number'],
        'subtotal' => (float)$q['subtotal'],
        'discount_percent' => (float)$q['discount_percent'],
        'discount_amount' => (float)$q['discount_amount'],
        'iva' => (float)$q['iva'],
        'total' => (float)$q['total'],
        'notes' => $q['notes'] ?? '',
        'status' => $q['status'],
        'invoice_issue_date' => q_date($q['invoice_issue_date'] ?? null),
        'invoice_issue_date_raw' => $q['invoice_issue_date'] ?? '',
        'invoice_number' => $q['invoice_number'] ?? '',
        'facto_document_id' => $q['facto_document_id'] ?? '',
        'billing_issue_log_id' => !empty($billingIssueByQuote[$qid]['id']) ? (int)$billingIssueByQuote[$qid]['id'] : 0,
        'billing_pdf_url' => !empty($billingIssueByQuote[$qid]['pdf_file_path']) ? '/modules/billing/preview_pdf.php?log_id=' . (int)$billingIssueByQuote[$qid]['id'] : '',
        'billing_xml_available' => !empty($billingIssueByQuote[$qid]['xml_file_path']),
        'billing_issue_environment' => $billingIssueByQuote[$qid]['environment'] ?? '',
        'billing_issue_status' => $billingIssueByQuote[$qid]['status'] ?? '',
        'billing_draft_log_id' => !empty($billingDraftByQuote[$qid]['id']) ? (int)$billingDraftByQuote[$qid]['id'] : 0,
        'billing_draft_document_key' => $billingDraftByQuote[$qid]['document_type_key'] ?? '',
        'billing_draft_environment' => $billingDraftByQuote[$qid]['environment'] ?? '',
        'billing_draft_mode' => $billingDraftByQuote[$qid]['mode'] ?? '',
        'billing_draft_status' => $billingDraftByQuote[$qid]['status'] ?? '',
        'billing_draft_document_number' => $billingDraftByQuote[$qid]['document_number'] ?? '',
        'billing_draft_facto_document_id' => $billingDraftByQuote[$qid]['facto_document_id'] ?? '',
        'billing_draft_pdf_url' => !empty($billingDraftByQuote[$qid]['pdf_file_path']) ? '/modules/billing/preview_pdf.php?log_id=' . (int)$billingDraftByQuote[$qid]['id'] : '',
        'billing_draft_has_xml' => !empty($billingDraftByQuote[$qid]['xml_file_path']),
        'billing_draft_updated_at' => !empty($billingDraftByQuote[$qid]['updated_at']) ? q_date($billingDraftByQuote[$qid]['updated_at']) : '',
        'billing_draft_issue_date' => $billingDraftByQuote[$qid]['_meta']['issue_date'] ?? '',
        'billing_draft_payment_terms' => $billingDraftByQuote[$qid]['_meta']['payment_terms_key'] ?? '',
        'billing_draft_error_message' => $billingDraftByQuote[$qid]['error_message'] ?? '',
        'payment_date' => q_date($q['payment_date'] ?? null),
        'payment_date_raw' => $q['payment_date'] ?? '',
        'created_at' => q_date($q['created_at']),
        'created_at_raw' => $q['created_at'],
        'customer_name' => q_display_customer($q),
        'customer_legal_name' => $q['legal_name'] ?? '',
        'customer_commercial_name' => $q['commercial_name'] ?? '',
        'customer_base_name' => $q['customer_name'] ?? '',
        'customer_tax_id' => $q['customer_tax_id'] ?? '',
        'email' => $q['email'] ?? '',
        'phone' => $q['phone'] ?? '',
        'whatsapp_phone' => q_phone($q['phone'] ?? ''),
        'address' => $q['address'] ?? '',
        'district' => $q['district'] ?? '',
        'city' => $q['city'] ?? '',
        'opportunity_title' => !empty($q['opportunity_title']) ? $q['opportunity_title'] : 'Venta directa',
        'deleted_at' => $q['deleted_at'] ?? '',
        'deleted_by' => $q['deleted_by'] ?? '',
        'deleted_reason' => $q['deleted_reason'] ?? '',
        'is_deleted' => !empty($q['deleted_at']),
        'search_text' => mb_strtolower(trim(implode(' ', array_filter(array(
            $q['quote_number'] ?? '',
            q_display_customer($q),
            $q['customer_name'] ?? '',
            $q['commercial_name'] ?? '',
            $q['legal_name'] ?? '',
            $q['customer_tax_id'] ?? '',
            $q['email'] ?? '',
            $q['phone'] ?? '',
            !empty($q['opportunity_title']) ? $q['opportunity_title'] : 'Venta directa',
            $q['status'] ?? '',
            $q['invoice_number'] ?? ''
        )))), 'UTF-8'),
        'items' => $itemsByQuote[$qid] ?? array()
    );
}

$opportunitiesPayload = array();

foreach ($opportunities as $op) {
    $opportunitiesPayload[] = array(
        'id' => (int)$op['id'],
        'customer_id' => (int)$op['customer_id'],
        'title' => $op['title'],
        'stage' => $op['stage'],
        'customer' => array(
            'id' => (int)$op['customer_id'],
            'customer_name' => $op['customer_name'],
            'commercial_name' => $op['commercial_name'] ?? '',
            'legal_name' => $op['legal_name'] ?? '',
            'display_name' => q_display_customer($op),
            'customer_tax_id' => $op['customer_tax_id'] ?? '',
            'email' => $op['email'] ?? '',
            'phone' => $op['phone'] ?? '',
            'address' => $op['address'] ?? '',
            'district' => $op['district'] ?? '',
            'city' => $op['city'] ?? ''
        )
    );
}

$totalQuotes = count($quotes);
$totalAmount = 0;
$acceptedAmount = 0;
$sentAmount = 0;

foreach ($quotes as $q) {
    $totalAmount += (float)$q['total'];
    if ($q['status'] === 'Aceptada') $acceptedAmount += (float)$q['total'];
    if ($q['status'] === 'Enviada') $sentAmount += (float)$q['total'];
}

$sessionUser = current_user();
$currentUserName = !empty($sessionUser['name']) ? $sessionUser['name'] : (!empty($sessionUser['email']) ? $sessionUser['email'] : 'Sistema');

require_once '../../layouts/header.php';
require_once '../../layouts/sidebar.php';
?>

<main class="app-main <?php echo $showDeletedQuotes ? 'quotes-deleted-view' : ''; ?>" id="quotesMain" data-module="quotes">
    <?php require_once '../../layouts/topbar.php'; ?>

    <section class="content quotes-page-shell">
        <div class="page-header header-responsive">
            <div class="page-header-content">
                <span class="label">CRM</span>
                <h2>Cotizaciones</h2>
                <p class="text-muted"><?php echo $showDeletedQuotes ? 'Papelera lógica de cotizaciones eliminadas. Desde aquí puedes revisar y restaurar documentos.' : 'Cotizador comercial con flujo nativo de creación, edición y vista previa.'; ?></p>
            </div>

            <div class="cluster top-actions-cluster">
                <a href="/modules/crm/" class="btn btn-outline btn-icon-mobile quote-header-crm" title="Volver a CRM">
                    <i data-lucide="arrow-left"></i>
                    <span class="mobile-hidden">CRM</span>
                </a>
            
                <a href="/modules/crm/embudo.php" class="btn btn-outline btn-icon-mobile quote-header-embudo" title="Ir a Embudo">
                    <i data-lucide="kanban-square"></i>
                    <span class="mobile-hidden">Embudo</span>
                </a>

                <?php if ($hasQuoteDeletedAt): ?>
                    <?php if ($showDeletedQuotes): ?>
                        <a href="/modules/crm/quotes.php" class="btn btn-outline btn-icon-mobile" title="Volver a cotizaciones activas" data-no-spa="true">
                            <i data-lucide="list"></i>
                            <span class="mobile-hidden">Activas</span>
                        </a>
                    <?php else: ?>
                        <a href="/modules/crm/quotes.php?view=deleted" class="btn btn-outline btn-icon-mobile" title="Ver cotizaciones eliminadas" data-no-spa="true">
                            <i data-lucide="archive-restore"></i>
                            <span class="mobile-hidden">Eliminadas</span>
                        </a>
                    <?php endif; ?>
                <?php endif; ?>
            
                <?php if (!$showDeletedQuotes): ?>
                <button type="button" class="btn btn-primary quote-open-create quote-header-create btn-icon-mobile" title="Nueva cotización">
                    <i data-lucide="plus"></i>
                    <span class="mobile-hidden">Nueva cotización</span>
                </button>
                <?php endif; ?>
            </div>
        </div>

        <section class="filter-bar-container quotes-filter-bar">
            <input id="quoteSearch" type="search" class="input" placeholder="Buscar folio, cliente, RUT, nombre comercial o razón social" autocomplete="off">

            <select id="quoteStatusFilter" class="input quotes-status-select">
                <option value="">Todos</option>
                <?php foreach ($allowedStatuses as $status): ?>
                    <option value="<?php echo safe_text($status); ?>"><?php echo safe_text($status); ?></option>
                <?php endforeach; ?>
            </select>

            <div class="quote-filter-date-group" aria-label="Filtrar por fecha de creación de cotización">
                <label class="quote-date-filter-field">
                    <span>Desde</span>
                    <input id="quoteCreatedFrom" type="date" class="input" title="Cotización creada desde" aria-label="Cotización creada desde">
                    <em>dd/mm/aaaa</em>
                </label>

                <label class="quote-date-filter-field">
                    <span>Hasta</span>
                    <input id="quoteCreatedTo" type="date" class="input" title="Cotización creada hasta" aria-label="Cotización creada hasta">
                    <em>dd/mm/aaaa</em>
                </label>
            </div>
        </section>

        <section class="grid grid-4 responsive-kpis quotes-kpis-row">
            <article class="kpi-card">
                <div class="kpi-label">Cotizaciones</div>
                <div class="kpi-main"><div class="kpi-value" id="kpiCount"><?php echo (int)$totalQuotes; ?></div></div>
            </article>

            <article class="kpi-card">
                <div class="kpi-label">Monto total</div>
                <div class="kpi-main"><div class="kpi-value" id="kpiTotal"><?php echo clp($totalAmount); ?></div></div>
            </article>

            <article class="kpi-card">
                <div class="kpi-label">Aceptadas</div>
                <div class="kpi-main"><div class="kpi-value" id="kpiAccepted"><?php echo clp($acceptedAmount); ?></div></div>
            </article>

            <article class="kpi-card">
                <div class="kpi-label">Enviadas</div>
                <div class="kpi-main"><div class="kpi-value" id="kpiSent"><?php echo clp($sentAmount); ?></div></div>
            </article>
        </section>

        <section class="card quotes-table-card">
            <div class="table-wrap quotes-table-wrap">
                <table class="table quotes-table">
                    <colgroup>
                        <col class="col-folio">
                        <col class="col-client">
                        <col class="col-origin">
                        <col class="col-status">
                        <col class="col-total">
                        <col class="col-date">
                        <col class="col-action">
                    </colgroup>

                    <thead>
                        <tr>
                            <th>Folio</th>
                            <th>Cliente</th>
                            <th>Origen</th>
                            <th>Estado</th>
                            <th>Total</th>
                            <th>Fecha</th>
                            <th class="text-center">Acción</th>
                        </tr>
                    </thead>

                    <tbody id="quotesTableBody">
                        <?php if (empty($quotes)): ?>
                            <tr class="empty-state-row">
                                <td colspan="7" class="text-muted"><?php echo $showDeletedQuotes ? 'No hay cotizaciones eliminadas.' : 'No hay cotizaciones registradas.'; ?></td>
                            </tr>
                        <?php else: ?>
                            <?php foreach ($quotes as $q): ?>
                                <?php
                                    $origin = !empty($q['opportunity_title']) ? $q['opportunity_title'] : 'Venta directa';
                                    $searchData = mb_strtolower(trim(implode(' ', array_filter(array(
                                        $q['quote_number'] ?? '',
                                        q_display_customer($q),
                                        $q['customer_name'] ?? '',
                                        $q['commercial_name'] ?? '',
                                        $q['legal_name'] ?? '',
                                        $q['customer_tax_id'] ?? '',
                                        $q['email'] ?? '',
                                        $q['phone'] ?? '',
                                        $origin,
                                        $q['status'] ?? '',
                                        $q['invoice_number'] ?? ''
                                    )))), 'UTF-8');
                                    $createdDateRaw = !empty($q['created_at']) ? substr((string)$q['created_at'], 0, 10) : '';
                                ?>
                                <tr class="quote-row <?php echo !empty($q['deleted_at']) ? 'quote-row-deleted' : ''; ?>" data-quote-id="<?php echo (int)$q['id']; ?>" data-search="<?php echo safe_text($searchData); ?>" data-status="<?php echo safe_text($q['status']); ?>" data-created-date="<?php echo safe_text($createdDateRaw); ?>" data-deleted="<?php echo !empty($q['deleted_at']) ? '1' : '0'; ?>">
                                    <td data-label="Folio">
                                        <button type="button" class="quote-link quote-open-preview" data-quote-id="<?php echo (int)$q['id']; ?>" title="<?php echo safe_text($q['quote_number']); ?>">
                                            <span class="quote-folio-full"><?php echo safe_text($q['quote_number']); ?></span>
                                            <span class="quote-folio-mobile"><?php echo safe_text(q_compact_quote_number($q['quote_number'])); ?></span>
                                        </button>
                                    </td>
                                    <td data-label="Cliente">
                                        <div class="quote-client-cell">
                                            <strong><?php echo safe_text(q_display_customer($q)); ?></strong>
                                            <span><?php echo safe_text($q['customer_tax_id'] ?? ''); ?></span>
                                        </div>
                                    </td>
                                    <td data-label="Origen">
                                        <div class="quote-origin-cell">
                                            <span><?php echo safe_text($origin); ?></span>
                                        </div>
                                    </td>
                                    <td data-label="Estado">
                                        <select class="quote-status-quick <?php echo q_badge($q['status']); ?>" data-quote-id="<?php echo (int)$q['id']; ?>" data-current="<?php echo safe_text($q['status']); ?>" aria-label="Actualizar estado de cotización" <?php echo !empty($q['deleted_at']) ? 'disabled' : ''; ?>>
                                            <?php echo q_status_options($allowedStatuses, $q['status']); ?>
                                        </select>
                                    </td>
                                    <td data-label="Total">
                                        <strong class="metric-value"><?php echo clp($q['total']); ?></strong>
                                    </td>
                                    <td data-label="Fecha"><?php echo safe_text(q_date($q['created_at'])); ?></td>
                                    <td data-label="Acción" class="actions-cell-align">
                                        <div class="actions-btn-group">
                                            <button type="button" class="btn btn-sm btn-outline action-icon-btn quote-open-preview" data-quote-id="<?php echo (int)$q['id']; ?>" title="Ver cotización">
                                                <i data-lucide="eye"></i>
                                            </button>
                                            <?php if (!empty($q['deleted_at'])): ?>
                                                <form method="POST" action="/modules/crm/quote_restore.php" data-no-spa="true" class="quote-restore-inline-form" style="margin:0;">
                                                    <input type="hidden" name="id" value="<?php echo (int)$q['id']; ?>">
                                                    <input type="hidden" name="redirect" value="/modules/crm/quotes.php?view=deleted">
                                                    <button type="submit" class="btn btn-sm btn-outline action-icon-btn quote-restore-btn" title="Restaurar cotización">
                                                        <i data-lucide="rotate-ccw"></i>
                                                    </button>
                                                </form>
                                            <?php endif; ?>
                                        </div>
                                    </td>
                                </tr>
                            <?php endforeach; ?>
                        <?php endif; ?>
                    </tbody>
                </table>
            </div>
        </section>
    </section>

    <div id="quotesModalBackdrop" class="product-modal-backdrop" aria-hidden="true">
        <div class="product-modal-box quotes-modal-box">

            <div id="quoteLayerPreview" class="quote-view-layer active">
                <div class="product-modal-header quote-modal-header">
                    <button type="button" class="btn btn-outline action-square-btn quote-modal-close" title="Volver">
                        <i data-lucide="arrow-left"></i>
                    </button>

                    <div class="quote-modal-title">
                        <h2 class="h3">Detalle de Cotización</h2>
                        <span class="text-secondary text-sm">Visualiza y comparte el documento</span>
                    </div>

                    <button type="button" class="btn btn-outline action-square-btn quote-modal-close" title="Cerrar">
                        <i data-lucide="x"></i>
                    </button>
                </div>

                <div id="quoteSaveNotice" class="quote-save-notice hidden"></div>

                <div class="product-modal-body quote-document-body">
                    <div id="quotePreviewDocument"></div>
                </div>

                <div class="product-modal-footer quote-modal-footer">
                    <div class="modal-footer-actions-left"></div>

                    <div class="modal-footer-actions-right">
                        <a href="#" id="quotePdfBtn" target="_blank" rel="noopener" class="btn btn-outline">
                            <i data-lucide="file-text"></i>
                            <span>Ver PDF</span>
                        </a>

                        <a href="#" id="quoteWhatsAppBtn" target="_blank" rel="noopener" class="btn btn-outline quote-wa-btn">
                            <i data-lucide="message-circle"></i>
                            <span>WhatsApp</span>
                        </a>

                        <a href="#" id="quoteMailBtn" class="btn btn-outline">
                            <i data-lucide="mail"></i>
                            <span>Email</span>
                        </a>

                        <button type="button" class="btn btn-outline quote-open-edit">
                            <i data-lucide="pencil"></i>
                            <span>Editar</span>
                        </button>

                        <button type="button" id="quoteBillingBtn" class="btn btn-primary quote-billing-btn hidden">
                            <i data-lucide="file-text"></i>
                            <span>Facturar</span>
                        </button>
                    </div>
                </div>
            </div>

            <div id="quoteLayerForm" class="quote-view-layer">
                <div class="product-modal-header quote-modal-header">
                    <button type="button" class="btn btn-outline action-square-btn quote-form-back" title="Volver">
                        <i data-lucide="arrow-left"></i>
                    </button>

                    <div class="quote-modal-title">
                        <h2 class="h3" id="quoteFormTitle">Nueva Cotización</h2>
                        <span class="text-secondary text-sm" id="quoteFormSubtitle">Completa cliente, productos y condiciones</span>
                    </div>

                    <button type="button" class="btn btn-outline action-square-btn quote-modal-close" title="Cerrar">
                        <i data-lucide="x"></i>
                    </button>
                </div>

                <form id="quoteForm" autocomplete="off" data-no-spa="true">
                    <input type="hidden" name="quote_id" id="formQuoteId" value="0">
                    <input type="hidden" name="customer_id" id="formCustomerId" value="0">
                    <input type="hidden" name="opportunity_id" id="formOpportunityId" value="0">

                    <div class="product-modal-body">
                        <div class="quote-form-grid-main">
                            <div class="quote-search-field" id="customerSelectorBlock">
                                <span class="label">Cliente</span>

                                <div class="quote-inline-search">
                                    <input type="search" class="input" id="customerSearch" placeholder="Buscar cliente por nombre o RUT" autocomplete="off">

                                    <button type="button" class="btn btn-outline btn-icon-mobile quote-open-customer-create" title="Crear cliente">
                                        <i data-lucide="user-plus"></i>
                                        <span class="mobile-hidden">Crear cliente</span>
                                    </button>
                                </div>

                                <div id="customerResults" class="search-results-box"></div>
                                <div id="selectedCustomerChip" class="selected-chip hidden"></div>
                            </div>

                            <label>
                                <span class="label">Oportunidad</span>
                                <select class="input" id="opportunitySelect">
                                    <option value="0" data-customer-id="0">Venta directa</option>
                                    <?php foreach ($opportunities as $op): ?>
                                        <option value="<?php echo (int)$op['id']; ?>" data-customer-id="<?php echo (int)$op['customer_id']; ?>">
                                            <?php echo safe_text(($op['customer_name'] ?: $op['opportunity_customer_name']) . ' · ' . $op['title']); ?>
                                        </option>
                                    <?php endforeach; ?>
                                </select>
                            </label>

                            <label>
                                <span class="label">Estado</span>
                                <select class="input" name="status" id="formStatus">
                                    <?php foreach ($allowedStatuses as $status): ?>
                                        <option value="<?php echo safe_text($status); ?>"><?php echo safe_text($status); ?></option>
                                    <?php endforeach; ?>
                                </select>
                            </label>

                            <label class="quote-invoice-field hidden">
                                <span class="label">Emisión factura</span>
                                <input class="input" type="date" name="invoice_issue_date" id="formInvoiceIssueDate">
                            </label>

                            <label class="quote-payment-field hidden">
                                <span class="label">Fecha pago</span>
                                <input class="input" type="date" name="payment_date" id="formPaymentDate">
                            </label>

                            <label>
                                <span class="label">Descuento %</span>
                                <input class="input" type="number" min="0" max="100" step="0.1" name="discount_percent" id="formDiscount" value="0">
                            </label>
                        </div>

                        <div class="quote-search-field mt-4">
                            <span class="label">Productos</span>

                            <div class="quote-inline-search">
                                <input type="search" class="input" id="productSearch" placeholder="Buscar producto por nombre o SKU" autocomplete="off">

                                <button type="button" class="btn btn-outline btn-icon-mobile quote-add-manual" title="Agregar producto manual">
                                    <i data-lucide="plus"></i>
                                    <span class="mobile-hidden">Manual</span>
                                </button>
                            </div>

                            <div id="productResults" class="search-results-box"></div>
                        </div>

                        <div class="quote-builder-head mobile-hidden">
                            <div>Producto</div>
                            <div>SKU</div>
                            <div>Cant.</div>
                            <div>Unidad</div>
                            <div>Precio U. Neto</div>
                            <div>Total fila</div>
                            <div></div>
                        </div>

                        <div id="quoteItems" class="quote-items-box mt-2"></div>

                        <div class="quote-form-bottom mt-4">
                            <label>
                                <span class="label">Condiciones comerciales</span>
                                <textarea class="textarea" name="notes" id="formNotes" rows="5" placeholder="Sin condiciones comerciales adicionales especificadas."></textarea>
                            </label>

                            <div class="quote-summary-card">
                                <div><span>Subtotal Bruto:</span><strong id="sumSubtotal">$0</strong></div>
                                <div><span>Monto Neto:</span><strong id="sumNet">$0</strong></div>
                                <div><span>IVA (19%):</span><strong id="sumIva">$0</strong></div>
                                <hr>
                                <div class="total"><span>Total General:</span><strong id="sumTotal">$0</strong></div>
                            </div>
                        </div>
                    </div>

                    <div class="product-modal-footer quote-modal-footer">
                        <div class="modal-footer-actions-left">
                            <button type="button" class="btn btn-outline quote-danger-btn quote-delete-btn hidden" id="quoteFormDeleteBtn" title="Eliminar cotización" aria-label="Eliminar cotización">
                                <i data-lucide="trash-2"></i>
                                <span>Eliminar</span>
                            </button>
                        </div>

                        <div class="modal-footer-actions-right">
                            <button type="button" class="btn btn-outline quote-form-back" id="quoteFormCancelBtn" title="Cancelar" aria-label="Cancelar edición">
                                <i data-lucide="x"></i>
                                <span>Cancelar</span>
                            </button>

                            <button type="submit" class="btn btn-primary" id="quoteSaveBtn">
                                <i data-lucide="save"></i>
                                <span>Guardar <span class="mobile-hidden">cotización</span></span>
                            </button>
                        </div>
                    </div>
                </form>
            </div>

            <div id="quoteLayerCustomer" class="quote-view-layer">
                <div class="product-modal-header quote-modal-header">
                    <button type="button" class="btn btn-outline action-square-btn quote-back-form" title="Volver">
                        <i data-lucide="arrow-left"></i>
                    </button>

                    <div class="quote-modal-title">
                        <h2 class="h3">Crear Cliente</h2>
                        <span class="text-secondary text-sm">Registro rápido para cotización</span>
                    </div>

                    <button type="button" class="btn btn-outline action-square-btn quote-modal-close" title="Cerrar">
                        <i data-lucide="x"></i>
                    </button>
                </div>

                <form id="customerQuickForm" autocomplete="off" data-no-spa="true">
                    <div class="product-modal-body">
                        <?php icylab_render_customer_quick_fields(array(
                            'name_field' => 'customer_name',
                            'tax_field' => 'customer_tax_id',
                            'tax_id' => 'quickCustomerTaxId',
                            'phone_prefix_id' => 'quickCustomerPhonePrefix',
                            'phone_number_id' => 'quickCustomerPhoneNumber',
                            'phone_hidden_id' => 'quickCustomerPhone',
                            'values' => array('country' => 'Chile'),
                            'required' => array('customer_name', 'country', 'address', 'district', 'city', 'region')
                        )); ?>
                    </div>

                    <div class="product-modal-footer quote-modal-footer">
                        <div class="modal-footer-actions-left"></div>
                        <div class="modal-footer-actions-right">
                            <button type="button" class="btn btn-outline quote-back-form" title="Cancelar" aria-label="Cancelar creación de cliente">
                                <i data-lucide="x"></i>
                                <span>Cancelar</span>
                            </button>
                            <button class="btn btn-primary" type="submit">
                                <i data-lucide="save"></i>
                                <span>Crear <span class="mobile-hidden">cliente</span></span>
                            </button>
                        </div>
                    </div>
                </form>
            </div>

            <div id="quoteLayerBilling" class="quote-view-layer">
                <div class="product-modal-header quote-modal-header">
                    <button type="button" class="btn btn-outline action-square-btn quote-billing-back" title="Volver a cotización">
                        <i data-lucide="arrow-left"></i>
                    </button>

                    <div class="quote-modal-title">
                        <h2 class="h3" id="quoteBillingTitle">Facturación tributaria</h2>
                        <span class="text-secondary text-sm" id="quoteBillingSubtitle">Emisión vinculada a cotización</span>
                    </div>

                    <button type="button" class="btn btn-outline action-square-btn quote-modal-close" title="Cerrar">
                        <i data-lucide="x"></i>
                    </button>
                </div>

                <div class="product-modal-body quote-billing-body">
                    <div id="quoteBillingContent" class="quote-billing-content"></div>
                </div>

                <div class="product-modal-footer quote-modal-footer quote-billing-footer">
                    <div class="modal-footer-actions-left"></div>
                    <div class="modal-footer-actions-right" id="quoteBillingActions"></div>
                </div>
            </div>

        </div>
    </div>

    <script src="/assets/js/customer-location.js?v=p4_5_1"></script>
    <script id="quotesPayload" type="application/json"><?php echo json_encode($quotesPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?></script>
    <script id="quotesOpportunitiesPayload" type="application/json"><?php echo json_encode($opportunitiesPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?></script>
    <script>
        window.ICY_USER_NAME = <?php echo json_encode($currentUserName, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
        window.ICY_QUOTES_ALLOWED_STATUSES = <?php echo json_encode($allowedStatuses, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
        window.ICY_QUOTES_CAN_BILL = <?php echo (function_exists('user_can') && user_can('module.billing')) ? 'true' : 'false'; ?>;
        window.ICY_BILLING_PAYMENT_METHODS = <?php echo json_encode($billingPaymentMethodsPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
        window.ICY_BILLING_CASH_ACCOUNTS = <?php echo json_encode($billingCashAccountsPayload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); ?>;
    </script>

<style>
    .quotes-page-shell {
        padding-top: var(--space-6);
    }
    
    #quotesMain .page-header.header-responsive {
        display: flex;
        align-items: flex-start;
        justify-content: space-between;
        gap: var(--space-4);
        margin-bottom: var(--space-4);
    }
    
    #quotesMain .top-actions-cluster {
        display: flex;
        align-items: center;
        justify-content: flex-end;
        gap: var(--space-2);
        flex: 0 0 auto;
    }
    
    #quotesMain .quote-header-crm,
    #quotesMain .quote-header-embudo,
    #quotesMain .quote-header-create {
        white-space: nowrap;
        flex: 0 0 auto;
    }
    
    #quotesMain .quote-header-create {
        white-space: nowrap;
        flex: 0 0 auto;
    }
    
    .quotes-filter-bar {
        display: grid;
        grid-template-columns: minmax(280px, 1fr) 180px minmax(360px, 430px);
        gap: var(--space-3);
        align-items: center;
        width: 100%;
        max-width: 100%;
        padding: var(--space-4);
        background: var(--surface-1);
        border: 1px solid var(--border-default);
        border-radius: var(--radius-2xl);
        margin-bottom: var(--space-4);
        overflow: hidden;
    }
    
    .quotes-filter-bar > * {
        min-width: 0;
    }
    
    .quotes-kpis-row {
        margin-bottom: var(--space-4);
    }
    
    .quote-filter-date-group {
        display: grid;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
        gap: var(--space-2);
        min-width: 0;
        width: 100%;
    }
    
    .quote-filter-date-group input {
        width: 100%;
        min-width: 0;
    }

    #quotesMain .quote-date-filter-field {
        position: relative;
        display: block;
        min-width: 0;
        width: 100%;
    }

    #quotesMain .quote-date-filter-field > span,
    #quotesMain .quote-date-filter-field > em {
        display: none;
    }
@media (min-width: 981px) and (max-width: 1280px) {
    #quotesMain .quotes-filter-bar {
        grid-template-columns: minmax(220px, 1fr) 160px minmax(300px, 360px);
        gap: var(--space-2);
    }

    #quotesMain .quote-filter-date-group {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }
}
.quote-status-quick { width: 100%; height: 36px; border-radius: var(--radius-lg); border: 1px solid var(--border-default); background: var(--surface-2); color: var(--text-primary); font-weight: 800; font-size: 0.82rem; padding: 0 24px 0 10px; cursor: pointer; }
.quote-status-quick.badge-success { border-color: rgba(0, 200, 80, 0.35); color: var(--color-primary); background: rgba(0, 255, 48, 0.08); }
.quote-status-quick.badge-warning { border-color: rgba(255, 190, 80, 0.45); color: var(--text-primary); background: rgba(255, 190, 80, 0.08); }
.quote-status-quick.badge-error { border-color: rgba(255, 77, 79, 0.45); color: var(--color-error); background: rgba(255, 77, 79, 0.08); }
.quote-row-deleted { opacity: 0.74; }
.quote-row-deleted .quote-link { text-decoration: line-through; }

/* Filtros: asegurar que las filas ocultas por JS desaparezcan también en móvil,
   donde las filas se fuerzan como grid con display: grid !important. */
#quotesMain #quotesTableBody tr.quote-row.is-filter-hidden,
#quotesMain #quotesTableBody tr.quote-row[hidden] {
    display: none !important;
}

.quote-restore-inline-form { display: inline-flex; }
.quotes-table-card {
    overflow: hidden !important;
    display: flex;
    flex-direction: column;
    min-height: clamp(280px, calc(100dvh - 420px), 520px);
}

.quotes-table-wrap {
    width: 100% !important;
    max-width: 100% !important;
    flex: 1 1 auto;
    min-height: 0;
    overflow-x: hidden !important;
    overflow-y: auto;
}

.quotes-table {
    width: 100% !important;
    min-width: 0 !important;
    max-width: 100% !important;
    table-layout: fixed !important;
    border-collapse: collapse;
}

.quotes-table th,
.quotes-table td {
    vertical-align: middle;
    min-width: 0 !important;
    box-sizing: border-box;
}

.col-folio { width: 12%; }
.col-client { width: 20%; }
.col-origin { width: auto; }
.col-status { width: 135px; }
.col-total { width: 128px; }
.col-date { width: 120px; }
.col-action { width: 96px; }

.quote-link {
    border: 0;
    background: transparent;
    padding: 0;
    color: var(--color-primary);
    font: inherit;
    font-weight: 800;
    cursor: pointer;
    text-align: left;
    max-width: 100%;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.quote-client-cell,
.quote-origin-cell {
    display: grid;
    min-width: 0;
    max-width: 100%;
}

.quote-client-cell strong,
.quote-origin-cell span {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.quote-client-cell span {
    color: var(--text-muted);
    font-size: 0.85rem;
}

.actions-cell-align {
    text-align: center !important;
    width: 76px;
    min-width: 76px;
    white-space: nowrap;
}

.quotes-table th:last-child {
    text-align: center !important;
}

.actions-btn-group {
    display: flex !important;
    justify-content: center !important;
    align-items: center !important;
    gap: 6px !important;
    flex-wrap: nowrap !important;
}

.action-icon-btn {
    padding: 0 !important;
    width: 36px !important;
    height: 36px !important;
    min-width: 36px !important;
    max-width: 36px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    border: 1px solid var(--border-default) !important;
    border-radius: var(--radius-md) !important;
    background: var(--surface-2) !important;
    color: var(--text-primary) !important;
    cursor: pointer;
}

.action-icon-btn:hover {
    background: var(--surface-3) !important;
}

.action-square-btn {
    padding: 0 !important;
    width: 42px !important;
    height: 42px !important;
    min-width: 42px !important;
    max-width: 42px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    border: 1px solid var(--border-default) !important;
    border-radius: var(--radius-lg) !important;
    background: var(--surface-1) !important;
    color: var(--text-primary) !important;
    cursor: pointer;
}

.action-square-btn:hover {
    background: var(--surface-2) !important;
}
.product-modal-backdrop { position: fixed; inset: 0; background: rgba(0,0,0,0.6); backdrop-filter: blur(4px); z-index: 1050; display: none; align-items: center; justify-content: center; opacity: 0; transition: opacity 0.2s ease; }
.product-modal-backdrop.show { opacity: 1; }
.quotes-modal-box { width: 1120px; max-width: 96%; max-height: 94dvh; overflow: hidden; position: relative; background: var(--surface-1); border: 1px solid var(--border-default); border-radius: var(--radius-xl); box-shadow: var(--shadow-lg); display: flex; flex-direction: column; transform: translateY(20px); transition: transform 0.2s ease; }
.product-modal-backdrop.show .quotes-modal-box { transform: translateY(0); }
.quote-view-layer { display: none; flex-direction: column; flex: 1; min-height: 0; width: 100%; }
.quote-view-layer.active { display: flex; }
.quote-modal-header { flex-shrink: 0; display: flex; align-items: flex-start; gap: 12px; justify-content: space-between; padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border-subtle); }
.quote-modal-title { flex: 1; min-width: 0; }
.quote-modal-title h2 { margin-bottom: 2px; }
.quote-save-notice { padding: 10px var(--space-5); border-bottom: 1px solid rgba(0,255,48,0.18); background: var(--color-primary-soft); color: var(--text-primary); font-size: 0.9rem; font-weight: 700; }
.quote-document-body { padding: 0 !important; overflow-y: auto; flex: 1; background: #fff; }
.quote-doc { color: #00122f; background: #fff; padding: 42px 52px 28px; font-family: var(--font-sans); }
.quote-doc-head { display: flex; justify-content: space-between; gap: 24px; margin-bottom: 28px; align-items: flex-start; }
.quote-doc h2 { color: #00122f; font-size: 1.55rem; letter-spacing: -0.03em; }
.quote-doc-grid { display: grid; grid-template-columns: 1fr 1fr; border: 1px solid #dbe3ef; border-radius: 8px; overflow: hidden; margin-bottom: 34px; }
.quote-doc-cell { padding: 14px 18px; border-bottom: 1px solid #dbe3ef; min-height: 74px; }
.quote-doc-cell:nth-child(odd) { border-right: 1px solid #dbe3ef; }
.quote-doc-cell:nth-last-child(-n+2) { border-bottom: 0; }
.quote-doc-label { display: block; color: #536b8f; font-size: 0.76rem; font-weight: 800; text-transform: uppercase; margin-bottom: 8px; }
.quote-doc-value { color: #00122f; font-size: 1rem; }
.quote-doc-table { width: 100%; border-collapse: collapse; margin-bottom: 28px; }
.quote-doc-table th { color: #536b8f; font-size: 0.76rem; text-transform: uppercase; text-align: left; padding: 10px 14px; border-bottom: 2px solid #dbe3ef; }
.quote-doc-table td { color: #00122f; padding: 14px; border-bottom: 1px solid #dbe3ef; }
.quote-doc-table .text-right { text-align: right; }
.quote-doc-bottom { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 30px; align-items: start; }
.quote-doc-conditions { border: 1px solid #dbe3ef; border-radius: 8px; min-height: 118px; padding: 18px; background: #f8fafc; }
.quote-doc-totalbox { border: 1px solid #dbe3ef; border-radius: 8px; padding: 18px 22px; display: grid; gap: 12px; }
.quote-doc-totalbox div { display: flex; justify-content: space-between; gap: 16px; }
.quote-doc-totalbox hr { border: 0; border-top: 1px solid #dbe3ef; width: 100%; }
.quote-doc-totalbox .grand { font-size: 1.25rem; font-weight: 800; }
.product-modal-footer.quote-modal-footer { flex-shrink: 0; display: flex !important; flex-direction: row !important; justify-content: space-between !important; align-items: center !important; gap: 8px !important; padding: var(--space-3) var(--space-4) !important; background: var(--surface-0); border-top: 1px solid var(--border-subtle); border-radius: 0 0 var(--radius-xl) var(--radius-xl); }
.modal-footer-actions-right { display: flex !important; gap: 8px !important; justify-content: flex-end !important; flex: 1; flex-wrap: nowrap; }
.quote-wa-btn { color: #25D366 !important; border-color: #25D366 !important; }
.quote-form-grid-main { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1.2fr) 150px 130px; gap: var(--space-3); align-items: start; }
.quote-inline-search { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: var(--space-2); }
.quote-search-field { position: relative; }
.search-results-box { position: absolute; width: 100%; z-index: 1060; box-shadow: var(--shadow-lg); border: 1px solid var(--border-default); background: var(--surface-1); max-height: 240px; overflow-y: auto; border-radius: var(--radius-lg); top: calc(100% + 6px); left: 0; display: none; }
.search-results-box.show { display: block; }
.search-result-item { width: 100%; background: transparent; border: none; padding: 10px 12px; text-align: left; cursor: pointer; color: var(--text-secondary); border-bottom: 1px solid var(--border-subtle); display: flex; flex-direction: column; }
.search-result-item:hover { background: var(--surface-2); }
.search-result-item strong { color: var(--text-primary); font-size: 0.9rem; }
.search-result-item span { color: var(--text-muted); font-size: 0.75rem; margin-top: 2px; }
.search-result-item.is-muted { cursor: default; }
.selected-chip { margin-top: 8px; padding: 8px 10px; background: var(--color-primary-soft); border: 1px solid rgba(0,255,48,0.2); border-radius: var(--radius-md); font-size: 0.85rem; color: var(--text-primary); }
.selected-chip.is-locked { background: var(--surface-2); border-color: var(--border-default); }
.quote-builder-head { display: grid; grid-template-columns: minmax(220px, 1fr) 105px 82px 110px 130px 120px 42px; gap: var(--space-2); align-items: center; margin-top: var(--space-5); padding: 0 10px 8px; border-bottom: 1px solid var(--border-subtle); color: var(--text-muted); font-size: 0.75rem; font-weight: 800; text-transform: uppercase; }
.quote-items-box { display: grid; gap: var(--space-2); }
.eq-row { display: grid; grid-template-columns: minmax(220px, 1fr) 105px 82px 110px 130px 120px 42px; gap: var(--space-2); align-items: end; padding: 10px; background: var(--surface-2); border: 1px solid var(--border-default); border-radius: var(--radius-lg); }
.mobile-label { display: none; }
.it-total-text { height: 42px; display: flex; align-items: center; font-weight: 800; }
.quote-form-bottom { display: grid; grid-template-columns: minmax(0, 1fr) 360px; gap: var(--space-4); align-items: start; }
.quote-summary-card { border: 1px solid var(--border-default); border-radius: var(--radius-xl); padding: var(--space-4); display: grid; gap: 12px; background: var(--surface-2); }
.quote-summary-card div { display: flex; justify-content: space-between; gap: 14px; }
.quote-summary-card hr { width: 100%; border: 0; border-top: 1px solid var(--border-default); }
.quote-summary-card .total { font-size: 1.15rem; font-weight: 800; }
.quote-customer-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); }
.hidden { display: none !important; }

#quotesMain .quote-folio-mobile {
    display: none;
}

#quotesMain .quote-folio-full {
    display: inline;
}
/* QUOTES RESPONSIVE CLEANUP FINAL */

/* Tablet / escritorio angosto */
@media (max-width: 980px) {
    #quotesMain .quote-form-grid-main,
    #quotesMain .quote-form-bottom,
    #quotesMain .quote-doc-bottom {
        grid-template-columns: 1fr !important;
    }

    #quotesMain .quote-doc {
        padding: 30px 26px;
    }

    #quotesMain .quote-builder-head {
        display: none !important;
    }
}

/* Mobile real / touch */
@media (max-width: 980px), (hover: none) and (pointer: coarse) {
    #quotesMain {
        overflow: hidden !important;
        max-width: 100vw !important;
    }

    #quotesMain * {
        box-sizing: border-box !important;
    }

    #quotesMain .quotes-page-shell {
        display: flex !important;
        flex-direction: column !important;
        height: calc(100dvh - var(--topbar-height, 72px)) !important;
        max-height: calc(100dvh - var(--topbar-height, 72px)) !important;
        min-height: 0 !important;
        overflow: hidden !important;
        padding: var(--space-3) !important;
        max-width: 100% !important;
    }

    /* Header móvil homologado a embudo */
    #quotesMain .page-header.header-responsive {
        display: grid !important;
        grid-template-columns: minmax(0, 1fr) auto !important;
        align-items: start !important;
        gap: 12px !important;
        width: 100% !important;
        margin-bottom: var(--space-4) !important;
    }

    #quotesMain .page-header-content {
        display: block !important;
        grid-column: 1 / 2 !important;
        min-width: 0 !important;
        max-width: 100% !important;
    }

    #quotesMain .page-header-content .label {
        display: block !important;
        margin: 0 0 6px 0 !important;
        font-size: 0.8rem !important;
        line-height: 1.1 !important;
    }

    #quotesMain .page-header-content h1 {
        display: block !important;
        margin: 0 0 8px 0 !important;
        font-size: 1.55rem !important;
        line-height: 1.15 !important;
    }

    #quotesMain .page-header-content p {
        display: block !important;
        margin: 0 !important;
        font-size: 0.95rem !important;
        line-height: 1.35 !important;
        color: var(--text-secondary) !important;
    }

    #quotesMain .top-actions-cluster {
        display: flex !important;
        grid-column: 2 / 3 !important;
        align-items: flex-start !important;
        justify-content: flex-end !important;
        gap: 8px !important;
        width: auto !important;
        min-width: 92px !important;
        max-width: max-content !important;
        padding-top: 34px !important;
        margin: 0 !important;
        flex: 0 0 auto !important;
    }

    #quotesMain .top-actions-cluster .quote-header-crm {
        display: none !important;
    }

    #quotesMain .top-actions-cluster .quote-header-embudo,
    #quotesMain .top-actions-cluster .quote-header-create {
        width: 42px !important;
        height: 42px !important;
        min-width: 42px !important;
        max-width: 42px !important;
        padding: 0 !important;
        margin: 0 !important;
        display: inline-flex !important;
        align-items: center !important;
        justify-content: center !important;
        border-radius: var(--radius-md) !important;
        font-size: 0 !important;
        flex: 0 0 42px !important;
    }

    #quotesMain .top-actions-cluster .quote-header-embudo span,
    #quotesMain .top-actions-cluster .quote-header-create span {
        display: none !important;
    }

    #quotesMain .top-actions-cluster .quote-header-embudo svg,
    #quotesMain .top-actions-cluster .quote-header-create svg {
        margin: 0 !important;
    }

    /* Filtros móviles */
    #quotesMain .quotes-filter-bar {
        display: grid !important;
        grid-template-columns: minmax(0, 1fr) !important;
        gap: 8px !important;
        flex-shrink: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        margin: 0 0 var(--space-3) 0 !important;
        padding: 0 !important;
        background: transparent !important;
        border: 0 !important;
        box-shadow: none !important;
        border-radius: 0 !important;
        overflow: visible !important;
    }

    #quotesMain #quoteSearch,
    #quotesMain #quoteStatusFilter {
        grid-column: 1 / -1 !important;
        min-width: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        height: 44px !important;
    }

    #quotesMain #quoteStatusFilter {
        padding-right: 38px !important;
    }

    #quotesMain .quote-filter-date-group {
        grid-column: 1 / -1 !important;
        display: grid !important;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) !important;
        gap: 8px !important;
        width: 100% !important;
        max-width: 100% !important;
    }

    #quotesMain .quote-filter-date-group input {
        height: 42px !important;
        min-width: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
    }

    #quotesMain .quote-date-filter-field {
        position: relative !important;
        min-width: 0 !important;
        width: 100% !important;
    }

    #quotesMain .quote-date-filter-field > span {
        display: block !important;
        margin-bottom: 4px !important;
        font-size: 0.68rem !important;
        line-height: 1 !important;
        font-weight: 800 !important;
        letter-spacing: 0.04em !important;
        text-transform: uppercase !important;
        color: var(--text-muted) !important;
    }

    #quotesMain .quote-date-filter-field > input {
        height: 44px !important;
        min-height: 44px !important;
        padding-left: 12px !important;
        padding-right: 12px !important;
        color: var(--text-primary) !important;
        background: var(--surface-1) !important;
    }

    #quotesMain .quote-date-filter-field > em {
        position: absolute !important;
        left: 12px !important;
        bottom: 15px !important;
        display: block !important;
        font-style: normal !important;
        font-size: 0.88rem !important;
        line-height: 1 !important;
        color: var(--text-muted) !important;
        pointer-events: none !important;
        opacity: 0.68 !important;
    }

    #quotesMain .quote-date-filter-field.has-value > em {
        display: none !important;
    }

    #quotesMain .mobile-hidden {
        display: none !important;
    }

    #quotesMain .quotes-kpis-row,
    #quotesMain .responsive-kpis.quotes-kpis-row,
    #quotesMain .grid.grid-4.responsive-kpis.quotes-kpis-row {
        display: none !important;
        visibility: hidden !important;
        height: 0 !important;
        min-height: 0 !important;
        max-height: 0 !important;
        margin: 0 !important;
        padding: 0 !important;
        overflow: hidden !important;
    }

    /* Tarjeta tabla */
    #quotesMain .quotes-table-card {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        display: flex !important;
        flex-direction: column !important;
        overflow: hidden !important;
        border-radius: var(--radius-xl) !important;
    }

    #quotesMain .quotes-table-wrap,
    #quotesMain .table-wrap {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        width: 100% !important;
        max-width: 100% !important;
        overflow-x: hidden !important;
        overflow-y: auto !important;
        -webkit-overflow-scrolling: touch !important;
    }

    #quotesMain .quotes-table-wrap::after,
    #quotesMain .table-wrap::after {
        content: none !important;
        display: none !important;
    }

    #quotesMain .quotes-table,
    #quotesMain .quotes-table thead,
    #quotesMain .quotes-table tbody {
        display: block !important;
        width: 100% !important;
        max-width: 100% !important;
        min-width: 0 !important;
        border-collapse: collapse !important;
        table-layout: fixed !important;
    }

    #quotesMain .quotes-table colgroup,
    #quotesMain .quotes-table th:nth-child(3),
    #quotesMain .quotes-table td:nth-child(3),
    #quotesMain .quotes-table th:nth-child(5),
    #quotesMain .quotes-table td:nth-child(5),
    #quotesMain .quotes-table th:nth-child(6),
    #quotesMain .quotes-table td:nth-child(6) {
        display: none !important;
        width: 0 !important;
        max-width: 0 !important;
        padding: 0 !important;
        border: 0 !important;
        overflow: hidden !important;
    }

    /* Columnas móviles: Folio | Cliente | Estado | Acción */
    #quotesMain .quotes-table thead tr,
    #quotesMain .quotes-table tbody tr.quote-row {
        display: grid !important;
        grid-template-columns: 82px minmax(0, 1fr) 104px 92px !important;
        width: 100% !important;
        max-width: 100% !important;
        min-width: 0 !important;
        align-items: stretch !important;
        gap: 0 !important;
        box-sizing: border-box !important;
    }

    #quotesMain .quotes-table thead tr {
        min-height: 42px !important;
        border-bottom: 1px solid var(--border-subtle) !important;
    }

    #quotesMain .quotes-table tbody tr.quote-row {
        min-height: 72px !important;
        border-bottom: 1px solid var(--border-subtle) !important;
    }

    #quotesMain .quotes-table th,
    #quotesMain .quotes-table td {
        display: flex !important;
        align-items: center !important;
        align-self: stretch !important;
        width: auto !important;
        min-width: 0 !important;
        max-width: 100% !important;
        margin: 0 !important;
        padding: 10px 8px !important;
        border: 0 !important;
        overflow: hidden !important;
        white-space: nowrap !important;
    }

    #quotesMain .quotes-table thead th {
        min-height: 42px !important;
        font-size: 0.69rem !important;
        line-height: 1 !important;
        letter-spacing: 0.04em !important;
        color: var(--text-muted) !important;
    }

    #quotesMain .quotes-table tbody td {
        min-height: 72px !important;
    }

    #quotesMain .quotes-table th:nth-child(4),
    #quotesMain .quotes-table td:nth-child(4),
    #quotesMain .quotes-table th:nth-child(7),
    #quotesMain .quotes-table td:nth-child(7) {
        justify-content: center !important;
        text-align: center !important;
        padding-left: 4px !important;
        padding-right: 4px !important;
    }

    #quotesMain .quote-folio-full {
        display: none !important;
    }

    #quotesMain .quote-folio-mobile {
        display: inline !important;
    }

    #quotesMain .quote-link {
        display: block !important;
        width: 100% !important;
        max-width: 100% !important;
        min-width: 0 !important;
        color: var(--color-primary) !important;
        font-size: 0.82rem !important;
        font-weight: 800 !important;
        line-height: 1.1 !important;
        white-space: nowrap !important;
        overflow: hidden !important;
        text-overflow: ellipsis !important;
        text-align: left !important;
        border: 0 !important;
        background: transparent !important;
        box-shadow: none !important;
        appearance: none !important;
        -webkit-appearance: none !important;
    }

    #quotesMain .quote-client-cell {
        display: flex !important;
        flex-direction: column !important;
        justify-content: center !important;
        gap: 2px !important;
        width: 100% !important;
        min-width: 0 !important;
        max-width: 100% !important;
        overflow: hidden !important;
    }

    #quotesMain .quote-client-cell strong,
    #quotesMain .quote-client-cell span {
        display: block !important;
        width: 100% !important;
        min-width: 0 !important;
        max-width: 100% !important;
        white-space: nowrap !important;
        overflow: hidden !important;
        text-overflow: ellipsis !important;
    }

    #quotesMain .quote-client-cell strong {
        font-size: 0.82rem !important;
        line-height: 1.12 !important;
        font-weight: 800 !important;
    }

    #quotesMain .quote-client-cell span {
        font-size: 0.75rem !important;
        line-height: 1.1 !important;
        color: var(--text-muted) !important;
    }

    #quotesMain .quote-status-quick {
        width: 104px !important;
        max-width: 104px !important;
        min-width: 104px !important;
        height: 38px !important;
        padding: 0 34px 0 10px !important;
        font-size: 0.74rem !important;
        text-overflow: ellipsis !important;
        overflow: hidden !important;
        white-space: nowrap !important;
    }

    #quotesMain .actions-cell-align,
    #quotesMain .actions-btn-group {
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        width: 100% !important;
        min-width: 0 !important;
        max-width: 100% !important;
        padding-left: 0 !important;
        padding-right: 0 !important;
        overflow: visible !important;
    }

    #quotesMain .action-icon-btn {
        width: 40px !important;
        height: 40px !important;
        min-width: 40px !important;
        max-width: 40px !important;
        flex: 0 0 40px !important;
        padding: 0 !important;
        border-radius: var(--radius-lg) !important;
    }

    #quotesMain .quotes-pager {
        flex-shrink: 0 !important;
        min-height: 48px !important;
    }

    /* Modal móvil */
    #quotesMain .product-modal-backdrop {
        align-items: stretch !important;
        padding: 8px !important;
        overflow: hidden !important;
    }

    #quotesMain .quotes-modal-box {
        width: 100% !important;
        max-width: 100% !important;
        height: calc(100dvh - 16px) !important;
        max-height: calc(100dvh - 16px) !important;
        display: flex !important;
        flex-direction: column !important;
        overflow: hidden !important;
    }

    #quotesMain .quote-view-layer {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        height: 100% !important;
        overflow: hidden !important;
    }

    #quotesMain .quote-view-layer.active {
        display: flex !important;
        flex-direction: column !important;
        min-height: 0 !important;
    }

    #quotesMain .quote-modal-header {
        flex: 0 0 auto !important;
        padding: var(--space-3) !important;
    }

    #quotesMain #quoteForm,
    #quotesMain #customerQuickForm {
        display: flex !important;
        flex-direction: column !important;
        flex: 1 1 auto !important;
        min-height: 0 !important;
        overflow: hidden !important;
    }

    #quotesMain .quote-document-body,
    #quotesMain #quoteForm > .product-modal-body,
    #quotesMain #customerQuickForm > .product-modal-body,
    #quotesMain .quote-view-layer .product-modal-body {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        overflow-y: auto !important;
        overflow-x: hidden !important;
        -webkit-overflow-scrolling: touch !important;
    }

    #quotesMain .product-modal-footer.quote-modal-footer {
        flex: 0 0 68px !important;
        height: 68px !important;
        min-height: 68px !important;
        max-height: 68px !important;
        padding: 12px !important;
        gap: 8px !important;
        flex-wrap: nowrap !important;
        overflow: hidden !important;
    }

    #quotesMain .modal-footer-actions-left {
        display: flex !important;
        align-items: center !important;
        justify-content: flex-start !important;
        gap: 8px !important;
        flex: 0 0 auto !important;
        max-width: 104px !important;
        overflow: hidden !important;
    }

    #quotesMain .modal-footer-actions-left:empty {
        display: none !important;
    }

    #quotesMain .modal-footer-actions-right {
        display: flex !important;
        gap: 8px !important;
        flex: 1 1 auto !important;
        min-width: 0 !important;
        max-width: 100% !important;
        overflow: hidden !important;
        justify-content: flex-end !important;
    }

    #quotesMain .quote-modal-footer .btn {
        width: 44px !important;
        min-width: 44px !important;
        max-width: 44px !important;
        height: 44px !important;
        min-height: 44px !important;
        padding: 0 !important;
        flex: 0 0 44px !important;
        border-radius: var(--radius-lg) !important;
    }

    #quotesMain .quote-modal-footer .btn span {
        display: none !important;
    }

    #quotesMain .quote-modal-footer .btn svg {
        margin: 0 !important;
        flex-shrink: 0 !important;
    }

    #quotesMain #quoteSaveBtn {
        width: 48px !important;
        min-width: 48px !important;
        max-width: 48px !important;
        flex-basis: 48px !important;
    }

    #quotesMain #quoteFormDeleteBtn,
    #quotesMain .quote-delete-btn,
    #quotesMain .quote-danger-btn {
        color: var(--color-error) !important;
        border-color: rgba(255, 77, 79, 0.55) !important;
        background: rgba(255, 77, 79, 0.05) !important;
    }

    #quotesMain #quoteFormDeleteBtn svg,
    #quotesMain .quote-delete-btn svg,
    #quotesMain .quote-danger-btn svg {
        color: var(--color-error) !important;
        stroke: var(--color-error) !important;
    }

    #quotesMain .quote-doc {
        padding: 22px 16px !important;
    }

    #quotesMain .quote-doc-head,
    #quotesMain .quote-doc-grid {
        display: block !important;
    }

    #quotesMain .quote-doc-cell {
        border-right: 0 !important;
        border-bottom: 1px solid #dbe3ef !important;
    }

    #quotesMain .quote-doc-table th:nth-child(1),
    #quotesMain .quote-doc-table td:nth-child(1) {
        display: none !important;
    }

    #quotesMain .quote-customer-grid,
    #quotesMain .quote-form-grid-main {
        grid-template-columns: 1fr !important;
    }

    #quotesMain .eq-row {
        display: grid !important;
        grid-template-columns: 1fr 1fr !important;
        align-items: stretch !important;
        gap: 12px !important;
        padding: 16px !important;
        overflow: visible !important;
    }

    #quotesMain .eq-col-prod,
    #quotesMain .eq-col-del {
        grid-column: 1 / -1 !important;
    }

    #quotesMain .mobile-label {
        display: block !important;
        font-size: 11px !important;
        font-weight: 700 !important;
        text-transform: uppercase !important;
        color: var(--text-muted) !important;
        margin-bottom: 6px !important;
    }

    #quotesMain .eq-col-del .remove-row-btn {
        width: 100% !important;
        max-width: 100% !important;
        color: var(--color-error) !important;
        border-color: rgba(255, 77, 79, 0.55) !important;
    }

    #quotesMain .eq-col-del .remove-row-btn svg {
        color: var(--color-error) !important;
        stroke: var(--color-error) !important;
    }
}



/* P0.1 MODAL QUOTES - geometría estable y footer siempre visible */
#quotesMain .product-modal-backdrop {
    align-items: center !important;
    justify-content: center !important;
    padding: 24px !important;
    overflow: hidden !important;
}

#quotesMain .quotes-modal-box {
    width: min(1120px, calc(100vw - 48px)) !important;
    max-width: calc(100vw - 48px) !important;
    height: min(860px, calc(100vh - 48px)) !important;
    height: min(860px, calc(100dvh - 48px)) !important;
    max-height: calc(100vh - 48px) !important;
    max-height: calc(100dvh - 48px) !important;
    min-height: min(620px, calc(100vh - 48px)) !important;
    min-height: min(620px, calc(100dvh - 48px)) !important;
    display: flex !important;
    flex-direction: column !important;
    overflow: hidden !important;
}

#quotesMain .quote-view-layer,
#quotesMain .quote-view-layer.active {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    overflow: hidden !important;
}

#quotesMain .quote-view-layer.active {
    display: flex !important;
    flex-direction: column !important;
}

#quotesMain #quoteForm,
#quotesMain #customerQuickForm {
    display: flex !important;
    flex: 1 1 auto !important;
    flex-direction: column !important;
    min-height: 0 !important;
    overflow: hidden !important;
}

#quotesMain .quote-document-body,
#quotesMain #quoteForm > .product-modal-body,
#quotesMain #customerQuickForm > .product-modal-body {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    overflow-y: auto !important;
    overflow-x: hidden !important;
    -webkit-overflow-scrolling: touch !important;
}

#quotesMain .product-modal-footer.quote-modal-footer {
    flex: 0 0 auto !important;
    min-height: 72px !important;
    height: auto !important;
    max-height: none !important;
    overflow: visible !important;
}

#quotesMain #customerSelectorBlock.is-locked .quote-open-customer-create {
    display: none !important;
}

#quotesMain #customerSelectorBlock.is-locked .quote-inline-search {
    grid-template-columns: minmax(0, 1fr) !important;
}

#quotesMain #customerSelectorBlock.is-locked #customerSearch {
    width: 100% !important;
}

#quotesMain #customerSelectorBlock.is-locked .selected-chip {
    width: 100% !important;
    max-width: 100% !important;
}

@media (max-width: 980px), (hover: none) and (pointer: coarse) {
    #quotesMain .product-modal-backdrop {
        align-items: stretch !important;
        padding: 8px !important;
    }

    #quotesMain .quotes-modal-box {
        width: 100% !important;
        max-width: 100% !important;
        height: calc(100vh - 16px) !important;
        height: calc(100dvh - 100px) !important;
        max-height: calc(100vh - 100px) !important;
        max-height: calc(100dvh - 100px) !important;
        min-height: 0 !important;
    }

    #quotesMain .product-modal-footer.quote-modal-footer {
        min-height: 68px !important;
        height: 68px !important;
        max-height: 68px !important;
        overflow: hidden !important;
    }
}

/* Campos comerciales nuevos */
#quotesMain .quote-invoice-field.hidden,
#quotesMain .quote-payment-field.hidden {
    display: none !important;
}


/* P0.1C QUOTES DASHBOARD - sin scroll vertical de página en escritorio
   Regla: el único scroll vertical permitido en el dashboard de cotizaciones
   es el scroll interno de .quotes-table-wrap. */
@media (min-width: 981px) {
    #quotesMain {
        height: 100vh !important;
        max-height: 100vh !important;
        overflow: hidden !important;
    }

    #quotesMain .topbar {
        flex: 0 0 auto !important;
    }

    #quotesMain .quotes-page-shell {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        height: auto !important;
        max-height: none !important;
        display: flex !important;
        flex-direction: column !important;
        overflow: hidden !important;
        padding-top: var(--space-5) !important;
        padding-bottom: var(--space-5) !important;
    }

    #quotesMain .page-header.header-responsive,
    #quotesMain .quotes-filter-bar,
    #quotesMain .quotes-kpis-row {
        flex: 0 0 auto !important;
    }

    #quotesMain .quotes-table-card {
        flex: 1 1 0 !important;
        min-height: 0 !important;
        height: auto !important;
        max-height: none !important;
        display: flex !important;
        flex-direction: column !important;
        overflow: hidden !important;
    }

    #quotesMain .quotes-table-wrap {
        flex: 1 1 auto !important;
        min-height: 0 !important;
        height: auto !important;
        max-height: none !important;
        overflow-y: auto !important;
        overflow-x: hidden !important;
        -webkit-overflow-scrolling: touch !important;
    }

    #quotesMain .quotes-table {
        margin-bottom: 0 !important;
    }
}


/* P14 - cotizaciones: footer homologado con Crear Cliente y modal contenido */
#quotesMain .product-modal-backdrop {
    align-items: center !important;
    justify-content: center !important;
    padding: 24px !important;
    overflow: hidden !important;
}

#quotesMain .quotes-modal-box {
    width: min(1120px, calc(100vw - 48px)) !important;
    max-width: calc(100vw - 48px) !important;
    height: min(700px, calc(100dvh - 100px)) !important;
    min-height: min(560px, calc(100dvh - 100px)) !important;
    max-height: calc(100dvh - 100px) !important;
    display: flex !important;
    flex-direction: column !important;
    overflow: hidden !important;
}

#quotesMain .quote-view-layer.active,
#quotesMain #quoteForm,
#quotesMain #customerQuickForm {
    display: flex !important;
    flex-direction: column !important;
    flex: 1 1 auto !important;
    min-height: 0 !important;
    overflow: hidden !important;
}

#quotesMain .quote-document-body,
#quotesMain #quoteForm > .product-modal-body,
#quotesMain #customerQuickForm > .product-modal-body {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    overflow-y: auto !important;
    overflow-x: hidden !important;
    -webkit-overflow-scrolling: touch !important;
    padding-bottom: var(--space-5) !important;
}

#quotesMain .product-modal-footer.quote-modal-footer {
    flex: 0 0 auto !important;
    min-height: 72px !important;
    height: auto !important;
    max-height: none !important;
    display: flex !important;
    align-items: center !important;
    justify-content: flex-end !important;
    gap: 12px !important;
    flex-wrap: nowrap !important;
    padding: var(--space-4) var(--space-5) !important;
    background: var(--surface-0) !important;
    border-top: 1px solid var(--border-subtle) !important;
    border-radius: 0 0 var(--radius-xl) var(--radius-xl) !important;
    overflow: visible !important;
}

#quotesMain .product-modal-footer.quote-modal-footer:has(.modal-footer-actions-left:not(:empty)) {
    justify-content: space-between !important;
}

#quotesMain .modal-footer-actions-left {
    display: flex !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 10px !important;
    margin-right: auto !important;
    min-width: 0 !important;
    max-width: none !important;
    overflow: visible !important;
    flex: 0 0 auto !important;
}

#quotesMain .modal-footer-actions-left:empty {
    display: none !important;
}

#quotesMain .modal-footer-actions-right {
    width: auto !important;
    min-width: 0 !important;
    margin-left: auto !important;
    display: flex !important;
    align-items: center !important;
    justify-content: flex-end !important;
    gap: 12px !important;
    flex: 0 1 auto !important;
    overflow: visible !important;
}

#quotesMain .quote-modal-footer .btn,
#quotesMain .quote-modal-footer #quoteSaveBtn {
    width: auto !important;
    max-width: none !important;
    min-width: 132px !important;
    height: 48px !important;
    min-height: 48px !important;
    padding: 0 18px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 8px !important;
    flex: 0 0 auto !important;
    white-space: nowrap !important;
    border-radius: var(--radius-lg) !important;
}

#quotesMain #quoteFormDeleteBtn,
#quotesMain .quote-delete-btn,
#quotesMain .quote-danger-btn {
    width: 48px !important;
    min-width: 48px !important;
    max-width: 48px !important;
    padding: 0 !important;
    flex: 0 0 48px !important;
    color: var(--color-error) !important;
    border-color: rgba(255, 77, 79, 0.58) !important;
    background: rgba(255, 77, 79, 0.06) !important;
}

#quotesMain #quoteFormDeleteBtn svg,
#quotesMain .quote-delete-btn svg,
#quotesMain .quote-danger-btn svg {
    color: var(--color-error) !important;
    stroke: var(--color-error) !important;
}

#quotesMain .quote-modal-footer .btn span {
    display: inline-block !important;
    max-width: 18ch !important;
    overflow: hidden !important;
    text-overflow: ellipsis !important;
    white-space: nowrap !important;
}

#quotesMain #quoteFormDeleteBtn span,
#quotesMain .quote-delete-btn span,
#quotesMain .quote-danger-btn span {
    display: none !important;
}

@media (max-width: 760px) {
    #quotesMain .product-modal-backdrop {
        align-items: center !important;
        padding: 8px !important;
    }

    #quotesMain .quotes-modal-box {
        width: calc(100vw - 16px) !important;
        max-width: calc(100vw - 16px) !important;
        height: calc(100dvh - 200px) !important;
        min-height: 0 !important;
        max-height: calc(100dvh - 200px) !important;
    }

    #quotesMain .product-modal-footer.quote-modal-footer {
        min-height: 68px !important;
        height: auto !important;
        max-height: none !important;
        padding: 10px 18px calc(10px + env(safe-area-inset-bottom)) !important;
        gap: 10px !important;
        overflow: visible !important;
    }

    #quotesMain .modal-footer-actions-left,
    #quotesMain .modal-footer-actions-right {
        gap: 10px !important;
    }

    #quotesMain .modal-footer-actions-right {
        flex: 1 1 auto !important;
    }

    #quotesMain .quote-modal-footer .btn,
    #quotesMain .quote-modal-footer #quoteSaveBtn {
        width: auto !important;
        min-width: 104px !important;
        max-width: calc((100vw - 122px) / 2) !important;
        height: 48px !important;
        min-height: 48px !important;
        padding: 0 14px !important;
        flex: 0 1 auto !important;
    }

    #quotesMain #quoteFormDeleteBtn,
    #quotesMain .quote-delete-btn,
    #quotesMain .quote-danger-btn {
        width: 48px !important;
        min-width: 48px !important;
        max-width: 48px !important;
        padding: 0 !important;
        flex: 0 0 48px !important;
    }

    #quotesMain .quote-modal-footer .btn span {
        display: inline-block !important;
        max-width: 12ch !important;
        overflow: hidden !important;
        text-overflow: ellipsis !important;
        white-space: nowrap !important;
    }

    #quotesMain #quoteFormDeleteBtn span,
    #quotesMain .quote-delete-btn span,
    #quotesMain .quote-danger-btn span {
        display: none !important;
    }
}



/* P15 - Cotizaciones: vista previa compacta móvil sin romper footer */
@media (max-width: 760px) {
    #quotesMain #quoteLayerPreview .product-modal-footer.quote-modal-footer {
        justify-content: flex-end !important;
    }

    #quotesMain #quoteLayerPreview .modal-footer-actions-right {
        width: auto !important;
        max-width: 100% !important;
        flex: 0 1 auto !important;
        gap: 8px !important;
        overflow: visible !important;
    }

    #quotesMain #quoteLayerPreview .quote-modal-footer .btn {
        width: 48px !important;
        min-width: 48px !important;
        max-width: 48px !important;
        height: 48px !important;
        min-height: 48px !important;
        padding: 0 !important;
        flex: 0 0 48px !important;
    }

    #quotesMain #quoteLayerPreview .quote-modal-footer .btn span {
        display: none !important;
    }
}


/* =========================================================
   P22 - Quotes: modales homologados a Crear/Editar oportunidad
   Header/footer fijos; body con scroll interno; footer sin
   desplazamientos, con eliminar a la izquierda y acciones a la derecha.
   ========================================================= */
#quotesMain #quotesModalBackdrop.product-modal-backdrop {
    position: fixed !important;
    inset: 0 !important;
    display: none;
    align-items: center !important;
    justify-content: center !important;
    padding: 24px !important;
    overflow: hidden !important;
    background: rgba(0, 0, 0, 0.68) !important;
    backdrop-filter: blur(5px) !important;
    z-index: 1050 !important;
}

#quotesMain #quotesModalBackdrop.product-modal-backdrop.show {
    display: flex !important;
    opacity: 1 !important;
}

#quotesMain #quotesModalBackdrop > .quotes-modal-box {
    width: min(1120px, calc(100vw - 48px)) !important;
    max-width: calc(100vw - 48px) !important;
    height: min(700px, calc(100dvh - 100px)) !important;
    min-height: min(700px, calc(100dvh - 100px)) !important;
    max-height: calc(100dvh - 100px) !important;
    display: flex !important;
    flex-direction: column !important;
    overflow: hidden !important;
    border-radius: var(--radius-xl) !important;
    background: var(--surface-1) !important;
    border: 1px solid var(--border-default) !important;
    box-shadow: var(--shadow-lg) !important;
}

#quotesMain #quotesModalBackdrop .quote-view-layer {
    display: none !important;
    flex: 1 1 auto !important;
    width: 100% !important;
    height: 100% !important;
    min-height: 0 !important;
    overflow: hidden !important;
}

#quotesMain #quotesModalBackdrop .quote-view-layer.active {
    display: flex !important;
    flex-direction: column !important;
}

#quotesMain #quotesModalBackdrop #quoteForm,
#quotesMain #quotesModalBackdrop #customerQuickForm {
    display: flex !important;
    flex: 1 1 auto !important;
    flex-direction: column !important;
    width: 100% !important;
    min-height: 0 !important;
    overflow: hidden !important;
}

#quotesMain #quotesModalBackdrop .quote-modal-header {
    flex: 0 0 auto !important;
    min-height: 78px !important;
    padding: 20px 24px !important;
    display: flex !important;
    flex-direction: row !important;
    align-items: flex-start !important;
    justify-content: space-between !important;
    gap: 14px !important;
    border-bottom: 1px solid var(--border-subtle) !important;
    background: var(--surface-1) !important;
}

#quotesMain #quotesModalBackdrop .quote-modal-title {
    flex: 1 1 auto !important;
    min-width: 0 !important;
}

#quotesMain #quotesModalBackdrop .quote-modal-title .h3,
#quotesMain #quotesModalBackdrop .quote-modal-title h2 {
    line-height: 1.14 !important;
    margin-bottom: 3px !important;
}

#quotesMain #quotesModalBackdrop .quote-document-body,
#quotesMain #quotesModalBackdrop #quoteForm > .product-modal-body,
#quotesMain #quotesModalBackdrop #customerQuickForm > .product-modal-body,
#quotesMain #quotesModalBackdrop .quote-view-layer > .product-modal-body {
    flex: 1 1 auto !important;
    min-height: 0 !important;
    max-height: none !important;
    padding: 20px 24px !important;
    overflow-y: auto !important;
    overflow-x: hidden !important;
    -webkit-overflow-scrolling: touch !important;
    overscroll-behavior: contain !important;
}

#quotesMain #quotesModalBackdrop .product-modal-footer.quote-modal-footer {
    flex: 0 0 auto !important;
    min-height: 76px !important;
    height: auto !important;
    max-height: none !important;
    padding: 14px 20px !important;
    display: flex !important;
    flex-direction: row !important;
    align-items: center !important;
    justify-content: flex-end !important;
    gap: 12px !important;
    flex-wrap: nowrap !important;
    background: var(--surface-0) !important;
    border-top: 1px solid var(--border-subtle) !important;
    border-radius: 0 0 var(--radius-xl) var(--radius-xl) !important;
    overflow: visible !important;
}

#quotesMain #quoteLayerForm .product-modal-footer.quote-modal-footer:has(.modal-footer-actions-left:not(:empty)) {
    justify-content: space-between !important;
}

#quotesMain #quotesModalBackdrop .modal-footer-actions-left {
    flex: 0 0 auto !important;
    min-width: 48px !important;
    margin-right: auto !important;
    display: flex !important;
    align-items: center !important;
    justify-content: flex-start !important;
    gap: 8px !important;
    overflow: visible !important;
}

#quotesMain #quotesModalBackdrop .modal-footer-actions-left:empty {
    display: none !important;
    min-width: 0 !important;
    margin-right: 0 !important;
}

#quotesMain #quoteLayerForm .modal-footer-actions-left:has(#quoteFormDeleteBtn.hidden) {
    display: none !important;
    min-width: 0 !important;
    margin-right: 0 !important;
}

#quotesMain #quoteLayerForm .product-modal-footer.quote-modal-footer:has(#quoteFormDeleteBtn.hidden) {
    justify-content: flex-end !important;
}

#quotesMain #quotesModalBackdrop .modal-footer-actions-right {
    flex: 0 1 auto !important;
    width: auto !important;
    min-width: 0 !important;
    margin-left: auto !important;
    display: flex !important;
    align-items: center !important;
    justify-content: flex-end !important;
    gap: 12px !important;
    overflow: visible !important;
    flex-wrap: nowrap !important;
}

#quotesMain #quotesModalBackdrop .quote-modal-footer .btn,
#quotesMain #quotesModalBackdrop #quoteSaveBtn {
    width: auto !important;
    min-width: 118px !important;
    max-width: none !important;
    height: 48px !important;
    min-height: 48px !important;
    padding: 0 18px !important;
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    gap: 8px !important;
    flex: 0 0 auto !important;
    white-space: nowrap !important;
    text-align: center !important;
    overflow: visible !important;
    text-overflow: clip !important;
    border-radius: var(--radius-lg) !important;
}

#quotesMain #quotesModalBackdrop .modal-footer-actions-right .btn-primary,
#quotesMain #quotesModalBackdrop #quoteSaveBtn {
    min-width: 178px !important;
}

#quotesMain #quotesModalBackdrop .quote-modal-footer .btn span,
#quotesMain #quotesModalBackdrop #quoteSaveBtn span {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    min-width: 0 !important;
    max-width: none !important;
    overflow: visible !important;
    text-overflow: clip !important;
    white-space: nowrap !important;
    text-align: center !important;
}

#quotesMain #quotesModalBackdrop #quoteFormDeleteBtn,
#quotesMain #quotesModalBackdrop .quote-delete-btn,
#quotesMain #quotesModalBackdrop .quote-danger-btn {
    width: 48px !important;
    min-width: 48px !important;
    max-width: 48px !important;
    height: 48px !important;
    min-height: 48px !important;
    padding: 0 !important;
    flex: 0 0 48px !important;
    color: var(--color-error) !important;
    border-color: rgba(255, 77, 79, 0.58) !important;
    background: rgba(255, 77, 79, 0.06) !important;
}

#quotesMain #quotesModalBackdrop #quoteFormDeleteBtn span,
#quotesMain #quotesModalBackdrop .quote-delete-btn span,
#quotesMain #quotesModalBackdrop .quote-danger-btn span {
    display: none !important;
}

@media (max-width: 760px) {
    #quotesMain #quotesModalBackdrop.product-modal-backdrop {
        align-items: center !important;
        justify-content: center !important;
        padding: 12px !important;
    }

    #quotesMain #quotesModalBackdrop > .quotes-modal-box {
        width: calc(100vw - 24px) !important;
        max-width: calc(100vw - 24px) !important;
        height: calc(100dvh - 200px) !important;
        min-height: 0 !important;
        max-height: calc(100dvh - 200px) !important;
        border-radius: 18px !important;
    }

    #quotesMain #quotesModalBackdrop .quote-modal-header {
        min-height: 64px !important;
        padding: 14px 14px !important;
        align-items: flex-start !important;
        gap: 10px !important;
    }

    #quotesMain #quotesModalBackdrop .quote-modal-title .h3,
    #quotesMain #quotesModalBackdrop .quote-modal-title h2 {
        font-size: 1.05rem !important;
        line-height: 1.14 !important;
        margin-bottom: 2px !important;
    }

    #quotesMain #quotesModalBackdrop .quote-modal-title .text-sm {
        font-size: 0.78rem !important;
        line-height: 1.15 !important;
    }

    #quotesMain #quotesModalBackdrop .quote-document-body,
    #quotesMain #quotesModalBackdrop #quoteForm > .product-modal-body,
    #quotesMain #quotesModalBackdrop #customerQuickForm > .product-modal-body,
    #quotesMain #quotesModalBackdrop .quote-view-layer > .product-modal-body {
        padding: 14px !important;
    }

    #quotesMain #quotesModalBackdrop .product-modal-footer.quote-modal-footer {
        min-height: 68px !important;
        padding: 10px 12px calc(10px + env(safe-area-inset-bottom)) !important;
        gap: 10px !important;
        overflow: visible !important;
    }

    #quotesMain #quotesModalBackdrop .modal-footer-actions-left {
        min-width: 44px !important;
        gap: 8px !important;
    }

    #quotesMain #quotesModalBackdrop .modal-footer-actions-right {
        gap: 10px !important;
        flex: 0 1 auto !important;
        min-width: 0 !important;
    }

    #quotesMain #quotesModalBackdrop .quote-modal-footer .btn,
    #quotesMain #quotesModalBackdrop #quoteSaveBtn {
        height: 44px !important;
        min-height: 44px !important;
        min-width: 106px !important;
        max-width: none !important;
        padding: 0 14px !important;
        font-size: 0.94rem !important;
        flex: 0 0 auto !important;
    }

    #quotesMain #quotesModalBackdrop .modal-footer-actions-right .btn-primary,
    #quotesMain #quotesModalBackdrop #quoteSaveBtn {
        min-width: 124px !important;
    }

    #quotesMain #quotesModalBackdrop .quote-modal-footer .btn span,
    #quotesMain #quotesModalBackdrop #quoteSaveBtn span {
        display: inline-flex !important;
        overflow: visible !important;
        text-overflow: clip !important;
        max-width: none !important;
    }

    #quotesMain #quotesModalBackdrop #quoteFormDeleteBtn,
    #quotesMain #quotesModalBackdrop .quote-delete-btn,
    #quotesMain #quotesModalBackdrop .quote-danger-btn {
        width: 44px !important;
        min-width: 44px !important;
        max-width: 44px !important;
        height: 44px !important;
        min-height: 44px !important;
        padding: 0 !important;
        flex: 0 0 44px !important;
    }

    #quotesMain #quoteLayerPreview .modal-footer-actions-right {
        gap: 8px !important;
    }

    #quotesMain #quoteLayerPreview .quote-modal-footer .btn {
        width: 44px !important;
        min-width: 44px !important;
        max-width: 44px !important;
        height: 44px !important;
        min-height: 44px !important;
        padding: 0 !important;
        flex: 0 0 44px !important;
    }

    #quotesMain #quoteLayerPreview .quote-modal-footer .btn span {
        display: none !important;
    }

    #quotesMain #quoteLayerPreview .quote-modal-footer .btn svg,
    #quotesMain #quotesModalBackdrop .quote-danger-btn svg {
        margin: 0 !important;
        flex-shrink: 0 !important;
    }
}

@media (max-width: 980px), (hover: none) and (pointer: coarse) {
    #quotesMain.quotes-deleted-view .quotes-table thead tr,
    #quotesMain.quotes-deleted-view .quotes-table tbody tr.quote-row {
        grid-template-columns: 82px minmax(0, 1fr) 104px 92px !important;
    }

    #quotesMain.quotes-deleted-view .actions-btn-group {
        gap: 6px !important;
        justify-content: center !important;
    }

    #quotesMain.quotes-deleted-view .action-icon-btn {
        width: 38px !important;
        height: 38px !important;
        min-width: 38px !important;
        max-width: 38px !important;
        flex: 0 0 38px !important;
    }

    #quotesMain.quotes-deleted-view .quote-status-quick {
        width: 98px !important;
        min-width: 98px !important;
        max-width: 98px !important;
    }
}


/* Paso 5.2 - Wizard facturación tributaria dentro del modal de cotización */
#quotesMain .quote-billing-body { padding: 0 !important; background: var(--surface-0); }
#quotesMain .quote-billing-content { display: flex; flex-direction: column; gap: 18px; padding: 22px 24px 28px; }
#quotesMain .quote-billing-stepbar { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; }
#quotesMain .quote-billing-step { border: 1px solid var(--border-subtle); border-radius: 999px; padding: 8px 10px; font-size: .78rem; font-weight: 700; text-align: center; color: var(--text-secondary); background: var(--surface-1); }
#quotesMain .quote-billing-step.is-active { color: #fff; background: var(--primary-600, #0ea5e9); border-color: var(--primary-600, #0ea5e9); }
#quotesMain .quote-billing-card { border: 1px solid var(--border-subtle); border-radius: 18px; padding: 18px; background: var(--surface-1); }
#quotesMain .quote-billing-card h3 { margin: 0 0 12px; font-size: 1.05rem; }
#quotesMain .quote-billing-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
#quotesMain .quote-billing-grid.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
#quotesMain .quote-billing-grid .span-2 { grid-column: span 2; }
#quotesMain .quote-billing-readonly { display: block; border: 1px solid var(--border-subtle); border-radius: 12px; padding: 10px 12px; min-height: 44px; background: #fff; }
#quotesMain .quote-billing-readonly .label { margin: 0 0 3px; font-size: .72rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-secondary); }
#quotesMain .quote-billing-readonly strong, #quotesMain .quote-billing-readonly span.value { display: block; min-width: 0; overflow-wrap: anywhere; }
#quotesMain .quote-billing-table-wrap { overflow: auto; border: 1px solid var(--border-subtle); border-radius: 14px; background: #fff; }
#quotesMain .quote-billing-detail-doc { background:#fff; }
#quotesMain .quote-billing-detail-doc .quote-doc-table { margin-top: 4px; }
#quotesMain .quote-billing-doc-bottom { margin-top: 18px; }
#quotesMain .quote-billing-table { width: 100%; border-collapse: collapse; min-width: 760px; }
#quotesMain .quote-billing-table th, #quotesMain .quote-billing-table td { padding: 10px 12px; border-bottom: 1px solid var(--border-subtle); text-align: left; vertical-align: top; }
#quotesMain .quote-billing-table th { font-size: .74rem; text-transform: uppercase; letter-spacing: .04em; color: var(--text-secondary); background: var(--surface-2); }
#quotesMain .quote-billing-summary { margin-left: auto; width: min(420px, 100%); display: grid; gap: 6px; }
#quotesMain .quote-billing-summary div { display: flex; justify-content: space-between; gap: 12px; }
#quotesMain .quote-billing-alert { border-radius: 16px; padding: 16px 18px; border: 1px solid var(--border-subtle); background: var(--surface-1); }
#quotesMain .quote-billing-alert.success { border-color: rgba(34,197,94,.35); background: rgba(34,197,94,.08); }
#quotesMain .quote-billing-alert.error { border-color: rgba(239,68,68,.35); background: rgba(239,68,68,.08); }
#quotesMain .quote-billing-alert.warning { border-color: rgba(245,158,11,.35); background: rgba(245,158,11,.08); }
#quotesMain .quote-billing-alert.draft { border-color: rgba(14,165,233,.32); background: rgba(14,165,233,.08); }
#quotesMain .quote-billing-result-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
#quotesMain .quote-billing-pdf-frame { width: 100%; height: min(70vh, 680px); min-height: 460px; border: 1px solid var(--border-subtle); border-radius: 14px; background: #fff; }
#quotesMain .quote-billing-confirm-box { display: grid; gap: 12px; }
#quotesMain .quote-billing-confirm-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
#quotesMain .quote-billing-email-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
#quotesMain .quote-billing-email-grid .span-2 { grid-column: span 2; }
#quotesMain .quote-billing-email-options { display: flex; flex-wrap: wrap; gap: 10px; }
#quotesMain .quote-billing-email-options label { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--border-subtle); background: #fff; border-radius: 12px; padding: 10px 12px; font-weight: 700; }
#quotesMain #quoteBillingBtn[disabled] { cursor: not-allowed; opacity: .6; }
@media (max-width: 820px) {
    #quotesMain .quote-billing-stepbar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    #quotesMain .quote-billing-grid, #quotesMain .quote-billing-grid.three, #quotesMain .quote-billing-result-grid, #quotesMain .quote-billing-confirm-grid, #quotesMain .quote-billing-email-grid { grid-template-columns: 1fr; }
    #quotesMain .quote-billing-grid .span-2 { grid-column: auto; }
    #quotesMain .quote-billing-content { padding: 16px; }
    #quotesMain .quote-billing-pdf-frame { min-height: 420px; height: 62vh; }
}

</style>

<script>
(function () {
    function root() { return document.getElementById('quotesMain'); }
    function jsonFrom(id, fallback) { const el = document.getElementById(id); if (!el) return fallback; try { return JSON.parse(el.textContent || JSON.stringify(fallback)); } catch (e) { return fallback; } }
    function initState() { window.ICY_QUOTES_DATA = jsonFrom('quotesPayload', []); window.ICY_QUOTES_OPPORTUNITIES = jsonFrom('quotesOpportunitiesPayload', []); window.ICY_QUOTES_STATE = { currentQuoteId: 0, formMode: 'create', itemIndex: 0, noticeTimer: null, customerTimer: null, productTimer: null }; }
    function money(n) { return new Intl.NumberFormat('es-CL', { style: 'currency', currency: 'CLP', maximumFractionDigits: 0 }).format(Number(n || 0)); }
    function esc(v) { return String(v ?? '').replace(/[&<>"']/g, function (m) { return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[m]; }); }
    function compactQuoteNumber(value) { return String(value || '').replace(/^([A-Z]+)-\d{4}-(\d+)$/, '$1-$2'); }
    function badgeClass(status) { if (['Aceptada', 'Pagada'].includes(status)) return 'badge-success'; if (['Enviada', 'Facturada', 'Esperando pago'].includes(status)) return 'badge-warning'; if (['Rechazada', 'Vencida', 'Anulada'].includes(status)) return 'badge-error'; return ''; }
    function statusOptionsHtml(current) { return (window.ICY_QUOTES_ALLOWED_STATUSES || ['Borrador', 'Enviada', 'Aceptada', 'Facturada', 'Esperando pago', 'Pagada', 'Rechazada', 'Vencida', 'Anulada']).map(function (status) { return `<option value="${esc(status)}"${status === current ? ' selected' : ''}>${esc(status)}</option>`; }).join(''); }
    const QUOTE_UNITS = ['unidad', 'mL', 'L', 'g', 'kg', 'pack', 'caja', 'servicio'];
    function cleanUnit(value) { return QUOTE_UNITS.includes(String(value || '').trim()) ? String(value).trim() : 'unidad'; }
    function unitSelectHtml(name, value) { const current = cleanUnit(value); return `<select class="input it-unit" name="${esc(name)}">${QUOTE_UNITS.map(function(unit) { return `<option value="${esc(unit)}"${unit === current ? ' selected' : ''}>${esc(unit)}</option>`; }).join('')}</select>`; }
    function statusSelectHtml(q) { const current = q.status || 'Borrador'; return `<select class="quote-status-quick ${badgeClass(current)}" data-quote-id="${esc(q.id)}" data-current="${esc(current)}" aria-label="Actualizar estado de cotización">${statusOptionsHtml(current)}</select>`; }
    function quoteById(id) { return (window.ICY_QUOTES_DATA || []).find(function (q) { return Number(q.id) === Number(id); }); }
    function opportunityById(id) { return (window.ICY_QUOTES_OPPORTUNITIES || []).find(function (o) { return Number(o.id) === Number(id); }); }
    function modal() { return document.getElementById('quotesModalBackdrop'); }
    function setLayer(id) { document.querySelectorAll('#quotesModalBackdrop .quote-view-layer').forEach(function (layer) { layer.classList.remove('active'); }); const layer = document.getElementById(id); if (layer) { layer.classList.add('active'); const body = layer.querySelector('.product-modal-body'); if (body) body.scrollTop = 0; const modalBox = layer.closest('.quotes-modal-box'); if (modalBox) modalBox.dataset.activeLayer = id; } if (window.lucide) lucide.createIcons(); }
    function openModal() { const m = modal(); if (!m) return; m.style.display = 'flex'; requestAnimationFrame(function () { m.classList.add('show'); }); m.setAttribute('aria-hidden', 'false'); document.body.style.overflow = 'hidden'; }
    window.closeQuotesModal = function () { const m = modal(); if (!m) { document.body.style.overflow = ''; return; } m.classList.remove('show'); m.setAttribute('aria-hidden', 'true'); document.body.style.overflow = ''; setTimeout(function () { if (!m.classList.contains('show')) { m.style.display = 'none'; } }, 180); };
    function showNotice(message) { const notice = document.getElementById('quoteSaveNotice'); if (!notice) return; notice.textContent = message; notice.classList.remove('hidden'); if (window.ICY_QUOTES_STATE.noticeTimer) clearTimeout(window.ICY_QUOTES_STATE.noticeTimer); window.ICY_QUOTES_STATE.noticeTimer = setTimeout(function () { notice.classList.add('hidden'); }, 4200); }
    function hideNotice() { const notice = document.getElementById('quoteSaveNotice'); if (notice) notice.classList.add('hidden'); }
    function netAmount(q) { return Number(q.subtotal || 0) - Number(q.discount_amount || 0); }
    function renderPreviewDoc(q) {
        const rows = (q.items || []).map(function (it) { const sku = it.sku || ''; const displayName = it.display_name || String(it.name || '').replace(/\s*\(SKU:\s*[^)]+\)\s*$/i, ''); return `<tr><td><span class="mono" style="color:#536b8f;">${esc(sku || '—')}</span></td><td><strong>${esc(displayName)}</strong></td><td class="text-right">${esc(it.quantity || 1)}</td><td>${esc(cleanUnit(it.unit))}</td><td class="text-right">${money(it.price_unit)}</td><td class="text-right"><strong>${money(it.total_item)}</strong></td></tr>`; }).join('');
        return `<article class="quote-doc"><div class="quote-doc-head"><h2>Cotización ${esc(q.quote_number)}</h2><span class="badge ${badgeClass(q.status)}">${esc(String(q.status || 'Borrador').toUpperCase())}</span></div><div class="quote-doc-grid"><div class="quote-doc-cell"><span class="quote-doc-label">Razón social</span><strong class="quote-doc-value">${esc(q.customer_name || '')}</strong></div><div class="quote-doc-cell"><span class="quote-doc-label">Fecha</span><span class="quote-doc-value">${esc(q.created_at || '-')}</span></div><div class="quote-doc-cell"><span class="quote-doc-label">R.U.T.</span><span class="quote-doc-value">${esc(q.customer_tax_id || '—')}</span></div><div class="quote-doc-cell"><span class="quote-doc-label">Email cliente</span><span class="quote-doc-value">${esc(q.email || 'Sin email')}</span></div><div class="quote-doc-cell"><span class="quote-doc-label">Teléfono</span><span class="quote-doc-value">${esc(q.phone || 'Sin teléfono')}</span></div><div class="quote-doc-cell"><span class="quote-doc-label">Vendedor</span><span class="quote-doc-value">${esc(window.ICY_USER_NAME || 'Sistema')}</span></div></div><table class="quote-doc-table"><thead><tr><th>SKU</th><th>Glosa / Descripción</th><th class="text-right">Cant.</th><th>Unidad</th><th class="text-right">Prc. Unit</th><th class="text-right">Monto</th></tr></thead><tbody>${rows || '<tr><td colspan="6">Sin productos asociados.</td></tr>'}</tbody></table><div class="quote-doc-bottom"><div><span class="quote-doc-label">Condiciones comerciales</span><div class="quote-doc-conditions">${esc(q.notes || 'Sin condiciones comerciales adicionales especificadas.')}</div></div><div class="quote-doc-totalbox"><div><span>Subtotal Bruto:</span><strong>${money(q.subtotal)}</strong></div><div><span>Monto Neto:</span><strong>${money(netAmount(q))}</strong></div><div><span>Monto Exento:</span><strong>${money(0)}</strong></div><div><span>IVA (19%):</span><strong>${money(q.iva)}</strong></div><hr><div class="grand"><span>Total General:</span><strong>${money(q.total)}</strong></div></div></div></article>`;
    }
    window.openQuotePreview = function (id, noticeMessage) {
        const q = quoteById(id);
        if (!q) return;

        window.ICY_QUOTES_STATE.currentQuoteId = Number(q.id);
        document.getElementById('quotePreviewDocument').innerHTML = renderPreviewDoc(q);

        const publicUrl = `${window.location.origin}/modules/crm/quote_view.php?id=${q.id}`;
        const message = `Hola, te comparto la cotización ${q.quote_number} de ICYLAB por ${money(q.total)}: ${publicUrl}`;
        const pdfBtn = document.getElementById('quotePdfBtn');
        const waBtn = document.getElementById('quoteWhatsAppBtn');
        const mailBtn = document.getElementById('quoteMailBtn');
        const billingBtn = document.getElementById('quoteBillingBtn');
        const formDeleteBtn = document.getElementById('quoteFormDeleteBtn');

        if (formDeleteBtn) {
            formDeleteBtn.dataset.quoteId = q.id;
            formDeleteBtn.classList.toggle('hidden', !!q.is_deleted);
        }

        const editBtn = document.querySelector('#quoteLayerPreview .quote-open-edit');
        if (editBtn) editBtn.classList.toggle('hidden', !!q.is_deleted);

        if (pdfBtn) pdfBtn.href = publicUrl;

        if (waBtn) {
            if (q.whatsapp_phone) {
                waBtn.href = `https://wa.me/${q.whatsapp_phone}?text=${encodeURIComponent(message)}`;
                waBtn.classList.remove('hidden');
            } else {
                waBtn.href = '#';
                waBtn.classList.add('hidden');
            }
        }

        if (mailBtn) {
            if (q.email) {
                mailBtn.href = `mailto:${encodeURIComponent(q.email)}?subject=${encodeURIComponent('Cotización ICYLAB ' + q.quote_number)}&body=${encodeURIComponent(message)}`;
                mailBtn.classList.remove('hidden');
            } else {
                mailBtn.href = '#';
                mailBtn.classList.add('hidden');
            }
        }

        if (billingBtn) {
            const hasLinkedTaxDocument = String(q.facto_document_id || '').trim() !== '' || String(q.invoice_number || '').trim() !== '';
            const status = String(q.status || '').trim();
            const isAllowedBillingStatus = ['Aceptada', 'Pagada'].includes(status);
            const hasDraftTaxDocument = Number(q.billing_draft_log_id || 0) > 0;
            const canStartBilling = !!window.ICY_QUOTES_CAN_BILL && isAllowedBillingStatus && !hasLinkedTaxDocument && !q.is_deleted;
            const canReviewBilling = !!window.ICY_QUOTES_CAN_BILL && hasLinkedTaxDocument;
            const shouldShowBlocked = !!window.ICY_QUOTES_CAN_BILL && !hasLinkedTaxDocument && !q.is_deleted;

            billingBtn.dataset.quoteId = q.id;
            billingBtn.disabled = shouldShowBlocked && !canStartBilling;
            billingBtn.classList.toggle('hidden', !(canStartBilling || canReviewBilling || shouldShowBlocked));
            billingBtn.classList.toggle('btn-primary', canStartBilling);
            billingBtn.classList.toggle('btn-outline', canReviewBilling || (shouldShowBlocked && !canStartBilling));
            billingBtn.classList.toggle('quote-billing-blocked', shouldShowBlocked && !canStartBilling);
            billingBtn.title = canStartBilling
                ? (hasDraftTaxDocument ? 'Continuar borrador tributario' : 'Facturar cotización')
                : (canReviewBilling ? 'Ver documento tributario vinculado' : 'Disponible sólo para cotizaciones Aceptadas o Pagadas');

            const icon = billingBtn.querySelector('i');
            const label = billingBtn.querySelector('span');
            if (icon) icon.setAttribute('data-lucide', canReviewBilling ? 'receipt-text' : (hasDraftTaxDocument ? 'file-clock' : 'file-text'));
            if (label) label.textContent = canReviewBilling ? 'Ver facturación' : (hasDraftTaxDocument ? 'Continuar borrador' : 'Facturar');
        }

        setLayer('quoteLayerPreview');
        if (noticeMessage) showNotice(noticeMessage); else hideNotice();
        openModal();
    };
    function setCustomerChip(customer, locked, reason) { const chip = document.getElementById('selectedCustomerChip'); const input = document.getElementById('formCustomerId'); if (!chip || !input || !customer) return; input.value = customer.id || 0; chip.innerHTML = `Cliente seleccionado: <strong>${esc(customer.display_name || customer.legal_name || customer.customer_name || '')}</strong>${customer.customer_tax_id ? ' · ' + esc(customer.customer_tax_id) : ''}${locked ? '<span class="text-muted"> · bloqueado</span>' : ''}${reason ? '<div class="text-muted" style="margin-top:2px;">' + esc(reason) + '</div>' : ''}`; chip.classList.remove('hidden'); chip.classList.toggle('is-locked', !!locked); }
    function applyCustomerLock(locked) { const search = document.getElementById('customerSearch'); const btn = document.querySelector('.quote-open-customer-create'); const block = document.getElementById('customerSelectorBlock'); if (block) block.classList.toggle('is-locked', !!locked); if (search) { search.disabled = !!locked; search.placeholder = locked ? 'Cliente bloqueado para esta cotización' : 'Buscar cliente por nombre o RUT'; if (locked) search.value = ''; } if (btn) { btn.disabled = !!locked; btn.setAttribute('aria-hidden', locked ? 'true' : 'false'); btn.style.opacity = locked ? '0.45' : ''; btn.style.pointerEvents = locked ? 'none' : ''; } const results = document.getElementById('customerResults'); if (results) { results.innerHTML = ''; results.classList.remove('show'); } }
    function clearForm() { const form = document.getElementById('quoteForm'); if (form) form.reset(); document.getElementById('formQuoteId').value = 0; document.getElementById('formCustomerId').value = 0; document.getElementById('formOpportunityId').value = 0; document.getElementById('quoteItems').innerHTML = ''; document.getElementById('opportunitySelect').disabled = false; const invClear = document.getElementById('formInvoiceIssueDate'); if (invClear) invClear.value = ''; const payClear = document.getElementById('formPaymentDate'); if (payClear) payClear.value = ''; toggleInvoiceFields(); document.getElementById('opportunitySelect').value = 0; document.getElementById('selectedCustomerChip').classList.add('hidden'); document.getElementById('selectedCustomerChip').classList.remove('is-locked'); document.getElementById('customerSearch').value = ''; document.getElementById('productSearch').value = ''; document.getElementById('customerResults').classList.remove('show'); document.getElementById('productResults').classList.remove('show'); window.ICY_QUOTES_STATE.itemIndex = 0; applyCustomerLock(false); calculateQuote(); }
    const BILLING_DOCUMENT_TYPES = [
        { key: 'invoice_electronic', label: 'Factura electrónica afecta emitida' },
        { key: 'invoice_exempt_electronic', label: 'Factura exenta electrónica emitida' },
        { key: 'invoice_export_electronic', label: 'Factura de exportación electrónica emitida' }
    ];
    const BILLING_TERMS = [
        { key: 'contado', label: 'Contado' },
        { key: 'credito_7', label: 'Crédito 7 días' },
        { key: 'credito_15', label: 'Crédito 15 días' },
        { key: 'credito_30', label: 'Crédito 30 días' },
        { key: 'credito_45', label: 'Crédito 45 días' },
        { key: 'otro', label: 'Otra condición' }
    ];

    const BILLING_PAYMENT_METHODS = (window.ICY_BILLING_PAYMENT_METHODS || []).map(function (method) {
        return { id: method.id || 0, key: method.method_key || method.key || '', label: method.method_name || method.label || 'Método de pago', type: method.method_type || '', is_default: Number(method.is_default || 0) === 1 };
    });
    const BILLING_CASH_ACCOUNTS = (window.ICY_BILLING_CASH_ACCOUNTS || []).map(function (account) {
        return { id: account.id || 0, key: account.account_key || account.key || '', label: account.account_name || account.label || 'Caja/cuenta', type: account.account_type || '', currency: account.currency_code || 'CLP', is_default: Number(account.is_default || 0) === 1 };
    });
    function billingPaymentMethodOptionsHtml() {
        let html = '<option value="">Seleccionar método</option>';
        BILLING_PAYMENT_METHODS.forEach(function (method) { const value = method.id ? String(method.id) : method.key; html += '<option value="' + esc(value) + '" data-key="' + esc(method.key) + '" data-label="' + esc(method.label) + '"' + (method.is_default ? ' data-default="1"' : '') + '>' + esc(method.label) + '</option>'; });
        return html;
    }
    function billingCashAccountOptionsHtml() {
        let html = '<option value="">Seleccionar caja/cuenta</option>';
        BILLING_CASH_ACCOUNTS.forEach(function (account) { const value = account.id ? String(account.id) : account.key; const suffix = account.currency ? ' · ' + account.currency : ''; html += '<option value="' + esc(value) + '" data-key="' + esc(account.key) + '" data-label="' + esc(account.label) + '"' + (account.is_default ? ' data-default="1"' : '') + '>' + esc(account.label + suffix) + '</option>'; });
        return html;
    }
    function todayISO() { return new Date().toISOString().slice(0, 10); }
    function billingContent() { return document.getElementById('quoteBillingContent'); }
    function billingActions() { return document.getElementById('quoteBillingActions'); }
    function billingTitle(title, subtitle) { const t = document.getElementById('quoteBillingTitle'); const st = document.getElementById('quoteBillingSubtitle'); if (t) t.textContent = title || 'Facturación tributaria'; if (st) st.textContent = subtitle || 'Emisión vinculada a cotización'; }
    function renderBillingSteps(active) { const steps = [['prepare','1. Documento'],['detail','2. Receptor y detalle'],['preview','3. Preview'],['result','4. Resultado']]; return '<div class="quote-billing-stepbar">' + steps.map(function (step) { return '<div class="quote-billing-step ' + (step[0] === active ? 'is-active' : '') + '">' + esc(step[1]) + '</div>'; }).join('') + '</div>'; }
    function documentTypeLabel(key) { const found = BILLING_DOCUMENT_TYPES.find(function (item) { return item.key === key; }); return found ? found.label : 'Documento tributario'; }
    function billingConfig() { const doc = document.getElementById('quoteBillingDocumentType'); const env = document.getElementById('quoteBillingEnvironment'); const issue = document.getElementById('quoteBillingIssueDate'); const terms = document.getElementById('quoteBillingPaymentTerms'); return { documentKey: doc ? doc.value : 'invoice_electronic', environment: env ? env.value : 'sandbox', issueDate: issue ? issue.value : todayISO(), paymentTerms: terms ? terms.value : 'contado' }; }
    function setBillingActions(html) { const actions = billingActions(); if (actions) actions.innerHTML = html || ''; if (window.lucide) lucide.createIcons(); }
    function renderReadonly(label, value) { return '<div class="quote-billing-readonly"><div class="label">' + esc(label) + '</div><strong>' + esc(value || '—') + '</strong></div>'; }
    function hasNumberValue(value) { return value !== undefined && value !== null && value !== '' && !Number.isNaN(Number(value)); }
    function billingCalcValue(calculation, key, fallback) { return hasNumberValue(calculation && calculation[key]) ? Number(calculation[key]) : Number(fallback || 0); }
    function renderBillingQuoteDetail(q) {
        const rows = (q.items || []).map(function (it) {
            const sku = it.sku || '';
            const displayName = it.display_name || String(it.name || '').replace(/\s*\(SKU:\s*[^)]+\)\s*$/i, '');
            return '<tr>' +
                '<td><span class="mono" style="color:#536b8f;">' + esc(sku || '—') + '</span></td>' +
                '<td><strong>' + esc(displayName || 'Producto') + '</strong></td>' +
                '<td class="text-right">' + esc(it.quantity || 1) + '</td>' +
                '<td>' + esc(cleanUnit(it.unit)) + '</td>' +
                '<td class="text-right">' + money(it.price_unit) + '</td>' +
                '<td class="text-right"><strong>' + money(it.total_item) + '</strong></td>' +
                '</tr>';
        }).join('');
        return '<div class="quote-billing-card quote-billing-detail-doc"><h3>Detalle cotizado</h3>' +
            '<table class="quote-doc-table quote-billing-doc-table"><thead><tr><th>SKU</th><th>Glosa / Descripción</th><th class="text-right">Cant.</th><th>Unidad</th><th class="text-right">Prc. Unit</th><th class="text-right">Monto</th></tr></thead><tbody>' +
            (rows || '<tr><td colspan="6">Sin productos asociados.</td></tr>') +
            '</tbody></table>' +
            '<div class="quote-doc-bottom quote-billing-doc-bottom"><div><span class="quote-doc-label">Condiciones comerciales</span><div class="quote-doc-conditions">' + esc(q.notes || 'Sin condiciones comerciales adicionales especificadas.') + '</div></div>' +
            '<div class="quote-doc-totalbox"><div><span>Subtotal Bruto:</span><strong>' + money(q.subtotal) + '</strong></div><div><span>Monto Neto:</span><strong>' + money(netAmount(q)) + '</strong></div><div><span>Monto Exento:</span><strong>' + money(0) + '</strong></div><div><span>IVA (19%):</span><strong>' + money(q.iva) + '</strong></div><hr><div class="grand"><span>Total General:</span><strong>' + money(q.total) + '</strong></div></div></div>' +
            '</div>';
    }
    function normalizeValidationPayload(data) {
        if (!data) return null;
        if (data.result) return data.result;
        if (data.validation) return data.validation;
        return data;
    }

    function hasBillingDraft(q) {
        return q && Number(q.billing_draft_log_id || 0) > 0 && !(String(q.facto_document_id || '').trim() || String(q.invoice_number || '').trim());
    }
    function draftDocumentKey(q) {
        return (q && q.billing_draft_document_key) ? q.billing_draft_document_key : 'invoice_electronic';
    }
    function draftEnvironment(q) {
        return (q && q.billing_draft_environment) ? q.billing_draft_environment : 'sandbox';
    }
    function draftIssueDate(q) {
        return (q && q.billing_draft_issue_date) ? String(q.billing_draft_issue_date).slice(0, 10) : todayISO();
    }
    function draftPaymentTerms(q) {
        return (q && q.billing_draft_payment_terms) ? q.billing_draft_payment_terms : 'contado';
    }
    function billingDraftAlert(q) {
        if (!hasBillingDraft(q)) return '';
        let body = '<strong>Borrador tributario vinculado.</strong><br>Esta cotización tiene una preparación tributaria guardada';
        if (q.billing_draft_updated_at) body += ' · actualizado ' + esc(q.billing_draft_updated_at);
        body += '.';
        if (q.billing_draft_document_number) body += '<br>Folio preview/borrador: ' + esc(q.billing_draft_document_number);
        if (q.billing_draft_error_message) body += '<br><span class="text-secondary">Pendiente: ' + esc(q.billing_draft_error_message) + '</span>';
        return '<div class="quote-billing-alert draft">' + body + '</div>';
    }
    function renderBillingPrepare(q, message) {
        billingTitle('Facturar cotización', (q.quote_number || '') + ' · flujo vinculado');
        const content = billingContent();
        if (!content) return;
        const allowed = ['Aceptada', 'Pagada'].includes(String(q.status || '').trim());
        const linked = String(q.facto_document_id || '').trim() !== '' || String(q.invoice_number || '').trim() !== '';
        let html = renderBillingSteps('prepare');
        if (message) html += '<div class="quote-billing-alert success">' + esc(message) + '</div>';
        if (linked) {
            html += '<div class="quote-billing-alert success"><strong>Documento tributario vinculado.</strong><br>Esta cotización ya cuenta con documento asociado.</div><div class="quote-billing-card"><h3>Datos disponibles</h3><div class="quote-billing-grid">' + renderReadonly('Factura / folio', q.invoice_number || 'Sin folio local') + renderReadonly('FACTO document_id', q.facto_document_id || 'No informado') + renderReadonly('Estado cotización', q.status || 'Sin estado') + renderReadonly('Total', money(q.total)) + '</div></div>';
            if (q.billing_pdf_url) {
                html += '<div class="quote-billing-card"><h3>PDF factura asociada</h3><iframe class="quote-billing-pdf-frame" src="' + esc(q.billing_pdf_url) + '"></iframe></div>';
            } else {
                html += '<div class="quote-billing-alert warning"><strong>PDF no disponible.</strong><br>La cotización tiene documento tributario vinculado, pero no se encontró PDF asociado en billing_issue_logs.</div>';
            }
            content.innerHTML = html;
            setBillingActions('<button type="button" class="btn btn-outline quote-modal-close"><i data-lucide="x"></i><span>Cerrar</span></button><button type="button" class="btn btn-primary quote-billing-email-layer"><i data-lucide="mail"></i><span>Enviar correo</span></button>');
            return;
        }
        if (!allowed || q.is_deleted) {
            html += '<div class="quote-billing-alert error"><strong>No se puede iniciar facturación.</strong><br>';
            if (!allowed) html += 'La cotización debe estar Aceptada o Pagada. Estado actual: ' + esc(q.status || 'Sin estado') + '.<br>';
            if (q.is_deleted) html += 'La cotización está eliminada.<br>';
            html += '</div>';
            content.innerHTML = html;
            setBillingActions('<button type="button" class="btn btn-outline quote-modal-close"><i data-lucide="x"></i><span>Cerrar</span></button>');
            return;
        }
        html += billingDraftAlert(q);
        html += '<div class="quote-billing-card"><h3>Datos del documento</h3><div class="quote-billing-grid three">';
        html += '<label><span class="label">Tipo documento</span><select class="input" id="quoteBillingDocumentType">' + BILLING_DOCUMENT_TYPES.map(function (dt) { return '<option value="' + esc(dt.key) + '" ' + (dt.key === draftDocumentKey(q) ? 'selected' : '') + '>' + esc(dt.label) + '</option>'; }).join('') + '</select></label>';
        html += '<label><span class="label">Ambiente</span><select class="input" id="quoteBillingEnvironment"><option value="sandbox" ' + (draftEnvironment(q) === 'sandbox' ? 'selected' : '') + '>Sandbox</option><option value="production" disabled>Producción bloqueada</option></select></label>';
        html += '<label><span class="label">Fecha emisión</span><input class="input" type="date" id="quoteBillingIssueDate" value="' + esc(draftIssueDate(q)) + '"></label>';
        html += '<label><span class="label">Condición de pago</span><select class="input" id="quoteBillingPaymentTerms">' + BILLING_TERMS.map(function (term) { return '<option value="' + esc(term.key) + '" ' + (term.key === draftPaymentTerms(q) ? 'selected' : '') + '>' + esc(term.label) + '</option>'; }).join('') + '</select></label>';
        html += renderReadonly('Folio cotización', q.quote_number || '');
        html += renderReadonly('Total cotizado', money(q.total));
        html += '</div></div><div class="quote-billing-card"><h3>Reglas del flujo vinculado</h3><p class="text-secondary">El receptor, productos y montos quedan bloqueados a la cotización seleccionada. Para cambiar datos comerciales, vuelve y edita la cotización antes de emitir.</p></div>';
        content.innerHTML = html;
        setBillingActions('<button type="button" class="btn btn-outline quote-billing-save-draft"><i data-lucide="save"></i><span>Guardar borrador</span></button><button type="button" class="btn btn-primary quote-billing-continue"><i data-lucide="arrow-right"></i><span>Continuar</span></button>');
    }
    function renderBillingDetail(q) {
        billingTitle('Receptor y detalle', (q.quote_number || '') + ' · revisión previa');
        const content = billingContent();
        if (!content) return;
        let html = renderBillingSteps('detail');
        html += '<div class="quote-billing-card"><h3>Receptor</h3><div class="quote-billing-grid">' +
            renderReadonly('RUT', q.customer_tax_id || 'Sin RUT') +
            renderReadonly('Razón social', q.customer_name || 'Sin razón social') +
            renderReadonly('Email DTE / cliente', q.email || 'Sin email') +
            renderReadonly('Teléfono', q.phone || 'Sin teléfono') +
            '</div><p class="text-secondary mt-2">El receptor no se puede cambiar en este flujo. Si falta información tributaria, edita el cliente y vuelve a validar.</p></div>';
        html += renderBillingQuoteDetail(q);
        content.innerHTML = html;
        setBillingActions('<button type="button" class="btn btn-outline quote-billing-save-draft"><i data-lucide="save"></i><span>Guardar borrador</span></button><button type="button" class="btn btn-primary quote-billing-validate"><i data-lucide="shield-check"></i><span>Validar</span></button>');
    }
    function renderValidationResult(q, data) {
        const validation = normalizeValidationPayload(data);
        if (!validation) {
            renderBillingError('La respuesta no contiene validación tributaria.');
            return;
        }
        const ok = !!validation.ok;
        const calculation = validation.calculation || {};
        const errors = Array.isArray(validation.errors) ? validation.errors.slice() : [];
        const warnings = Array.isArray(validation.warnings) ? validation.warnings.slice() : [];
        if (!ok && !errors.length) {
            if (validation.message) errors.push(validation.message);
            else if (validation.error) errors.push(validation.error);
            else errors.push('La validación fue bloqueada, pero el servicio no devolvió el detalle del bloqueo. Revisa el log de billing_issue_logs o vuelve a validar.');
        }
        const subtotalValue = billingCalcValue(calculation, 'subtotal_before_global_discount', q.subtotal);
        const globalDiscountValue = billingCalcValue(calculation, 'global_discount_amount', q.discount_amount || 0);
        const taxableValue = billingCalcValue(calculation, 'taxable_net_amount', netAmount(q));
        const exemptValue = billingCalcValue(calculation, 'exempt_net_amount', 0);
        const ivaValue = billingCalcValue(calculation, 'taxes_amount', q.iva);
        const totalValue = billingCalcValue(calculation, 'total_amount', q.total);
        billingTitle(ok ? 'Validación aprobada' : 'Validación bloqueada', (q.quote_number || '') + ' · ' + documentTypeLabel(billingConfig().documentKey));
        let html = renderBillingSteps('detail');
        html += '<div class="quote-billing-alert ' + (ok ? 'success' : 'error') + '"><strong>' + (ok ? 'Preparación OK' : 'No se puede emitir aún') + '</strong><br>' + esc(documentTypeLabel(billingConfig().documentKey)) + ' · ' + esc(q.quote_number || '') + ' · sandbox</div>';
        if (errors.length) html += '<div class="quote-billing-card"><h3>Bloqueos</h3><ul>' + errors.map(function (e) { return '<li>' + esc(e) + '</li>'; }).join('') + '</ul></div>';
        if (warnings.length) html += '<div class="quote-billing-card"><h3>Advertencias</h3><ul>' + warnings.map(function (e) { return '<li>' + esc(e) + '</li>'; }).join('') + '</ul></div>';
        html += '<div class="quote-billing-result-grid"><div class="quote-billing-card"><h3>Montos recalculados</h3><div class="quote-billing-summary"><div><span>Subtotal</span><strong>' + money(subtotalValue) + '</strong></div><div><span>Descuento global</span><strong>' + money(globalDiscountValue) + '</strong></div><div><span>Neto afecto</span><strong>' + money(taxableValue) + '</strong></div><div><span>Neto exento</span><strong>' + money(exemptValue) + '</strong></div><div><span>IVA</span><strong>' + money(ivaValue) + '</strong></div><hr><div><span>Total</span><strong>' + money(totalValue) + '</strong></div></div></div><div class="quote-billing-card"><h3>Control</h3><p><strong>Idempotency key:</strong><br><code>' + esc(validation.idempotency_key || 'No generada') + '</code></p><p><strong>Vencimiento:</strong><br>' + esc((validation.payment && validation.payment.due_date) || 'No definido') + '</p></div></div>';
        const payload = validation.payload_preview || validation.payload || null;
        html += '<details><summary>Payload preliminar interno</summary><pre style="white-space:pre-wrap;max-height:300px;overflow:auto;">' + esc(payload ? JSON.stringify(payload, null, 2) : 'Payload no disponible para esta validación.') + '</pre></details>';
        billingContent().innerHTML = html;
        setBillingActions(ok ? '<button type="button" class="btn btn-outline quote-billing-save-draft"><i data-lucide="save"></i><span>Guardar borrador</span></button><button type="button" class="btn btn-primary quote-billing-preview"><i data-lucide="file-search"></i><span>Previsualizar</span></button>' : '<button type="button" class="btn btn-outline quote-billing-save-draft"><i data-lucide="save"></i><span>Guardar borrador</span></button><button type="button" class="btn btn-outline quote-modal-close"><i data-lucide="x"></i><span>Cerrar</span></button>');
    }
    function renderBillingPreview(q, data) {
        const result = data && data.result ? data.result : null;
        if (!result) {
            renderBillingError('La respuesta no contiene resultado de preview.');
            return;
        }
        const preview = result.preview || {};
        const validation = result.validation || {};
        const summary = result.facto_response_summary || {};
        const ok = !!result.ok;
        billingTitle(ok ? 'Preview FACTO sandbox' : 'Error preview FACTO', (q.quote_number || '') + ' · revisión PDF');
        let html = renderBillingSteps('preview');
        html += '<div class="quote-billing-alert ' + (ok ? 'success' : 'error') + '"><strong>' + (ok ? 'Preview FACTO generado' : 'Preview FACTO con error') + '</strong><br>' + esc(result.message || preview.message || '') + '<br>' + esc(documentTypeLabel(billingConfig().documentKey)) + ' · ' + esc(q.quote_number || '') + ' · sandbox</div>';
        html += '<div class="quote-billing-result-grid"><div class="quote-billing-card"><h3>Resultado FACTO</h3><p><strong>Log:</strong> #' + esc(preview.log_id || '') + '</p><p><strong>FACTO document_id:</strong><br>' + esc(preview.facto_document_id || summary.document_id || 'No informado en preview') + '</p><p><strong>Folio:</strong><br>' + esc(preview.document_number || summary.document_number || 'No informado en preview') + '</p><p><strong>PDF:</strong> ' + (preview.has_pdf ? 'Disponible' : 'No disponible') + '</p><p><strong>XML:</strong> ' + (preview.has_xml ? 'Disponible' : 'No disponible') + '</p></div><div class="quote-billing-card"><h3>Control</h3><p><strong>Idempotency key:</strong><br><code>' + esc(validation.idempotency_key || '') + '</code></p><p><strong>Ambiente:</strong><br>' + esc(preview.environment || 'sandbox') + '</p></div></div>';
        if (preview.has_pdf && preview.pdf_url) {
            html += '<div><h3>PDF preliminar</h3><iframe class="quote-billing-pdf-frame" src="' + esc(preview.pdf_url) + '"></iframe></div>';
        } else {
            html += '<div class="quote-billing-alert warning"><strong>Sin PDF preliminar.</strong><br>FACTO respondió, pero no incluyó PDF en el preview.</div>';
        }
        html += '<details><summary>Resumen respuesta FACTO</summary><pre style="white-space:pre-wrap;max-height:260px;overflow:auto;">' + esc(JSON.stringify(summary, null, 2)) + '</pre></details>';
        billingContent().innerHTML = html;
        setBillingActions(ok ? '<button type="button" class="btn btn-primary quote-billing-confirm-layer"><i data-lucide="send"></i><span>Emitir documento</span></button>' : '<button type="button" class="btn btn-outline quote-modal-close"><i data-lucide="x"></i><span>Cerrar</span></button>');
    }
    function issueConfirmationIsReady() {
        const confirmationInput = document.getElementById('quoteBillingIssueConfirmation');
        const confirmation = confirmationInput ? String(confirmationInput.value || '').trim().toUpperCase() : '';
        if (confirmation !== 'EMITIR') return false;
        const markPaid = document.getElementById('quoteBillingMarkAsPaid');
        if (markPaid && markPaid.checked) {
            const paymentMethod = document.getElementById('quoteBillingPaymentMethod');
            const cashAccount = document.getElementById('quoteBillingCashAccount');
            const paymentDate = document.getElementById('quoteBillingPaymentDate');
            if (!paymentMethod || !paymentMethod.value) return false;
            if (!cashAccount || !cashAccount.value) return false;
            if (!paymentDate || !paymentDate.value) return false;
        }
        return true;
    }
    function updateIssueConfirmButton() {
        const btn = document.querySelector('.quote-billing-confirm-issue');
        if (!btn) return;
        const ready = issueConfirmationIsReady();
        btn.disabled = !ready;
        btn.setAttribute('aria-disabled', ready ? 'false' : 'true');
        btn.title = ready ? 'Confirmar emisión sandbox' : 'Completa la confirmación requerida antes de emitir';
    }
    function renderBillingConfirm(q) {
        billingTitle('Confirmar emisión sandbox', (q.quote_number || '') + ' · requiere EMITIR');
        let html = renderBillingSteps('preview');
        html += '<div class="quote-billing-card quote-billing-confirm-box"><h3>Confirmación final</h3><p class="text-secondary">Esto emitirá el documento en FACTO sandbox, lo vinculará a la cotización, actualizará oportunidad/cobranza y creará el registro financiero. Producción sigue bloqueada.</p><label class="quote-billing-readonly"><input type="checkbox" id="quoteBillingMarkAsPaid" value="1"> Registrar como pagada al emitir</label><div class="quote-billing-confirm-grid" id="quoteBillingPaidFields" style="display:none;"><label><span class="label">Fecha de pago</span><input class="input" type="date" id="quoteBillingPaymentDate" value="' + esc(todayISO()) + '"></label><label><span class="label">Método de pago</span><select class="input" id="quoteBillingPaymentMethod">' + billingPaymentMethodOptionsHtml() + '</select></label><label><span class="label">Caja / cuenta destino</span><select class="input" id="quoteBillingCashAccount">' + billingCashAccountOptionsHtml() + '</select></label><label><span class="label">Referencia pago</span><input class="input" type="text" id="quoteBillingPaymentReference" placeholder="Nº transferencia, voucher, folio..."></label><label><span class="label">Nota pago</span><input class="input" type="text" id="quoteBillingPaymentNotes" placeholder="Comentario interno para análisis"></label></div><label><span class="label">Confirmación</span><input class="input" type="text" id="quoteBillingIssueConfirmation" placeholder="Escribe EMITIR" autocomplete="off"></label><p class="text-secondary mt-2">El botón de emisión se activa sólo cuando escribes EMITIR. Si marcas la factura como pagada, también debes seleccionar método de pago, caja/cuenta y fecha de pago.</p></div>';
        billingContent().innerHTML = html;
        setBillingActions('<button type="button" class="btn btn-danger quote-billing-confirm-issue" disabled aria-disabled="true"><i data-lucide="send"></i><span>Confirmar emisión sandbox</span></button>');
        updateIssueConfirmButton();
    }
    function renderIssueResult(q, data) {
        const result = data && data.result ? data.result : null;
        if (!result) {
            renderBillingError('La respuesta no contiene resultado de emisión.');
            return;
        }
        const issue = result.issue || {};
        const summary = result.facto_response_summary || {};
        const ok = !!result.ok;
        billingTitle(ok ? 'Emisión sandbox completada' : 'Emisión sandbox pendiente', (q.quote_number || '') + ' · resultado');
        let html = renderBillingSteps('result');
        html += '<div class="quote-billing-alert ' + (ok ? 'success' : 'error') + '"><strong>' + (ok ? 'Documento emitido en sandbox' : 'Emisión con observación') + '</strong><br>' + esc(result.message || '') + '<br>' + esc(documentTypeLabel(billingConfig().documentKey)) + ' · ' + esc(q.quote_number || '') + ' · sandbox</div>';
        html += '<div class="quote-billing-result-grid"><div class="quote-billing-card"><h3>Resultado FACTO</h3><p><strong>Log:</strong> #' + esc(issue.log_id || '') + '</p><p><strong>FACTO document_id:</strong><br>' + esc(issue.facto_document_id || summary.document_id || 'No informado') + '</p><p><strong>Folio:</strong><br>' + esc(issue.document_number || summary.document_number || 'No informado') + '</p><p><strong>PDF:</strong> ' + (issue.has_pdf ? 'Disponible' : 'No disponible') + '</p><p><strong>XML:</strong> ' + (issue.has_xml ? 'Disponible' : 'No disponible') + '</p></div><div class="quote-billing-card"><h3>ICYLAB OS</h3><p><strong>Cotización:</strong><br>' + esc(issue.quote_status || 'No actualizada') + '</p><p><strong>Cobranza:</strong><br>' + esc(issue.payment_status || 'No actualizada') + '</p><p><strong>Cuenta por cobrar:</strong><br>' + (issue.collection_created ? 'Creada/actualizada' : 'No creada') + '</p><p><strong>Pago:</strong><br>' + (issue.payment_registered ? 'Registrado' : 'No registrado') + '</p></div></div>';
        if (issue.has_pdf && issue.pdf_url) {
            html += '<div><h3>PDF emitido sandbox</h3><iframe class="quote-billing-pdf-frame" src="' + esc(issue.pdf_url) + '"></iframe></div>';
        }
        html += '<details><summary>Resumen respuesta FACTO</summary><pre style="white-space:pre-wrap;max-height:260px;overflow:auto;">' + esc(JSON.stringify(summary, null, 2)) + '</pre></details>';
        billingContent().innerHTML = html;
        if (ok) {
            q.status = issue.quote_status || 'Facturada';
            q.facto_document_id = issue.facto_document_id || q.facto_document_id || 'sandbox';
            q.invoice_number = issue.document_number || q.invoice_number || '';
            q.billing_issue_log_id = issue.log_id || q.billing_issue_log_id || 0;
            q.billing_pdf_url = issue.pdf_url || q.billing_pdf_url || '';
            q.billing_xml_available = !!issue.has_xml;
            q.billing_issue_environment = issue.environment || q.billing_issue_environment || 'sandbox';
            renderTable();
        }
        setBillingActions('<button type="button" class="btn btn-outline quote-modal-close"><i data-lucide="x"></i><span>Cerrar</span></button><button type="button" class="btn btn-primary quote-billing-email-layer"><i data-lucide="mail"></i><span>Enviar correo</span></button>');
    }
    function defaultBillingEmailSubject(q) {
        const folio = q.invoice_number || q.quote_number || '';
        return 'Documento tributario ICYLAB ' + folio;
    }
    function defaultBillingEmailBody(q) {
        const folio = q.invoice_number || 'emitido';
        return 'Hola,\n\nAdjuntamos el documento tributario asociado a la cotización ' + (q.quote_number || '') + '.\n\nFolio: ' + folio + '\n\nSaludos,\nICYLAB';
    }
    function renderBillingEmail(q, message) {
        billingTitle('Enviar documento', (q.quote_number || '') + ' · PDF/XML');
        const content = billingContent();
        if (!content) return;
        let html = renderBillingSteps('result');
        if (message) html += '<div class="quote-billing-alert success">' + esc(message) + '</div>';
        const hasDocument = String(q.facto_document_id || '').trim() !== '' || String(q.invoice_number || '').trim() !== '' || Number(q.billing_issue_log_id || 0) > 0;
        if (!hasDocument) {
            html += '<div class="quote-billing-alert error"><strong>No hay documento emitido.</strong><br>Primero debes emitir o vincular un documento tributario.</div>';
            content.innerHTML = html;
            setBillingActions('<button type="button" class="btn btn-outline quote-billing-prepare"><i data-lucide="arrow-left"></i><span>Volver</span></button>');
            return;
        }
        const pdfChecked = q.billing_pdf_url ? 'checked' : '';
        const xmlChecked = q.billing_xml_available ? 'checked' : '';
        const xmlDisabled = q.billing_xml_available ? '' : 'disabled';
        html += '<div class="quote-billing-card"><h3>Documento a enviar</h3><div class="quote-billing-grid">' +
            renderReadonly('Factura / folio', q.invoice_number || 'Sin folio local') +
            renderReadonly('FACTO document_id', q.facto_document_id || 'No informado') +
            renderReadonly('Cliente', q.customer_name || '') +
            renderReadonly('Total', money(q.total)) +
            '</div></div>';
        html += '<div class="quote-billing-card"><h3>Correo</h3><div class="quote-billing-email-grid">' +
            '<label><span class="label">Email cliente</span><input class="input" type="email" id="quoteBillingEmailTo" value="' + esc(q.email || '') + '" placeholder="cliente@dominio.cl"></label>' +
            '<label><span class="label">CC / email adicional</span><input class="input" type="text" id="quoteBillingEmailCc" placeholder="correo1@dominio.cl, correo2@dominio.cl"></label>' +
            '<label class="span-2"><span class="label">Asunto</span><input class="input" type="text" id="quoteBillingEmailSubject" value="' + esc(defaultBillingEmailSubject(q)) + '"></label>' +
            '<label class="span-2"><span class="label">Mensaje</span><textarea class="input" id="quoteBillingEmailBody" rows="7">' + esc(defaultBillingEmailBody(q)) + '</textarea></label>' +
            '</div><div class="quote-billing-email-options mt-3">' +
            '<label><input type="checkbox" id="quoteBillingAttachPdf" value="1" ' + pdfChecked + '> Adjuntar PDF</label>' +
            '<label><input type="checkbox" id="quoteBillingAttachXml" value="1" ' + xmlChecked + ' ' + xmlDisabled + '> Adjuntar XML</label>' +
            '</div><p class="text-secondary mt-2">El envío queda registrado en billing_document_emails para trazabilidad.</p></div>';
        content.innerHTML = html;
        setBillingActions('<button type="button" class="btn btn-outline quote-billing-prepare"><i data-lucide="arrow-left"></i><span>Volver</span></button><button type="button" class="btn btn-primary quote-billing-send-email"><i data-lucide="mail"></i><span>Enviar documento</span></button>');
    }
    function renderBillingEmailResult(q, data) {
        const result = data && data.result ? data.result : {};
        billingTitle('Documento enviado', (q.quote_number || '') + ' · correo');
        let html = renderBillingSteps('result');
        html += '<div class="quote-billing-alert success"><strong>Correo enviado correctamente.</strong><br>' + esc(result.message || 'Documento enviado.') + '</div>';
        html += '<div class="quote-billing-card"><h3>Detalle del envío</h3><div class="quote-billing-grid">' +
            renderReadonly('Destinatario', result.recipient_email || '') +
            renderReadonly('CC', result.cc_email || 'Sin copia') +
            renderReadonly('Asunto', result.subject || '') +
            renderReadonly('Registro email', result.email_id ? ('#' + result.email_id) : 'No informado') +
            '</div></div>';
        billingContent().innerHTML = html;
        setBillingActions('<button type="button" class="btn btn-outline quote-billing-prepare"><i data-lucide="arrow-left"></i><span>Volver a facturación</span></button><button type="button" class="btn btn-primary quote-modal-close"><i data-lucide="x"></i><span>Cerrar</span></button>');
    }
    function renderBillingError(message) { billingTitle('Error facturación', 'Flujo vinculado'); billingContent().innerHTML = renderBillingSteps('detail') + '<div class="quote-billing-alert error">' + esc(message || 'Error no especificado') + '</div>'; setBillingActions('<button type="button" class="btn btn-outline quote-modal-close"><i data-lucide="x"></i><span>Cerrar</span></button>'); }
    async function postBilling(url, fields) { const fd = new FormData(); Object.keys(fields || {}).forEach(function (key) { fd.append(key, fields[key]); }); const response = await fetch(url, { method: 'POST', body: fd, credentials: 'same-origin', headers: { 'Accept': 'application/json' } }); const raw = await response.text(); let data = null; try { data = JSON.parse(raw); } catch (e) { throw new Error('Respuesta no JSON. HTTP ' + response.status + '. ' + raw.slice(0, 240)); } if (!response.ok || !data.success) throw new Error((data && data.message) ? data.message : ('Error HTTP ' + response.status)); return data; }
    window.openQuoteBilling = function (id) { const q = quoteById(id || window.ICY_QUOTES_STATE.currentQuoteId); if (!q) return; window.ICY_QUOTES_STATE.currentQuoteId = Number(q.id); setLayer('quoteLayerBilling'); openModal(); renderBillingPrepare(q); };
    async function saveQuoteBillingDraft(trigger) {
        const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId);
        if (!q) return;
        const cfg = billingConfig();
        if (trigger) {
            trigger.disabled = true;
            trigger.innerHTML = '<i data-lucide="loader" class="spin-icon"></i><span>Guardando...</span>';
            if (window.lucide) lucide.createIcons();
        }
        try {
            const data = await postBilling('/modules/billing/save_draft_ajax.php', {
                quote_id: q.id,
                document_type_key: cfg.documentKey,
                environment: cfg.environment,
                issue_date: cfg.issueDate,
                payment_terms_key: cfg.paymentTerms
            });
            const result = data && data.result ? data.result : null;
            const draft = result && result.draft ? result.draft : {};
            q.billing_draft_log_id = draft.log_id || q.billing_draft_log_id || 0;
            q.billing_draft_document_key = draft.document_type_key || cfg.documentKey;
            q.billing_draft_environment = draft.environment || cfg.environment;
            q.billing_draft_mode = draft.mode || 'dry_run';
            q.billing_draft_status = draft.status || 'draft';
            q.billing_draft_issue_date = cfg.issueDate;
            q.billing_draft_payment_terms = cfg.paymentTerms;
            q.billing_draft_error_message = (draft.errors && draft.errors.length) ? draft.errors.join(' | ') : '';
            renderBillingPrepare(q, (result && result.message) ? result.message : 'Borrador tributario guardado.');
        } catch (err) {
            renderBillingError(err.message || 'No fue posible guardar el borrador tributario.');
        }
    }
    async function validateQuoteBilling(trigger) { const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId); if (!q) return; const cfg = billingConfig(); if (trigger) { trigger.disabled = true; trigger.innerHTML = '<i data-lucide="loader" class="spin-icon"></i><span>Validando...</span>'; if (window.lucide) lucide.createIcons(); } try { const data = await postBilling('/modules/billing/validate_issue_ajax.php', { quote_id: q.id, document_type_key: cfg.documentKey, environment: cfg.environment, issue_date: cfg.issueDate, payment_terms_key: cfg.paymentTerms }); renderValidationResult(q, data); } catch (err) { renderBillingError(err.message || 'No fue posible validar.'); } }
    async function previewQuoteBilling(trigger) { const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId); if (!q) return; const cfg = billingConfig(); if (trigger) { trigger.disabled = true; trigger.innerHTML = '<i data-lucide="loader" class="spin-icon"></i><span>Generando...</span>'; if (window.lucide) lucide.createIcons(); } try { const data = await postBilling('/modules/billing/preview_issue_ajax.php', { quote_id: q.id, document_type_key: cfg.documentKey, environment: cfg.environment, issue_date: cfg.issueDate, payment_terms_key: cfg.paymentTerms }); renderBillingPreview(q, data); } catch (err) { renderBillingError(err.message || 'No fue posible generar preview.'); } }
    function selectedOptionData(selectId) {
        const select = document.getElementById(selectId);
        const option = select && select.selectedOptions && select.selectedOptions.length ? select.selectedOptions[0] : null;
        return {
            value: select ? (select.value || '') : '',
            key: option ? (option.dataset.key || '') : '',
            label: option ? (option.dataset.label || option.textContent || '') : ''
        };
    }
    async function confirmQuoteBilling(trigger) {
        const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId);
        if (!q) return;
        const cfg = billingConfig();
        const confirmation = (document.getElementById('quoteBillingIssueConfirmation') || {}).value || '';
        if (confirmation.trim().toUpperCase() !== 'EMITIR') { updateIssueConfirmButton(); return; }
        const markPaid = document.getElementById('quoteBillingMarkAsPaid');
        const paymentDateValue = (document.getElementById('quoteBillingPaymentDate') || {}).value || '';
        const paymentMethod = selectedOptionData('quoteBillingPaymentMethod');
        const cashAccount = selectedOptionData('quoteBillingCashAccount');
        if (markPaid && markPaid.checked && (!paymentMethod.value || !cashAccount.value || !paymentDateValue)) { updateIssueConfirmButton(); return; }
        if (trigger) { trigger.disabled = true; trigger.innerHTML = '<i data-lucide="loader" class="spin-icon"></i><span>Emitiendo...</span>'; if (window.lucide) lucide.createIcons(); }
        try {
            const data = await postBilling('/modules/billing/confirm_issue_ajax.php', {
                quote_id: q.id,
                document_type_key: cfg.documentKey,
                environment: cfg.environment,
                issue_date: cfg.issueDate,
                payment_terms_key: cfg.paymentTerms,
                confirmation: confirmation,
                mark_as_paid: markPaid && markPaid.checked ? '1' : '0',
                payment_date: paymentDateValue,
                payment_method_id: /^[0-9]+$/.test(paymentMethod.value) ? paymentMethod.value : '',
                payment_method: paymentMethod.key || paymentMethod.value,
                payment_method_label: paymentMethod.label,
                cash_account_id: /^[0-9]+$/.test(cashAccount.value) ? cashAccount.value : '',
                cash_account_key: cashAccount.key || cashAccount.value,
                cash_account_label: cashAccount.label,
                payment_reference: (document.getElementById('quoteBillingPaymentReference') || {}).value || '',
                payment_notes: (document.getElementById('quoteBillingPaymentNotes') || {}).value || ''
            });
            renderIssueResult(q, data);
        } catch (err) { renderBillingError(err.message || 'No fue posible emitir.'); }
    }
    async function sendBillingDocumentEmail(trigger) {
        const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId);
        if (!q) return;
        const to = (document.getElementById('quoteBillingEmailTo') || {}).value || '';
        const subject = (document.getElementById('quoteBillingEmailSubject') || {}).value || '';
        const body = (document.getElementById('quoteBillingEmailBody') || {}).value || '';
        const attachPdf = document.getElementById('quoteBillingAttachPdf');
        const attachXml = document.getElementById('quoteBillingAttachXml');
        if (!String(to).trim()) {
            renderBillingEmail(q, 'Indica un email destinatario antes de enviar.');
            return;
        }
        if ((!attachPdf || !attachPdf.checked) && (!attachXml || !attachXml.checked)) {
            renderBillingEmail(q, 'Selecciona al menos PDF o XML para adjuntar.');
            return;
        }
        if (trigger) {
            trigger.disabled = true;
            trigger.innerHTML = '<i data-lucide="loader" class="spin-icon"></i><span>Enviando...</span>';
            if (window.lucide) lucide.createIcons();
        }
        try {
            const data = await postBilling('/modules/billing/send_document_email_ajax.php', {
                quote_id: q.id,
                log_id: q.billing_issue_log_id || 0,
                recipient_email: to,
                cc_email: (document.getElementById('quoteBillingEmailCc') || {}).value || '',
                subject: subject,
                body: body.replace(/\n/g, '<br>'),
                attach_pdf: attachPdf && attachPdf.checked ? '1' : '0',
                attach_xml: attachXml && attachXml.checked ? '1' : '0'
            });
            renderBillingEmailResult(q, data);
        } catch (err) {
            renderBillingError(err.message || 'No fue posible enviar el documento por correo.');
        }
    }
    window.openQuoteCreate = function () { clearForm(); window.ICY_QUOTES_STATE.currentQuoteId = 0; window.ICY_QUOTES_STATE.formMode = 'create'; document.getElementById('quoteFormTitle').textContent = 'Nueva Cotización'; document.getElementById('quoteFormSubtitle').textContent = 'Completa cliente, productos y condiciones'; document.getElementById('quoteSaveBtn').querySelector('span').innerHTML = 'Crear&nbsp;<span class="mobile-hidden">cotización</span>'; const deleteBtn = document.getElementById('quoteFormDeleteBtn'); const cancelBtn = document.getElementById('quoteFormCancelBtn'); if (deleteBtn) { deleteBtn.classList.add('hidden'); deleteBtn.dataset.quoteId = '0'; } if (cancelBtn) cancelBtn.classList.remove('hidden'); setLayer('quoteLayerForm'); openModal(); };
    window.openQuoteEdit = function (id) { const q = quoteById(id || window.ICY_QUOTES_STATE.currentQuoteId); if (!q) return; if (q.is_deleted) { alert('Esta cotización está eliminada. Restáurala antes de editar.'); return; } clearForm(); window.ICY_QUOTES_STATE.currentQuoteId = Number(q.id); window.ICY_QUOTES_STATE.formMode = 'edit'; const deleteBtn = document.getElementById('quoteFormDeleteBtn'); const cancelBtn = document.getElementById('quoteFormCancelBtn'); if (deleteBtn) { deleteBtn.classList.remove('hidden'); deleteBtn.dataset.quoteId = q.id; } if (cancelBtn) cancelBtn.classList.remove('hidden'); document.getElementById('quoteFormTitle').textContent = 'Editar Cotización'; document.getElementById('quoteFormSubtitle').textContent = `${q.quote_number} · ${q.customer_name}`; document.getElementById('quoteSaveBtn').querySelector('span').innerHTML = 'Guardar&nbsp;<span class="mobile-hidden">cambios</span>'; document.getElementById('formQuoteId').value = q.id; document.getElementById('formCustomerId').value = q.customer_id; document.getElementById('formOpportunityId').value = q.opportunity_id || 0; document.getElementById('opportunitySelect').value = q.opportunity_id || 0; document.getElementById('opportunitySelect').disabled = true; document.getElementById('formStatus').value = q.status || 'Borrador'; document.getElementById('formDiscount').value = q.discount_percent || 0; document.getElementById('formNotes').value = q.notes || ''; const invInput = document.getElementById('formInvoiceIssueDate'); if (invInput) invInput.value = q.invoice_issue_date_raw || ''; const payInput = document.getElementById('formPaymentDate'); if (payInput) payInput.value = q.payment_date_raw || ''; toggleInvoiceFields(); setCustomerChip({ id: q.customer_id, display_name: q.customer_name, customer_tax_id: q.customer_tax_id }, true, 'Cliente original de la cotización'); applyCustomerLock(true); (q.items || []).forEach(function (item) { addItem({ product_id: item.product_id || 'manual', name: item.display_name || String(item.name || '').replace(/\s*\(SKU:\s*[^)]+\)\s*$/i, ''), sku: item.sku || '', quantity: item.quantity || 1, unit: item.unit || 'unidad', price_unit: item.price_unit || 0 }); }); calculateQuote(); setLayer('quoteLayerForm'); openModal(); };
    function addItem(data) { data = data || {}; const idx = window.ICY_QUOTES_STATE.itemIndex++; const row = document.createElement('div'); row.className = 'eq-row'; row.innerHTML = `<input type="hidden" name="items[${idx}][product_id]" value="${esc(data.product_id || 'manual')}"><div class="eq-col-prod"><span class="mobile-label">Producto</span><input class="input it-name" name="items[${idx}][name]" value="${esc(data.name || '')}" placeholder="Producto" required></div><div class="eq-col-sku"><span class="mobile-label">SKU</span><input class="input" name="items[${idx}][sku]" value="${esc(data.sku || '')}" placeholder="SKU"></div><div class="eq-col-qty"><span class="mobile-label">Cant.</span><input class="input it-qty" type="number" min="0.001" step="0.001" inputmode="decimal" name="items[${idx}][quantity]" value="${esc(data.quantity || 1)}"></div><div class="eq-col-unit"><span class="mobile-label">Unidad</span>${unitSelectHtml(`items[${idx}][unit]`, data.unit || 'unidad')}</div><div class="eq-col-price"><span class="mobile-label">Unitario</span><input class="input it-price" type="number" min="0" step="0.01" name="items[${idx}][price_unit]" value="${esc(data.price_unit || 0)}"></div><div class="eq-col-total"><span class="mobile-label">Total</span><div class="it-total-text">$0</div></div><div class="eq-col-del"><button type="button" class="btn btn-outline remove-row-btn" title="Quitar producto"><i data-lucide="trash-2"></i></button></div>`; document.getElementById('quoteItems').appendChild(row); calculateQuote(); if (window.lucide) lucide.createIcons(); }
    function calculateQuote() { let subtotal = 0; document.querySelectorAll('#quoteItems .eq-row').forEach(function (row) { const qty = Number(row.querySelector('.it-qty').value || 0); const price = Number(row.querySelector('.it-price').value || 0); const total = qty * price; subtotal += total; row.querySelector('.it-total-text').textContent = money(total); }); const discountPercent = Number(document.getElementById('formDiscount').value || 0); const discountAmount = subtotal * (discountPercent / 100); const net = subtotal - discountAmount; const iva = net * 0.19; const total = net + iva; document.getElementById('sumSubtotal').textContent = money(subtotal); document.getElementById('sumNet').textContent = money(net); document.getElementById('sumIva').textContent = money(iva); document.getElementById('sumTotal').textContent = money(total); }
    function rowHtml(q) {
        const haystack = q.search_text || [q.quote_number, q.customer_name, q.customer_base_name, q.customer_commercial_name, q.customer_legal_name, q.customer_tax_id, q.email, q.phone, q.opportunity_title, q.status, q.invoice_number].join(' ').toLowerCase();
        const fullFolio = esc(q.quote_number || '');
        const mobileFolio = esc(compactQuoteNumber(q.quote_number || ''));

        return `<tr class="quote-row" data-quote-id="${q.id}" data-search="${esc(haystack)}" data-status="${esc(q.status)}" data-created-date="${esc(String(q.created_at_raw || '').slice(0, 10))}">
            <td data-label="Folio">
                <button type="button" class="quote-link quote-open-preview" data-quote-id="${q.id}" title="${fullFolio}">
                    <span class="quote-folio-full">${fullFolio}</span>
                    <span class="quote-folio-mobile">${mobileFolio}</span>
                </button>
            </td>
            <td data-label="Cliente"><div class="quote-client-cell"><strong>${esc(q.customer_name)}</strong><span>${esc(q.customer_tax_id || '')}</span></div></td>
            <td data-label="Origen"><div class="quote-origin-cell"><span>${esc(q.opportunity_title || 'Venta directa')}</span></div></td>
            <td data-label="Estado">${statusSelectHtml(q)}</td>
            <td data-label="Total"><strong class="metric-value">${money(q.total)}</strong></td>
            <td data-label="Fecha">${esc(q.created_at || '-')}</td>
            <td data-label="Acción" class="actions-cell-align"><div class="actions-btn-group"><button type="button" class="btn btn-sm btn-outline action-icon-btn quote-open-preview" data-quote-id="${q.id}" title="Ver cotización"><i data-lucide="eye"></i></button>${q.is_deleted ? `<form method="POST" action="/modules/crm/quote_restore.php" data-no-spa="true" class="quote-restore-inline-form" style="margin:0;"><input type="hidden" name="id" value="${q.id}"><input type="hidden" name="redirect" value="/modules/crm/quotes.php?view=deleted"><button type="submit" class="btn btn-sm btn-outline action-icon-btn quote-restore-btn" title="Restaurar cotización"><i data-lucide="rotate-ccw"></i></button></form>` : ''}</div></td>
        </tr>`;
    }
    function renderTable() { const tbody = document.getElementById('quotesTableBody'); if (!tbody) return; const data = window.ICY_QUOTES_DATA || []; if (!data.length) tbody.innerHTML = '<tr class="empty-state-row"><td colspan="7" class="text-muted"><?php echo $showDeletedQuotes ? 'No hay cotizaciones eliminadas.' : 'No hay cotizaciones registradas.'; ?></td></tr>'; else tbody.innerHTML = data.map(rowHtml).join(''); renderKpis(); filterQuotes(); if (window.lucide) lucide.createIcons(); }
    function renderKpis() { const data = window.ICY_QUOTES_DATA || []; const total = data.reduce(function (sum, q) { return sum + Number(q.total || 0); }, 0); const accepted = data.filter(function (q) { return q.status === 'Aceptada'; }).reduce(function (sum, q) { return sum + Number(q.total || 0); }, 0); const sent = data.filter(function (q) { return q.status === 'Enviada'; }).reduce(function (sum, q) { return sum + Number(q.total || 0); }, 0); if (document.getElementById('kpiCount')) document.getElementById('kpiCount').textContent = data.length; if (document.getElementById('kpiTotal')) document.getElementById('kpiTotal').textContent = money(total); if (document.getElementById('kpiAccepted')) document.getElementById('kpiAccepted').textContent = money(accepted); if (document.getElementById('kpiSent')) document.getElementById('kpiSent').textContent = money(sent); }
    function normalizeQuoteSearch(value) {
        if (window.ICY_NORMALIZE_SEARCH) return window.ICY_NORMALIZE_SEARCH(value);
        return String(value || '').toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').trim();
    }
    function normalizeQuoteRut(value) {
        if (window.ICY_NORMALIZE_RUT_SEARCH) return window.ICY_NORMALIZE_RUT_SEARCH(value);
        return normalizeQuoteSearch(value).replace(/[^0-9k]/g, '');
    }
    function filterQuotes() {
        const rawQuery = document.getElementById('quoteSearch')?.value || '';
        const query = normalizeQuoteSearch(rawQuery);
        const rutQuery = normalizeQuoteRut(rawQuery);
        const status = document.getElementById('quoteStatusFilter')?.value || '';
        const from = document.getElementById('quoteCreatedFrom')?.value || '';
        const to = document.getElementById('quoteCreatedTo')?.value || '';
        let visibleCount = 0;

        document.querySelectorAll('#quotesTableBody .quote-row').forEach(function (row) {
            const createdDate = row.dataset.createdDate || '';
            const rawHaystack = row.dataset.search || '';
            const haystack = normalizeQuoteSearch(rawHaystack);
            const rutHaystack = normalizeQuoteRut(rawHaystack);
            const matchesText = !query || haystack.indexOf(query) !== -1 || (rutQuery.length >= 2 && rutHaystack.indexOf(rutQuery) !== -1);
            const visible = matchesText
                && (!status || row.dataset.status === status)
                && (!from || (createdDate && createdDate >= from))
                && (!to || (createdDate && createdDate <= to));

            row.classList.toggle('is-filter-hidden', !visible);
            row.hidden = !visible;
            row.style.display = visible ? '' : 'none';
            if (visible) visibleCount++;
        });

        const tbody = document.getElementById('quotesTableBody');
        const oldEmpty = document.getElementById('quoteFilterEmptyRow');
        if (oldEmpty) oldEmpty.remove();
        if (tbody && visibleCount === 0 && (window.ICY_QUOTES_DATA || []).length > 0) {
            tbody.insertAdjacentHTML('beforeend', '<tr id="quoteFilterEmptyRow"><td colspan="7" class="text-muted">Sin resultados para el filtro aplicado.</td></tr>');
        }
    }
    function syncQuoteDatePlaceholders() {
        ['quoteCreatedFrom', 'quoteCreatedTo'].forEach(function (id) {
            const input = document.getElementById(id);
            if (!input) return;

            const field = input.closest('.quote-date-filter-field');
            if (!field) return;

            field.classList.toggle('has-value', !!input.value);
        });
    }

    function bindQuoteFiltersDirect() {
        ['quoteSearch', 'quoteCreatedFrom', 'quoteCreatedTo'].forEach(function (id) {
            const el = document.getElementById(id);
            if (!el || el.dataset.quoteFilterBound === '1') return;
            el.dataset.quoteFilterBound = '1';
            el.addEventListener('input', function () {
                if (id === 'quoteCreatedFrom' || id === 'quoteCreatedTo') syncQuoteDatePlaceholders();
                filterQuotes();
            }, { passive: true });
            el.addEventListener('change', function () {
                if (id === 'quoteCreatedFrom' || id === 'quoteCreatedTo') syncQuoteDatePlaceholders();
                filterQuotes();
            }, { passive: true });
        });
        const statusEl = document.getElementById('quoteStatusFilter');
        if (statusEl && statusEl.dataset.quoteFilterBound !== '1') {
            statusEl.dataset.quoteFilterBound = '1';
            statusEl.addEventListener('change', filterQuotes, { passive: true });
            statusEl.addEventListener('input', filterQuotes, { passive: true });
        }
        syncQuoteDatePlaceholders();
    }

    function upsertQuote(q) { const data = window.ICY_QUOTES_DATA || []; const idx = data.findIndex(function (item) { return Number(item.id) === Number(q.id); }); if (idx >= 0) data[idx] = q; else data.unshift(q); window.ICY_QUOTES_DATA = data; }
    function removeQuoteFromState(id) { window.ICY_QUOTES_DATA = (window.ICY_QUOTES_DATA || []).filter(function (q) { return Number(q.id) !== Number(id); }); }

    function toggleInvoiceFields() { const status = document.getElementById('formStatus')?.value || ''; const invoiceField = document.querySelector('#quotesMain .quote-invoice-field'); const paymentField = document.querySelector('#quotesMain .quote-payment-field'); const showInvoice = ['Facturada', 'Esperando pago', 'Pagada'].includes(status); const showPayment = status === 'Pagada'; if (invoiceField) invoiceField.classList.toggle('hidden', !showInvoice); if (paymentField) paymentField.classList.toggle('hidden', !showPayment); if (showInvoice) { const inv = document.getElementById('formInvoiceIssueDate'); if (inv && !inv.value) inv.value = new Date().toISOString().slice(0, 10); } if (showPayment) { const pay = document.getElementById('formPaymentDate'); if (pay && !pay.value) pay.value = new Date().toISOString().slice(0, 10); } }
    function updateQuickStatus(select) { const id = Number(select.dataset.quoteId || 0); const previous = select.dataset.current || ''; const status = select.value || 'Borrador'; if (!id) return; select.disabled = true; const form = new FormData(); form.append('id', id); form.append('status', status); fetch('/modules/crm/quote_status_ajax.php', { method: 'POST', body: form, credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (res) { if (!res || !res.success) throw new Error((res && (res.message || res.error)) ? (res.message || res.error) : 'No se pudo actualizar el estado.'); if (res.quote) { upsertQuote(res.quote); renderTable(); filterQuotes(); } }).catch(function (err) { alert(err.message || 'Error actualizando estado.'); select.value = previous; }).finally(function () { select.disabled = false; if (window.lucide) lucide.createIcons(); }); }
    function deleteCurrentQuote(trigger) { const id = Number((trigger && trigger.dataset && trigger.dataset.quoteId) || window.ICY_QUOTES_STATE.currentQuoteId || 0); if (!id) return; const q = quoteById(id); const label = q && q.quote_number ? q.quote_number : ('ID ' + id); if (!window.confirm('¿Eliminar la cotización ' + label + '?\n\nSe ocultará del listado activo, pero se conservarán cabecera, productos, documento asociado e historial.')) return; const originalHtml = trigger ? trigger.innerHTML : ''; if (trigger) { trigger.disabled = true; trigger.innerHTML = '<i data-lucide="loader" class="spin-icon"></i>'; if (window.lucide) lucide.createIcons(); } const form = new FormData(); form.append('id', id); form.append('ajax', '1'); fetch('/modules/crm/quote_delete.php', { method: 'POST', body: form, credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (res) { if (!res || !res.success) { throw new Error((res && (res.message || res.error)) ? (res.message || res.error) : 'No se pudo eliminar la cotización.'); } removeQuoteFromState(id); renderTable(); window.closeQuotesModal(); }).catch(function (err) { alert(err.message || 'Error de conexión al eliminar.'); }).finally(function () { if (trigger) { trigger.disabled = false; trigger.innerHTML = originalHtml; if (window.lucide) lucide.createIcons(); } }); }
    function searchCustomers(query) { const box = document.getElementById('customerResults'); if (!box) return; if (query.length < 2) { box.innerHTML = ''; box.classList.remove('show'); return; } box.innerHTML = '<div class="search-result-item is-muted"><span>Buscando clientes...</span></div>'; box.classList.add('show'); fetch('/modules/crm/api_customers.php?q=' + encodeURIComponent(query), { credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (rows) { box.innerHTML = ''; if (!Array.isArray(rows) || rows.length === 0) { box.innerHTML = '<div class="search-result-item is-muted"><span>Sin coincidencias. Puedes crear un cliente nuevo.</span></div>'; box.classList.add('show'); return; } rows.forEach(function (customer) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'search-result-item quote-select-customer'; btn.dataset.customer = JSON.stringify(customer); btn.innerHTML = `<strong>${esc(customer.display_name || customer.legal_name || customer.customer_name)}</strong><span>${esc(customer.customer_tax_id || 'Sin RUT')} · ${esc(customer.email || 'Sin email')}</span>`; box.appendChild(btn); }); box.classList.add('show'); }).catch(function () { box.innerHTML = '<div class="search-result-item is-muted"><span>Error buscando clientes.</span></div>'; box.classList.add('show'); }); }
    function searchProducts(query) { const box = document.getElementById('productResults'); if (!box) return; if (query.length < 2) { box.innerHTML = ''; box.classList.remove('show'); return; } box.innerHTML = '<div class="search-result-item is-muted"><span>Buscando productos...</span></div>'; box.classList.add('show'); fetch('/modules/crm/api_products.php?q=' + encodeURIComponent(query), { credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (rows) { box.innerHTML = ''; if (!Array.isArray(rows) || rows.length === 0) { box.innerHTML = '<div class="search-result-item is-muted"><span>Sin productos encontrados.</span></div>'; box.classList.add('show'); return; } rows.forEach(function (product) { const btn = document.createElement('button'); btn.type = 'button'; btn.className = 'search-result-item quote-select-product'; btn.dataset.product = JSON.stringify(product); btn.innerHTML = `<strong>${esc(product.name || '')}</strong><span>${esc(product.sku || 'S/S')} · ${money(product.price || product.price_unit || 0)} · ${esc(product.unit || 'unidad')}</span>`; box.appendChild(btn); }); box.classList.add('show'); }).catch(function () { box.innerHTML = '<div class="search-result-item is-muted"><span>Error buscando productos.</span></div>'; box.classList.add('show'); }); }
    function submitQuoteForm(form) { const btn = document.getElementById('quoteSaveBtn'); const originalHtml = btn ? btn.innerHTML : ''; if (btn) { btn.disabled = true; btn.innerHTML = '<i data-lucide="loader" class="spin-icon"></i><span>Guardando...</span>'; if (window.lucide) lucide.createIcons(); } fetch('/modules/crm/quote_save_ajax.php', { method: 'POST', body: new FormData(form), credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (res) { if (!res || !res.success) { alert((res && (res.message || res.error)) ? (res.message || res.error) : 'Error al guardar la cotización.'); return; } if (!res.quote) { return fetch('/modules/crm/quote_get_ajax.php?id=' + encodeURIComponent(res.quote_id), { credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (fresh) { if (!fresh.success) throw new Error(fresh.message || 'No se pudo cargar la cotización guardada.'); return fresh.quote; }); } return res.quote; }).then(function (quote) { if (!quote) return; upsertQuote(quote); renderTable(); const mode = window.ICY_QUOTES_STATE.formMode; const msg = mode === 'edit' ? 'Cotización actualizada correctamente.' : 'Cotización creada correctamente.'; window.openQuotePreview(quote.id, msg); }).catch(function (err) { alert(err.message || 'Error de conexión al guardar.'); }).finally(function () { if (btn) { btn.disabled = false; btn.innerHTML = originalHtml; if (window.lucide) lucide.createIcons(); } }); }
    function formatRutInputValue(input) {
        if (!input) return;
        if (window.ICY_CUSTOMER_LOCATION && window.ICY_CUSTOMER_LOCATION.countryCode(input.closest('form')) !== 'cl') return;
        var value = String(input.value || '').replace(/[^0-9kK]/g, '');
        if (!value) { input.value = ''; return; }
        if (value.length === 1) { input.value = value.toUpperCase(); return; }
        var body = value.slice(0, -1).replace(/\B(?=(\d{3})+(?!\d))/g, '.');
        var dv = value.slice(-1).toUpperCase();
        input.value = body + '-' + dv;
    }
    function syncQuickCustomerPhone() {
        var prefix = document.getElementById('quickCustomerPhonePrefix');
        var number = document.getElementById('quickCustomerPhoneNumber');
        var real = document.getElementById('quickCustomerPhone');
        if (!prefix || !number || !real) return;
        var clean = String(number.value || '').replace(/[^0-9\s-]/g, '');
        number.value = clean;
        real.value = clean.replace(/[^0-9]/g, '').length > 0 ? prefix.value + ' ' + clean : '';
    }
    function bindCustomerQuickHelpersOnce() {
        if (window.__ICY_QUOTES_CUSTOMER_HELPERS_BOUND) return;
        window.__ICY_QUOTES_CUSTOMER_HELPERS_BOUND = true;
        document.addEventListener('input', function (e) {
            if (!root()) return;
            if (e.target && e.target.id === 'quickCustomerTaxId') {
                formatRutInputValue(e.target);
                return;
            }
            if (e.target && e.target.id === 'quickCustomerPhoneNumber') {
                syncQuickCustomerPhone();
            }
        });
        document.addEventListener('change', function (e) {
            if (!root()) return;
            if (e.target && e.target.id === 'quickCustomerPhonePrefix') {
                syncQuickCustomerPhone();
            }
        });
    }
    function submitCustomerForm(form) { syncQuickCustomerPhone(); const submitBtn = form.querySelector('button[type="submit"]'); const originalHtml = submitBtn ? submitBtn.innerHTML : ''; if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = '<i data-lucide="loader" class="spin-icon"></i><span>Creando...</span>'; if (window.lucide) lucide.createIcons(); } fetch('/modules/crm/quote_customer_save_ajax.php', { method: 'POST', body: new FormData(form), credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (res) { if (!res || !res.success) { alert((res && res.message) ? res.message : 'Error creando cliente.'); return; } setCustomerChip(res.customer, false, ''); applyCustomerLock(false); document.getElementById('customerSearch').value = ''; document.getElementById('customerResults').classList.remove('show'); form.reset(); setLayer('quoteLayerForm'); }).catch(function () { alert('Error de conexión al crear cliente.'); }).finally(function () { if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = originalHtml; if (window.lucide) lucide.createIcons(); } }); }
    function bindGlobalHandlersOnce() { if (window.__ICY_QUOTES_GLOBAL_HANDLERS_BOUND) return; window.__ICY_QUOTES_GLOBAL_HANDLERS_BOUND = true; document.addEventListener('click', function (e) { const appRoot = root(); if (!appRoot) return; const spaLink = e.target.closest('a'); if (spaLink && !spaLink.target && !spaLink.hasAttribute('data-no-spa')) window.closeQuotesModal(); const openPreviewBtn = e.target.closest('.quote-open-preview'); if (openPreviewBtn) { e.preventDefault(); window.openQuotePreview(openPreviewBtn.dataset.quoteId); return; } if (e.target.closest('.quote-open-create')) { e.preventDefault(); window.openQuoteCreate(); return; } if (e.target.closest('.quote-modal-close')) { e.preventDefault(); window.closeQuotesModal(); return; } if (e.target.closest('.quote-open-edit')) { e.preventDefault(); window.openQuoteEdit(); return; } const billingOpenBtn = e.target.closest('#quoteBillingBtn'); if (billingOpenBtn) { e.preventDefault(); if (billingOpenBtn.disabled) return; window.openQuoteBilling(billingOpenBtn.dataset.quoteId || window.ICY_QUOTES_STATE.currentQuoteId); return; } if (e.target.closest('.quote-billing-back')) { e.preventDefault(); const qid = window.ICY_QUOTES_STATE.currentQuoteId; if (qid) window.openQuotePreview(qid); else setLayer('quoteLayerPreview'); return; } if (e.target.closest('.quote-billing-prepare')) { e.preventDefault(); const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId); if (q) renderBillingPrepare(q); return; } const billingSaveDraftBtn = e.target.closest('.quote-billing-save-draft'); if (billingSaveDraftBtn) { e.preventDefault(); saveQuoteBillingDraft(billingSaveDraftBtn); return; } if (e.target.closest('.quote-billing-continue') || e.target.closest('.quote-billing-detail')) { e.preventDefault(); const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId); if (q) renderBillingDetail(q); return; } const billingValidateBtn = e.target.closest('.quote-billing-validate'); if (billingValidateBtn) { e.preventDefault(); validateQuoteBilling(billingValidateBtn); return; } const billingPreviewBtn = e.target.closest('.quote-billing-preview'); if (billingPreviewBtn) { e.preventDefault(); previewQuoteBilling(billingPreviewBtn); return; } if (e.target.closest('.quote-billing-confirm-layer')) { e.preventDefault(); const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId); if (q) renderBillingConfirm(q); return; } const billingConfirmIssueBtn = e.target.closest('.quote-billing-confirm-issue'); if (billingConfirmIssueBtn) { e.preventDefault(); if (billingConfirmIssueBtn.disabled) return; confirmQuoteBilling(billingConfirmIssueBtn); return; } if (e.target.closest('.quote-billing-email-layer')) { e.preventDefault(); const q = quoteById(window.ICY_QUOTES_STATE.currentQuoteId); if (q) renderBillingEmail(q); return; } const billingSendEmailBtn = e.target.closest('.quote-billing-send-email'); if (billingSendEmailBtn) { e.preventDefault(); sendBillingDocumentEmail(billingSendEmailBtn); return; } const restoreQuoteForm = e.target.closest('.quote-restore-inline-form'); if (restoreQuoteForm) { e.preventDefault(); if (!confirm('¿Restaurar esta cotización?')) return; const fd = new FormData(restoreQuoteForm); fd.append('ajax', '1'); fetch(restoreQuoteForm.action, { method: 'POST', body: fd, credentials: 'same-origin' }).then(function (r) { return r.json(); }).then(function (res) { if (!res || !res.success) { alert((res && res.error) ? res.error : 'No fue posible restaurar.'); return; } (window.ICY_SPA_GO ? window.ICY_SPA_GO('/modules/crm/quotes.php?view=deleted', { replace: true, force: true }) : (window.location.href = '/modules/crm/quotes.php?view=deleted')); }).catch(function () { alert('Error de conexión al restaurar.'); }); return; } const deleteQuoteBtn = e.target.closest('.quote-delete-btn'); if (deleteQuoteBtn) { e.preventDefault(); deleteCurrentQuote(deleteQuoteBtn); return; } if (e.target.closest('.quote-form-back')) { e.preventDefault(); if (window.ICY_QUOTES_STATE.currentQuoteId) window.openQuotePreview(window.ICY_QUOTES_STATE.currentQuoteId); else window.closeQuotesModal(); return; } if (e.target.closest('.quote-back-form')) { e.preventDefault(); setLayer('quoteLayerForm'); return; } if (e.target.closest('.quote-open-customer-create')) { e.preventDefault(); setLayer('quoteLayerCustomer'); return; } if (e.target.closest('.quote-add-manual')) { e.preventDefault(); addItem({}); return; } const removeRow = e.target.closest('.remove-row-btn'); if (removeRow) { e.preventDefault(); const row = removeRow.closest('.eq-row'); if (row) row.remove(); calculateQuote(); return; } const selectCustomer = e.target.closest('.quote-select-customer'); if (selectCustomer) { e.preventDefault(); const customer = JSON.parse(selectCustomer.dataset.customer || '{}'); setCustomerChip(customer, false, ''); document.getElementById('customerSearch').value = ''; document.getElementById('customerResults').classList.remove('show'); return; } const selectProduct = e.target.closest('.quote-select-product'); if (selectProduct) { e.preventDefault(); const product = JSON.parse(selectProduct.dataset.product || '{}'); addItem({ product_id: product.id || product.product_id || 'manual', name: product.name || '', sku: product.sku || '', quantity: 1, unit: product.unit || 'unidad', price_unit: product.price || product.price_unit || 0 }); document.getElementById('productSearch').value = ''; document.getElementById('productResults').classList.remove('show'); return; } if (e.target.id === 'quotesModalBackdrop') window.closeQuotesModal(); }); document.addEventListener('input', function (e) { if (!root()) return; if (e.target && (e.target.id === 'quoteSearch' || e.target.id === 'quoteCreatedFrom' || e.target.id === 'quoteCreatedTo')) { if (e.target.id === 'quoteCreatedFrom' || e.target.id === 'quoteCreatedTo') syncQuoteDatePlaceholders(); filterQuotes(); return; } if (e.target && e.target.id === 'quoteBillingIssueConfirmation') { updateIssueConfirmButton(); return; } if (e.target && e.target.id === 'customerSearch') { clearTimeout(window.ICY_QUOTES_STATE.customerTimer); const value = e.target.value.trim(); window.ICY_QUOTES_STATE.customerTimer = setTimeout(function () { searchCustomers(value); }, 220); return; } if (e.target && e.target.id === 'productSearch') { clearTimeout(window.ICY_QUOTES_STATE.productTimer); const value = e.target.value.trim(); window.ICY_QUOTES_STATE.productTimer = setTimeout(function () { searchProducts(value); }, 220); return; } if (e.target && (e.target.id === 'formDiscount' || e.target.classList.contains('it-qty') || e.target.classList.contains('it-price'))) calculateQuote(); }); document.addEventListener('change', function (e) { if (!root()) return; if (e.target && e.target.classList && e.target.classList.contains('quote-status-quick')) { updateQuickStatus(e.target); return; } if (e.target && (e.target.id === 'quoteStatusFilter' || e.target.id === 'quoteCreatedFrom' || e.target.id === 'quoteCreatedTo')) { if (e.target.id === 'quoteCreatedFrom' || e.target.id === 'quoteCreatedTo') syncQuoteDatePlaceholders(); filterQuotes(); return; } if (e.target && e.target.id === 'quoteBillingMarkAsPaid') { const fields = document.getElementById('quoteBillingPaidFields'); if (fields) fields.style.display = e.target.checked ? 'grid' : 'none'; updateIssueConfirmButton(); return; } if (e.target && (e.target.id === 'quoteBillingPaymentMethod' || e.target.id === 'quoteBillingCashAccount' || e.target.id === 'quoteBillingPaymentDate')) { updateIssueConfirmButton(); return; } if (e.target && e.target.id === 'formStatus') { toggleInvoiceFields(); return; } if (e.target && e.target.id === 'opportunitySelect') { const oppId = Number(e.target.value || 0); document.getElementById('formOpportunityId').value = oppId; if (oppId > 0) { const opp = opportunityById(oppId); if (opp && opp.customer) { setCustomerChip(opp.customer, true, 'Cliente asociado a oportunidad'); applyCustomerLock(true); } } else if (window.ICY_QUOTES_STATE.formMode !== 'edit') { document.getElementById('formCustomerId').value = 0; document.getElementById('selectedCustomerChip').classList.add('hidden'); applyCustomerLock(false); } } }); document.addEventListener('submit', function (e) { if (!root()) return; if (e.target && e.target.id === 'quoteForm') { e.preventDefault(); submitQuoteForm(e.target); return; } if (e.target && e.target.id === 'customerQuickForm') { e.preventDefault(); submitCustomerForm(e.target); } }); document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && root()) window.closeQuotesModal(); }); }
    window.initQuotesPage = function () { if (!root()) return; initState(); bindGlobalHandlersOnce(); bindCustomerQuickHelpersOnce(); bindQuoteFiltersDirect(); renderTable(); syncQuoteDatePlaceholders(); if (!modal() || modal().getAttribute('aria-hidden') !== 'false') document.body.style.overflow = ''; if (window.lucide) lucide.createIcons(); };
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', window.initQuotesPage); else window.initQuotesPage();
})();
</script>

</main>
<?php require_once '../../layouts/footer.php'; ?>
