<?php
// ── Environment detection ─────────────────────────────────────────
$_SIGHA_ENV   = 'PRD';
$_SIGHA_LABEL = 'SIGHA GHA';
$_SIGHA_COLOR = '#0F6E56';
$_SIGHA_SHOW_BANNER = false;
$_script = $_SERVER['SCRIPT_FILENAME'] ?? __FILE__;
if (strpos($_script, 'sigha-dev') !== false) {
    $_SIGHA_ENV = 'DEV'; $_SIGHA_LABEL = 'SIGHA DEV';
    $_SIGHA_COLOR = '#E07B00'; $_SIGHA_SHOW_BANNER = true;
} elseif (strpos($_script, 'sigha-tst') !== false) {
    $_SIGHA_ENV = 'TST'; $_SIGHA_LABEL = 'SIGHA TST';
    $_SIGHA_COLOR = '#0066B2'; $_SIGHA_SHOW_BANNER = true;
}
?>
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $_SIGHA_LABEL; ?></title>
<?php if ($_SIGHA_SHOW_BANNER): ?>
<style>
.sigha-env-banner{position:fixed;top:0;left:0;right:0;z-index:99999;
background:<?php echo $_SIGHA_COLOR; ?>;color:#fff;text-align:center;
font-size:11px;font-weight:800;padding:4px 0;letter-spacing:.1em;
text-transform:uppercase;font-family:Arial,sans-serif;
box-shadow:0 2px 8px rgba(0,0,0,.25);}
body{padding-top:26px!important;}
</style>
<?php endif; ?>

<!-- Flatpickr date range picker -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
<script src="https://cdn.jsdelivr.net/npm/flatpickr/dist/l10n/es.js"></script>
<script src="https://cdn.sheetjs.com/xlsx-latest/package/dist/xlsx.full.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<style>
:root {
  --delta-navy:   #002244;
  --delta-sky:    #0066B2;
  --delta-red:    #C8102E;
  --delta-red-lt: #E0455A;
  --delta-accent: #FFC72C;
  --border:       #E2E6EB;
  --text-dark:    #1A1F29;
  --text-mid:     #5B6472;
  --text-light:   #9AA3AF;
  --shadow-sm:    0 1px 3px rgba(0,0,0,.08);
  --shadow-md:    0 4px 16px rgba(0,0,0,.12);
  --header-h:     56px;
  --sidebar-w:    220px;
  --sidebar-w-collapsed: 64px;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: 'Barlow', 'Inter', -apple-system, sans-serif;
  background: #F4F6F9;
  color: var(--text-dark);
  font-size: 14px;
  overflow: hidden;
}

.hidden { display: none !important; }

/* ── LOGIN ───────────────────────────────────────────────── */
#loginScreen {
  position: fixed; inset: 0;
  background: linear-gradient(135deg, var(--delta-navy) 0%, #003366 100%);
  display: flex; align-items: center; justify-content: center;
  z-index: 9000;
}
.login-box {
  background: #fff; border-radius: 8px; padding: 40px 36px;
  width: 380px; max-width: 92vw; box-shadow: var(--shadow-md);
}
.login-logo {
  font-family: 'Barlow Condensed', sans-serif; font-size: 26px; font-weight: 800;
  color: var(--delta-navy); letter-spacing: .04em; text-align: center; margin-bottom: 4px;
}
.login-logo span { color: var(--delta-red); }
.login-sub {
  text-align: center; font-size: 12px; color: var(--text-mid);
  text-transform: uppercase; letter-spacing: .08em; margin-bottom: 28px;
}
.login-field { margin-bottom: 16px; }
.login-field label {
  display: block; font-size: 11px; font-weight: 700; text-transform: uppercase;
  color: var(--text-mid); letter-spacing: .05em; margin-bottom: 6px;
}
.login-field input {
  width: 100%; height: 42px; border: 1px solid var(--border); border-radius: 5px;
  padding: 0 12px; font-size: 14px; outline: none; transition: border-color .15s;
}
.login-field input:focus { border-color: var(--delta-sky); }
.login-btn {
  width: 100%; height: 44px; background: var(--delta-red); color: #fff; border: none;
  border-radius: 5px; font-family: 'Barlow Condensed', sans-serif; font-size: 16px;
  font-weight: 700; letter-spacing: .06em; text-transform: uppercase; cursor: pointer;
  margin-top: 8px; transition: background .15s;
}
.login-btn:hover { background: var(--delta-red-lt); }
.login-btn:disabled { opacity: .6; cursor: not-allowed; }
.login-msg {
  margin-top: 14px; font-size: 13px; text-align: center; min-height: 18px;
}
.login-msg.error   { color: var(--delta-red); }
.login-msg.success { color: #2ECC71; }
.login-msg.info    { color: var(--text-mid); }

/* ── APP SHELL ───────────────────────────────────────────── */
#appShell { display: none; height: 100vh; flex-direction: column; }
#appShell.active { display: flex; }

.app-header {
  height: var(--header-h); background: var(--delta-navy); display: flex;
  align-items: center; padding: 0 18px; flex-shrink: 0; z-index: 100;
  box-shadow: 0 2px 6px rgba(0,0,0,.15);
}
.app-header-toggle {
  background: none; border: none; color: #fff; cursor: pointer; padding: 6px;
  margin-right: 12px; opacity: .85;
}
.app-logo {
  font-family: 'Barlow Condensed', sans-serif; font-size: 19px; font-weight: 800;
  color: #fff; letter-spacing: .04em;
}
.app-logo span { color: var(--delta-accent); }
.app-header-right {
  margin-left: auto; display: flex; align-items: center; gap: 16px;
}
.app-header-user {
  font-size: 12px; color: rgba(255,255,255,.85); text-align: right; line-height: 1.3;
}
.app-header-user b { display: block; color: #fff; font-size: 13px; }
.app-header-logout {
  background: rgba(255,255,255,.12); border: none; color: #fff; cursor: pointer;
  padding: 6px 12px; border-radius: 4px; font-size: 12px; font-weight: 600;
}
.app-header-logout:hover { background: rgba(255,255,255,.22); }

.app-body { display: flex; flex: 1; min-height: 0; }

/* ── SIDEBAR (auto-hide: colapsada por defecto, expande al pasar el mouse) ── */
.sidebar {
  width: var(--sidebar-w-collapsed); background: var(--delta-navy); border-right: 1px solid rgba(255,255,255,.08);
  flex-shrink: 0; overflow-y: auto; overflow-x: hidden; transition: width .22s cubic-bezier(.4,0,.2,1);
  position: relative; z-index: 50; box-shadow: 2px 0 8px rgba(0,0,0,.15);
}
.sidebar:hover { width: var(--sidebar-w); }
/* .pinned permite fijarla expandida manualmente (botón del header) */
.sidebar.pinned { width: var(--sidebar-w); }
.sidebar .menu-label, .sidebar .menu-group-arrow { opacity: 0; transition: opacity .15s; }
.sidebar:hover .menu-label, .sidebar:hover .menu-group-arrow,
.sidebar.pinned .menu-label, .sidebar.pinned .menu-group-arrow { opacity: 1; }
.sidebar:not(:hover):not(.pinned) .menu-group-children { max-height: 0 !important; }

.menu-item, .menu-group-header {
  display: flex; align-items: center; gap: 11px; padding: 11px 16px;
  cursor: pointer; color: rgba(255,255,255,.65); font-size: 13px; font-weight: 600;
  transition: background .12s, color .12s; user-select: none; white-space: nowrap;
}
.menu-item:hover, .menu-group-header:hover { background: rgba(255,255,255,.08); color: #fff; }
.menu-item.active { background: rgba(255,255,255,.12); color: var(--delta-accent); border-right: 3px solid var(--delta-accent); }
.menu-item svg, .menu-group-header svg { flex-shrink: 0; width: 16px; height: 16px; }
.menu-group-arrow { margin-left: auto; transition: transform .15s, opacity .15s; }
.menu-group.open .menu-group-arrow { transform: rotate(90deg); }
.menu-group-children { max-height: 0; overflow: hidden; transition: max-height .2s ease; }
.menu-group.open .menu-group-children { max-height: 300px; }
.menu-group-children .menu-item { padding-left: 40px; }
.menu-group.has-active .menu-group-header { color: #fff; font-weight: 700; }

/* ── MAIN CONTENT ────────────────────────────────────────── */
.main-content {
  flex: 1; overflow-y: auto; padding: 18px 22px; min-width: 0;
}

/* Utility classes reused across modules */
.card {
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  box-shadow: var(--shadow-sm);
}
.btn-primary {
  height: 36px; padding: 0 18px; background: var(--delta-red); color: #fff;
  border: none; border-radius: 4px; font-family: 'Barlow Condensed', sans-serif;
  font-size: 14px; font-weight: 700; letter-spacing: .04em; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px; text-transform: uppercase;
}
.btn-primary:hover { background: var(--delta-red-lt); }
.btn-primary:disabled { opacity: .5; cursor: not-allowed; }
.btn-secondary {
  height: 36px; padding: 0 16px; background: #fff; color: var(--text-mid);
  border: 1px solid var(--border); border-radius: 4px; font-size: 13px;
  font-weight: 600; cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
}
.btn-secondary:hover { border-color: var(--delta-sky); color: var(--delta-sky); }

.data-table { width: 100%; border-collapse: collapse; font-size: 13px; text-transform: uppercase; }
.data-table thead th {
  background: #F4F7FB; color: var(--text-mid); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .05em; padding: 10px 12px; text-align: left;
  border-bottom: 2px solid var(--border); white-space: nowrap;
}
.data-table tbody tr { border-bottom: 1px solid #EEF1F5; }
.data-table tbody tr:hover { background: #F8FAFC; }
.data-table tbody td { padding: 10px 12px; font-weight: 600; }

.status-badge {
  display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px;
  border-radius: 10px; font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .03em; white-space: nowrap;
}
.status-badge.st-clickable { cursor: pointer; border: 1px solid transparent; transition: filter .1s, border-color .1s; }
.status-badge.st-clickable:hover { filter: brightness(0.94); border-color: currentColor; }
.status-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; }

/* Popover de cambio rápido de estado (click en el badge) */
.st-popover {
  position: fixed; z-index: 3000; background: #fff; border: 1px solid var(--border);
  border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.16); min-width: 200px;
  font-size: 13px; overflow: hidden;
}
.st-popover-hdr {
  padding: 8px 12px; font-size: 10px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .05em; color: var(--text-mid); background: #F4F6F9; border-bottom: 1px solid var(--border);
}
.st-popover-opt {
  padding: 9px 12px; cursor: pointer; font-weight: 600; color: var(--delta-navy);
}
.st-popover-opt:hover { background: #EFF6FF; }
.st-popover-empty { padding: 10px 12px; font-size: 12px; color: var(--text-light); }
.st-popover-confirm { padding: 12px; }
.st-popover-confirm-msg { font-size: 13px; color: var(--delta-navy); margin-bottom: 8px; line-height: 1.4; }
.st-popover-confirm-msg strong { color: var(--delta-red); }
.st-popover-confirm textarea {
  width: 100%; height: 50px; border: 1px solid var(--border); border-radius: 4px;
  padding: 6px 8px; font-size: 12px; outline: none; resize: vertical; margin-bottom: 8px; box-sizing: border-box;
}
.st-popover-confirm-actions { display: flex; gap: 6px; justify-content: flex-end; }
.st-popover-confirm-actions button {
  padding: 5px 12px; font-size: 12px; font-weight: 700; border-radius: 5px; cursor: pointer; border: 1px solid var(--border);
}
.st-popover-confirm-actions .st-btn-confirm { background: var(--delta-navy); color: #fff; border-color: var(--delta-navy); }
.st-popover-confirm-actions .st-btn-cancel  { background: #fff; color: var(--text-mid); }
.st-popover-warn { padding: 8px 12px; font-size: 11px; color: var(--delta-red); font-weight: 600; border-top: 1px solid var(--border); background:#FFF5F5; }

/* Status colors — operational */
.st-RECEIVED  { background: #FFF3CD; color: #946C00; }
.st-CONFIRMED { background: #D4EDDA; color: #1E7E34; }
.st-LOADED    { background: #D1ECF1; color: #0C5460; }
.st-OFFLOADED { background: #FCE4E4; color: #A12622; }
.st-CANCELLED { background: #E2E3E5; color: #6C757D; }
/* Status colors — quality */
.qs-NOT_READY      { background: #E2E3E5; color: #6C757D; }
.qs-PENDING_REVIEW { background: #FFE8CC; color: #B25E00; }
.qs-VALIDATED      { background: #D4EDDA; color: #1E7E34; }

.empty-state {
  padding: 50px 20px; text-align: center; color: var(--text-light); font-size: 14px;
}

/* ── Flight Summary — dashboard ejecutivo (Rediseño Flight Summary) ── */
.fs-panel {
  background: #fff; border: 1px solid var(--border); border-radius: 8px; overflow: hidden;
}
.fs-panel-header {
  background: var(--delta-navy); color: #fff; padding: 8px 12px; font-size: 11px;
  font-weight: 800; text-transform: uppercase; letter-spacing: .05em;
  display: flex; align-items: center; gap: 7px;
}
.fs-summary-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.fs-summary-table thead th {
  background: #F4F6F9; color: var(--text-mid); font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em; padding: 6px 12px; text-align: left;
  border-bottom: 1px solid var(--border);
}
.fs-summary-table tbody td { padding: 8px 12px; border-bottom: 1px solid #F0F2F5; vertical-align: middle; }
.fs-summary-table tbody tr:last-child td { border-bottom: none; }
.fs-summary-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; border-radius: 5px; flex-shrink: 0;
}
.fs-summary-label { font-weight: 700; color: #1A1F29; }
.fs-summary-detail { font-size: 13.5px; font-weight: 700; }
.fs-summary-detail-empty { font-size: 13px; color: var(--text-light); }
.fs-col-lbl {
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; color: var(--text-mid); text-align: center;
}
.fs-row-lbl { font-size: 11px; font-weight: 800; color: #1A1F29; }
.fs-panel-input { height: 30px; font-size: 12px; width: 100%; text-align: center; }
.fs-pct-bar { display: flex; align-items: center; gap: 8px; }
.fs-pct-num { font-size: 12px; font-weight: 800; color: #1A1F29; width: 34px; text-align: right; flex-shrink: 0; }
.fs-bar-track { background: #E5E7EB; border-radius: 5px; height: 10px; overflow: hidden; flex: 1; }
.fs-bar-fill { height: 10px; border-radius: 5px; width: 0%; transition: width .5s; }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: #C7CDD6; border-radius: 5px; }
::-webkit-scrollbar-track { background: transparent; }
</style>
</head>
<body>
<?php if ($_SIGHA_SHOW_BANNER): ?>
<div class="sigha-env-banner">
    <?php echo $_SIGHA_ENV === 'DEV' ? '&#128296;' : '&#129514;'; ?>
    <?php echo $_SIGHA_LABEL; ?> —
    AMBIENTE DE <?php echo $_SIGHA_ENV === 'DEV' ? 'DESARROLLO' : 'PRUEBAS'; ?> —
    NO USAR PARA OPERACIONES REALES
    <?php echo $_SIGHA_ENV === 'DEV' ? '&#128296;' : '&#129514;'; ?>
</div>
<?php endif; ?>

<!-- ═══════════════════════════════════════════════════════════
     LOGIN SCREEN
     ═══════════════════════════════════════════════════════════ -->
<div id="loginScreen">
  <div class="login-box">
    <div class="login-logo">SIGHA</div>
    <div class="login-sub">Sistema de Gestión GHA &middot; Delta Air Lines Cargo</div>

    <div class="login-field">
      <label>Email or Username</label>
      <input type="text" id="loginIdentifier" placeholder="usuario@ankass.com.pe" autocomplete="username">
    </div>
    <div class="login-field">
      <label>Contrase&ntilde;a</label>
      <input type="password" id="loginPassword" placeholder="&bull;&bull;&bull;&bull;&bull;&bull;&bull;&bull;" autocomplete="current-password">
    </div>
    <button class="login-btn" id="loginBtn" onclick="doLogin()">Ingresar</button>
    <div class="login-msg" id="loginMsg"></div>
  </div>
</div>

<!-- ═══════════════════════════════════════════════════════════
     APP SHELL (visible tras login exitoso)
     ═══════════════════════════════════════════════════════════ -->
<div id="appShell">

  <div class="app-header">
    <button class="app-header-toggle" onclick="toggleSidebar()" title="Colapsar men&uacute;">
      <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
    </button>
    <div class="app-logo">SIGHA <span>GHA</span></div>
    <div class="app-header-right">
      <div class="app-header-user">
        <b id="hdrUserEmail"></b>
        <span id="hdrUserRole"></span>
      </div>
      <button class="app-header-logout" onclick="doLogout()">Salir</button>
    </div>
  </div>

  <div class="app-body">

    <!-- ── SIDEBAR ── -->
    <div class="sidebar" id="sidebar">
      <div class="menu-item hidden" id="menu-Revenues" onclick="switchView('revenues')">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3v18h18"/><path d="M18 17V9M13 17V5M8 17v-3"/></svg>
        <span class="menu-label">Revenues &amp; Avg Rate</span>
      </div>

      <div class="menu-group" id="menugrp-AWBMGMT">
        <div class="menu-group-header" onclick="toggleMenuGroup('AWBMGMT')">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
          <span class="menu-label">AWB Management</span>
          <svg class="menu-group-arrow" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg>
        </div>
        <div class="menu-group-children">
          <div class="menu-item hidden" id="menu-Booking" onclick="switchView('booking')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
            <span class="menu-label">Booking</span>
          </div>
          <div class="menu-item hidden" id="menu-FlightDetail" onclick="switchView('flightdetail')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-1 .1-1.3.5l-.7.7c-.4.4-.3 1.1.2 1.4L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.4 5.8c.3.5 1 .6 1.4.2l.7-.7c.4-.3.6-.8.5-1.3z"/></svg>
            <span class="menu-label">Flight Profile</span>
          </div>
          <div class="menu-item hidden" id="menu-Manifest" onclick="switchView('manifest')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
            <span class="menu-label">Pre-Manifest</span>
          </div>
          <div class="menu-item hidden" id="menu-FlightDeparture" onclick="switchView('flightdeparture')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2.5 19h19"/><path d="M17.8 19V9.5a2 2 0 0 0-.6-1.4L11.6 2.5a2 2 0 0 0-2.8 0L3.2 8.1a2 2 0 0 0-.6 1.4V19"/><path d="M9 19v-5h4v5"/></svg>
            <span class="menu-label">Flight Departure</span>
          </div>
          <div class="menu-item hidden" id="menu-PostFlight" onclick="switchView('postflight')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2L15 22l-4-9-9-4 20-7z"/></svg>
            <span class="menu-label">Post-Flight</span>
          </div>
        </div>
      </div>

      <div class="menu-item hidden" id="menu-Validate" onclick="switchView('validate')">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg>
        <span class="menu-label">Control de Calidad</span>
      </div>

      <div class="menu-item hidden" id="menu-METRIC" onclick="switchView('metric')">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
        <span class="menu-label">METRIC Latam</span>
      </div>

      <div class="menu-group" id="menugrp-REPORTS">
        <div class="menu-group-header" onclick="toggleMenuGroup('REPORTS')">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/></svg>
          <span class="menu-label">Reports</span>
          <svg class="menu-group-arrow" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg>
        </div>
        <div class="menu-group-children">
          <div class="menu-item hidden" id="menu-Reports" onclick="switchView('reports')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/></svg>
            <span class="menu-label">Booking Forecast</span>
          </div>
          <div class="menu-item hidden" id="menu-Cancelled" onclick="switchView('cancelled')">
            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
            <span class="menu-label">AWB Cancelled</span>
          </div>
        </div>
      </div>

      <div class="menu-item hidden" id="menu-Catalog" onclick="switchView('catalog')">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
        <span class="menu-label">Catalog Management</span>
      </div>

      <div class="menu-item hidden" id="menu-Admin" onclick="switchView('admin')">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
        <span class="menu-label">User Management</span>
      </div>
    </div>

    <!-- ── MAIN CONTENT (cada vista se inyecta aquí) ── -->
    <div class="main-content" id="mainContent">
      <div class="empty-state">Selecciona una opci&oacute;n del men&uacute;.</div>
    </div>

  </div>
</div>

<script>
// ── Global error tracker (DEV) ──
window.onerror = function(msg, src, line, col, err) {
    console.error('[SIGHA-ERR]', msg, '| line:', line, '| file:', src, err ? err.stack : '');
    return false;
};
window.addEventListener('unhandledrejection', function(e) {
    console.error('[SIGHA-PROMISE]', e.reason);
});
// ══════════════════════════════════════════════════════════════
//  SIGHA v2 — NÚCLEO (auth, permisos, routing)
// ══════════════════════════════════════════════════════════════

// ── Configuración de entorno ─────────────────────────────────
// v2 se despliega en public_html/sigha/ — el frontend vive en
// sigha/index.php y el backend en sigha/api/. Si en el futuro
// se necesita un entorno scargo-dev separado (como awbdev/awbprd
// en v1), replicar aquí el mismo patrón de auto-detección por
// pathname que usaba v1.
const API_BASE = (function() {
    const parts = window.location.pathname.split('/');
    const base  = parts.length > 1 ? '/' + parts[1] : '';
    return base + '/api';
})();
const API_KEY  = 'Iquitos2025Lima2025Peru2025';

let currentUser = null;
let currentView = null;

// ── Fetch helper con API key automática ──────────────────────
async function apiFetch(endpoint, options = {}) {
    const headers = Object.assign(
        { 'Content-Type': 'application/json', 'X-API-Key': API_KEY },
        options.headers || {}
    );
    const resp = await fetch(API_BASE + endpoint, Object.assign({}, options, { headers }));
    return resp.json();
}

// ══════════════════════════════════════════════════════════════
//  LOGIN / SESIÓN
// ══════════════════════════════════════════════════════════════
async function doLogin() {
    const login = document.getElementById('loginIdentifier').value.trim();
    const pass  = document.getElementById('loginPassword').value;
    const msg   = document.getElementById('loginMsg');
    const btn   = document.getElementById('loginBtn');

    if (!login || !pass) {
        msg.className = 'login-msg error';
        msg.textContent = 'Ingresa email/username y contraseña.';
        return;
    }

    btn.disabled = true;
    msg.className = 'login-msg info';
    msg.textContent = 'Verificando...';

    try {
        const res = await apiFetch('/auth/login.php', {
            method: 'POST',
            body: JSON.stringify({ login, password: pass })
        });

        if (res.success) {
            currentUser = res;
            sessionStorage.setItem('ci_user', JSON.stringify(res));
            msg.className = 'login-msg success';
            msg.textContent = 'Bienvenido.';
            setTimeout(enterApp, 300);
        } else {
            msg.className = 'login-msg error';
            msg.textContent = res.message || 'Credenciales incorrectas.';
            btn.disabled = false;
        }
    } catch (e) {
        msg.className = 'login-msg error';
        msg.textContent = 'Error de conexión.';
        btn.disabled = false;
    }
}

function doLogout() {
    sessionStorage.removeItem('ci_user');
    currentUser = null;
    _allStationsCache = null; // evita filtrar con estaciones del usuario anterior
    document.getElementById('appShell').classList.remove('active');
    document.getElementById('loginScreen').style.display = 'flex';
    document.getElementById('loginIdentifier').value = '';
    document.getElementById('loginPassword').value = '';
    document.getElementById('loginBtn').disabled = false;
    document.getElementById('loginMsg').textContent = '';
}

function enterApp() {
    document.getElementById('loginScreen').style.display = 'none';
    document.getElementById('appShell').classList.add('active');
    document.getElementById('hdrUserEmail').textContent = currentUser.full_name || currentUser.email;
    document.getElementById('hdrUserRole').textContent  = currentUser.role || 'Sin rol';
    applyMenuPermissions();
}

// Restaurar sesión si ya había una activa (recarga de página)
(function restoreSession() {
    const raw = sessionStorage.getItem('ci_user');
    if (raw) {
        try {
            currentUser = JSON.parse(raw);
            enterApp();
        } catch (e) { sessionStorage.removeItem('ci_user'); }
    }
})();

// Enter key en los campos de login
document.addEventListener('DOMContentLoaded', () => {
    ['loginIdentifier', 'loginPassword'].forEach(id => {
        const el = document.getElementById(id);
        if (el) el.addEventListener('keydown', e => { if (e.key === 'Enter') doLogin(); });
    });
});

// ══════════════════════════════════════════════════════════════
//  MATRIZ DE PERMISOS
// ══════════════════════════════════════════════════════════════
const MENU_TREE = [
    { id: 'Revenues', label: 'Revenues & Avg Rate', actions: ['view'] },
    { id: 'AWBMGMT', label: 'AWB Management', children: [
        { id: 'Booking',      label: 'Booking',       actions: ['view','create','edit','delete'] },
        { id: 'AWBEntry',     label: 'AWB Entry',     actions: ['view','create','edit','delete'] },
        { id: 'FlightDetail', label: 'Flight Profile', actions: ['view','create','edit'] },
        { id: 'Manifest',     label: 'Pre-Manifest',  actions: ['view','create','edit'] },
        { id: 'FlightDeparture', label: 'Flight Departure', actions: ['view','edit'] },
        { id: 'PostFlight',   label: 'Post-Flight',   actions: ['view'] },
    ]},
    { id: 'Validate', label: 'Control de Calidad', actions: ['view','create'] },
    { id: 'METRIC', label: 'METRIC Latam', actions: ['view'] },
    { id: 'REPORTS', label: 'Reports', children: [
        { id: 'Reports',    label: 'Booking Forecast', actions: ['view'] },
        { id: 'Cancelled',  label: 'AWB Cancelled',   actions: ['view'] },
    ]},
    { id: 'Catalog', label: 'Catalog Management', actions: ['view','create','edit','delete'] },
    { id: 'Admin',   label: 'User Management',    actions: ['view','create','edit'] },
];

function hasPermission(moduleId, action) {
    action = action || 'view';
    if (!currentUser) return false;
    if (currentUser.role === 'Admin') return true;

    const perms = currentUser.menu_permissions;
    if (perms && typeof perms === 'object' && !Array.isArray(perms)) {
        if (perms.ALL && perms.ALL[action]) return true;
        if (perms[moduleId] && perms[moduleId][action]) return true;
    }
    return false;
}

function applyMenuPermissions() {
    function allModules(tree) {
        const flat = [];
        tree.forEach(item => {
            if (item.children) flat.push(...item.children);
            else flat.push(item);
        });
        return flat;
    }
    allModules(MENU_TREE).forEach(mod => {
        const el = document.getElementById('menu-' + mod.id);
        if (el && hasPermission(mod.id, 'view')) el.classList.remove('hidden');
    });
    // Mostrar grupos que tengan al menos un hijo visible
    MENU_TREE.forEach(item => {
        if (!item.children) return;
        const grp = document.getElementById('menugrp-' + item.id);
        if (!grp) return;
        const anyVisible = item.children.some(c => {
            const el = document.getElementById('menu-' + c.id);
            return el && !el.classList.contains('hidden');
        });
        if (anyVisible) grp.classList.remove('hidden');
        else grp.classList.add('hidden');
    });
}

// ══════════════════════════════════════════════════════════════
//  ESTACIONES — fuente única compartida por todos los selectores
//  Regla de autorización: cada usuario ve únicamente las estaciones
//  asignadas en su configuración (currentUser.stations). El rol
//  Admin NO otorga acceso implícito a todas las estaciones — solo
//  el valor explícito 'ALL' en stations lo hace (Corrección Station
//  Access Control: antes role==='Admin' bypaseaba el filtro).
// ══════════════════════════════════════════════════════════════
let _allStationsCache = null;

async function getAvailableStations() {
    if (_allStationsCache) return _allStationsCache;
    try {
        const res = await apiFetch('/flights/flights_api.php?list_stations=1');
        const all = res.success ? res.data : ['LIM'];
        const assigned = currentUser?.stations || [];
        if (assigned.includes('ALL')) {
            _allStationsCache = all;
        } else {
            _allStationsCache = all.filter(s => assigned.includes(s));
            if (!_allStationsCache.length) _allStationsCache = assigned.length ? assigned : ['LIM'];
        }
    } catch(e) {
        _allStationsCache = ['LIM'];
    }
    return _allStationsCache;
}

/**
 * Puebla un <select> de estación con las estaciones disponibles
 * para el usuario actual, preservando el valor ya seleccionado
 * si sigue siendo válido. extraOptions: array de [value,label]
 * adicionales a anteponer (ej. ['','Todas'] o ['','All Stations']).
 */
async function populateStationSelect(selectId, extraOptions) {
    const sel = document.getElementById(selectId);
    if (!sel) return;
    const stations = await getAvailableStations();
    const current = sel.value;

    let html = (extraOptions || []).map(([v,l]) => `<option value="${esc(v)}">${esc(l)}</option>`).join('');
    html += stations.map(s => `<option value="${esc(s)}">${esc(s)}</option>`).join('');
    sel.innerHTML = html;

    if (current && Array.from(sel.options).some(o => o.value === current)) {
        sel.value = current;
    } else if (!extraOptions || !extraOptions.length) {
        sel.value = stations[0] || 'LIM';
    }
}

// ══════════════════════════════════════════════════════════════
//  SIDEBAR
// ══════════════════════════════════════════════════════════════
function toggleSidebar() {
    document.getElementById('sidebar').classList.toggle('pinned');
}

function toggleMenuGroup(groupId) {
    const grp = document.getElementById('menugrp-' + groupId);
    if (!grp) return;
    const wasOpen = grp.classList.contains('open');
    document.querySelectorAll('.menu-group').forEach(g => g.classList.remove('open'));
    if (!wasOpen) grp.classList.add('open');
}

function autoExpandMenuGroup(groupId) {
    document.querySelectorAll('.menu-group').forEach(g => g.classList.remove('open', 'has-active'));
    const grp = document.getElementById('menugrp-' + groupId);
    if (grp) { grp.classList.add('open'); grp.classList.add('has-active'); }
}

// ══════════════════════════════════════════════════════════════
//  VIEW ROUTER
//  Cada módulo (booking, awbentry, manifest, validate, catalog,
//  admin, revenues, metric, reports) define su propia función
//  <modulo>Init() que renderiza su HTML dentro de #mainContent.
//  Esas funciones se agregan en los siguientes archivos del
//  frontend — este router solo orquesta cuál está activa.
// ══════════════════════════════════════════════════════════════
const VIEW_MODULE_MAP = {
    revenues: { menuId: 'Revenues', group: null,        initFn: 'revenuesInit' },
    booking:  { menuId: 'Booking',  group: 'AWBMGMT',    initFn: 'bookingInit' },
    flightdetail: { menuId: 'FlightDetail', group: 'AWBMGMT', initFn: 'flightDetailInit' },
    manifest: { menuId: 'Manifest', group: 'AWBMGMT',    initFn: 'manifestInit' },
    flightdeparture: { menuId: 'FlightDeparture', group: 'AWBMGMT', initFn: 'flightDepartureInit' },
    validate: { menuId: 'Validate', group: null,        initFn: 'validateInit' },
    metric:   { menuId: 'METRIC',   group: null,        initFn: 'metricInit' },
    reports:    { menuId: 'Reports',    group: 'REPORTS',    initFn: 'reportsInit' },
    postflight: { menuId: 'PostFlight', group: 'AWBMGMT',   initFn: 'postFlightInit' },
    cancelled:  { menuId: 'Cancelled',  group: 'REPORTS',   initFn: 'cancelledInit' },
    catalog:  { menuId: 'Catalog',  group: null,        initFn: 'catalogInit' },
    admin:    { menuId: 'Admin',    group: null,        initFn: 'adminInit' },
};
// Nota: el módulo "AWB Entry" (awbEntryInit, awbOpenModal, awbEntryEdit)
// ya no es una vista navegable — el formulario/modal completo de la
// guía se invoca directamente desde Booking ("New AWB" / "Editar") y
// desde Control de Calidad ("Revisar/Editar"). El permiso 'AWBEntry'
// se mantiene en MENU_TREE para seguir gobernando quién puede crear/
// editar/eliminar guías vía ese modal, independientemente del acceso
// a la lista de Booking.

function switchView(viewName) {
    // Flight Profile dirty-state guard: block navigating away from the
    // module while there are unsaved Flight Operational Data changes.
    if (currentView === 'flightdetail' && typeof fdDirty !== 'undefined' && fdDirty && viewName !== 'flightdetail') {
        fdConfirmUnsavedThen(() => switchViewImpl(viewName));
        return;
    }
    switchViewImpl(viewName);
}

function switchViewImpl(viewName) {
    const cfg = VIEW_MODULE_MAP[viewName];
    if (!cfg) { console.warn('Vista no reconocida:', viewName); return; }

    if (!hasPermission(cfg.menuId, 'view')) {
        document.getElementById('mainContent').innerHTML =
            '<div class="empty-state">No tienes permiso para acceder a este m&oacute;dulo.</div>';
        return;
    }

    currentView = viewName;

    document.querySelectorAll('.menu-item').forEach(el => el.classList.remove('active'));
    const menuEl = document.getElementById('menu-' + cfg.menuId);
    if (menuEl) menuEl.classList.add('active');

    if (cfg.group) autoExpandMenuGroup(cfg.group);
    else document.querySelectorAll('.menu-group').forEach(g => g.classList.remove('open', 'has-active'));

    // Cada módulo es responsable de renderizar su propio HTML
    // dentro de #mainContent al llamar a su initFn.
    if (typeof window[cfg.initFn] === 'function') {
        window[cfg.initFn]();
    } else {
        document.getElementById('mainContent').innerHTML =
            '<div class="empty-state">M&oacute;dulo "' + viewName + '" a&uacute;n no implementado en este build.</div>';
    }
}

// ══════════════════════════════════════════════════════════════
//  HELPERS COMPARTIDOS (usados por todos los módulos)
// ══════════════════════════════════════════════════════════════
function esc(s) {
    return String(s == null ? '' : s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
}
function fmtMoney(n) {
    return parseFloat(n || 0).toLocaleString('en', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
function fmtDate(d) {
    if (!d) return '—';
    const p = String(d).split('-');
    return p.length === 3 ? p[2] + '/' + p[1] + '/' + p[0] : d;
}
function fmtNum(n) {
    return parseFloat(n || 0).toLocaleString('es-PE', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}
// ── Peso sin decimales, con separador de miles y sin unidad (la unidad va
// solo en el encabezado de columna: "CHG WGT (KG)" / "GROSS WGT (KG)") ──
function fmtWgt0(n) {
    return Math.round(parseFloat(n || 0)).toLocaleString('es-PE', { minimumFractionDigits: 0, maximumFractionDigits: 0 });
}
// ── VOL (M³): mismo estándar (separador de miles, sin unidad) pero con
// hasta 1 decimal — solo cuando el valor realmente lo tiene (2 → "2",
// 2.3 → "2.3", nunca "2.0") ──
function fmtVol1(n) {
    const v = Math.round((parseFloat(n || 0) + Number.EPSILON) * 10) / 10;
    return v.toLocaleString('es-PE', { minimumFractionDigits: 0, maximumFractionDigits: 1 });
}

// ── Indicador de capacidad de vuelo: Payload (flight_operations) vs Peso
// comprometido = SUM(GROSS WGT) de las AWBs activas del vuelo/fecha (no
// CHG WGT: lo que realmente ocupa espacio/peso en la aeronave es el Gross
// Weight). Mismo diseño/lógica en Booking (por grupo de vuelo) y
// Pre-Manifest (vuelo filtrado). Barra tipo píldora, texto superpuesto
// con los kilos y el %, y un brillo/pulso rojo cuando hay sobreventa.
// Escala: verde 0-70%, amarillo 70-90%, naranja 90-100%, rojo >100%. ──
function capIndicatorColor(pct) {
    return pct > 100 ? '#E8384F' : pct > 90 ? '#F5941C' : pct > 70 ? '#F5C518' : '#1FAA59';
}
function capIndicatorHTML(payloadKg, grossKg) {
    if (!payloadKg) return '';
    const pct = (grossKg / payloadKg) * 100;
    const pctClamped = Math.min(100, Math.max(0, pct));
    const color = capIndicatorColor(pct);
    const freeKg = payloadKg - grossKg;
    const over = pct > 100;
    const tooltip = 'Payload del vuelo:      ' + fmtWgt0(payloadKg) + ' Kg\n'
        + 'Peso comprometido:      ' + fmtWgt0(grossKg) + ' Kg\n'
        + 'Capacidad disponible:   ' + fmtWgt0(Math.max(0, freeKg)) + ' Kg\n'
        + 'Porcentaje de ocupación: ' + pct.toFixed(1) + ' %'
        + (over ? '\n⚠ SOBREVENTA DE CAPACIDAD' : '');
    const label = fmtWgt0(grossKg) + ' / ' + fmtWgt0(payloadKg) + ' Kg &nbsp;(' + Math.round(pct) + '%)' + (over ? ' ⚠' : '');
    return '<div title="' + tooltip.replace(/"/g,'&quot;') + '" class="' + (over ? 'cap-bar-wrap cap-bar-over' : 'cap-bar-wrap') + '">'
        + '<div class="cap-bar-track">'
        + '<div class="cap-bar-fill' + (over ? ' cap-bar-pulse' : '') + '" style="width:' + pctClamped.toFixed(1) + '%;background:' + color + ';box-shadow:0 0 8px ' + color + 'aa;"></div>'
        + '<div class="cap-bar-gloss" style="width:' + pctClamped.toFixed(1) + '%;"></div>'
        + '<div class="cap-bar-label">' + label + '</div>'
        + '</div></div>';
}

const OPERATIONAL_LABELS = {
    RECEIVED: 'Recibida', CONFIRMED: 'Confirmada', PRE_MANIFEST: 'Pre-Manifiesto',
    LOADED: 'Embarcada', OFFLOADED: 'No Embarcada', CANCELLED: 'Cancelada',
};
const QUALITY_LABELS = {
    NOT_READY: 'No Lista', PENDING_REVIEW: 'Pend. Revisión', VALIDATED: 'Validada',
};

// Transiciones manuales permitidas desde el badge de estado — mismas que
// el BK_TRANSITIONS anterior, solo quitando WAITLIST/REJECTED (eliminados).
const ST_TRANSITIONS = {
    RECEIVED:     ['CONFIRMED', 'CANCELLED'],
    CONFIRMED:    ['LOADED', 'OFFLOADED', 'CANCELLED'],
    OFFLOADED:    ['CANCELLED'],
    PRE_MANIFEST: ['CONFIRMED', 'CANCELLED'],
};

// Registro compartido: fila completa (por id) para poder validar campos
// requeridos al confirmar CONFIRMED, sin importar qué módulo la renderizó.
window._stRowsById = window._stRowsById || {};

// operationalStatusBadge(status, row, reloadFn, canEdit)
//   status   — string, el operational_status actual (siempre requerido)
//   row      — objeto completo del shipment (opcional). Si se pasa, el
//              badge se vuelve clickeable y abre el popover de cambio rápido.
//   reloadFn — nombre (string) de la función global a llamar tras un cambio
//              exitoso, ej. 'bkLoad' o 'valLoad'.
//   canEdit  — si es false, el badge se muestra pero no abre el popover
//              (guías bloqueadas / sin permiso de edición).
function operationalStatusBadge(status, row, reloadFn, canEdit) {
    const label = OPERATIONAL_LABELS[status] || status;
    if (!row || canEdit === false) {
        return '<span class="status-badge st-' + status + '"><span class="status-dot"></span>' + label + '</span>';
    }
    window._stRowsById[row.id] = row;
    return '<span class="status-badge st-' + status + ' st-clickable" '
        + 'onclick="stBadgeClick(event,' + row.id + ',\'' + status + '\',\'' + (reloadFn||'') + '\')">'
        + '<span class="status-dot"></span>' + label + '</span>';
}
function qualityStatusBadge(status) {
    return '<span class="status-badge qs-' + status + '"><span class="status-dot"></span>' +
        (QUALITY_LABELS[status] || status) + '</span>';
}

// ── Popover de cambio rápido de estado ──────────────────────────
function stClosePopover() {
    const el = document.getElementById('stPopover');
    if (el) el.remove();
    document.removeEventListener('click', stOutsideClickHandler, true);
}
function stOutsideClickHandler(e) {
    const pop = document.getElementById('stPopover');
    if (pop && !pop.contains(e.target)) stClosePopover();
}
function stBadgeClick(evt, shipmentId, currentStatus, reloadFn) {
    evt.stopPropagation();
    stClosePopover();

    const opts = ST_TRANSITIONS[currentStatus] || [];
    const rect = evt.currentTarget.getBoundingClientRect();

    const pop = document.createElement('div');
    pop.className = 'st-popover';
    pop.id = 'stPopover';
    pop.innerHTML = '<div class="st-popover-hdr">Cambiar Estado</div>'
        + '<div id="stPopoverBody">'
        + (opts.length
            ? opts.map(function(s) {
                return '<div class="st-popover-opt" onclick="stSelectNew(' + shipmentId + ',\'' + currentStatus + '\',\'' + s + '\',\'' + reloadFn + '\')">' + (OPERATIONAL_LABELS[s]||s) + '</div>';
              }).join('')
            : '<div class="st-popover-empty">No hay transiciones disponibles.</div>')
        + '</div>';
    document.body.appendChild(pop);

    // Posicionar debajo del badge, ajustando si se sale de la ventana
    const popW = pop.offsetWidth || 200;
    let left = rect.left;
    if (left + popW > window.innerWidth - 8) left = window.innerWidth - popW - 8;
    pop.style.left = Math.max(8, left) + 'px';
    pop.style.top  = (rect.bottom + 4) + 'px';

    setTimeout(function(){ document.addEventListener('click', stOutsideClickHandler, true); }, 0);
}
function stSelectNew(shipmentId, oldStatus, newStatus, reloadFn) {
    const body = document.getElementById('stPopoverBody');
    if (!body) return;

    const row = window._stRowsById[shipmentId] || {};
    let warnHtml = '';

    // Validación al confirmar CONFIRMED: mismos campos requeridos que antes.
    if (newStatus === 'CONFIRMED') {
        const isEmptyVal = function(v) { return !v || v==='' || v==='-' || v==='-Select-' || v==='—'; };
        const missing = [];
        if (isEmptyVal(row.agent_name) && isEmptyVal(row.agent_id))         missing.push('Agent (Freight Forwarder)');
        if (isEmptyVal(row.shipper_name) && isEmptyVal(row.shipper_id))     missing.push('Shipper');
        if (isEmptyVal(row.consignee_name) && isEmptyVal(row.consignee_id)) missing.push('Consignee');
        if (!row.pieces || parseInt(row.pieces) <= 0)                      missing.push('PCS (Piezas)');
        if (!row.gross_weight || parseFloat(row.gross_weight) <= 0)        missing.push('Gross WGT');
        if (!row.chg_weight  || parseFloat(row.chg_weight)  <= 0)          missing.push('CHG WGT');
        if (!row.description)                                             missing.push('Nature of Goods');
        if (missing.length) {
            body.innerHTML = '<div class="st-popover-confirm">'
                + '<div class="st-popover-confirm-msg">⚠ No es posible confirmar. Completar antes:<br>'
                + missing.map(function(f){ return '&nbsp;• ' + esc(f); }).join('<br>') + '</div>'
                + '<div class="st-popover-confirm-actions"><button class="st-btn-cancel" onclick="stClosePopover()">Cerrar</button></div>'
                + '</div>';
            return;
        }
    }

    const needsReason = (newStatus === 'CANCELLED');
    body.innerHTML = '<div class="st-popover-confirm">'
        + '<div class="st-popover-confirm-msg">¿Seguro que quieres cambiar el estado de <strong>' + (OPERATIONAL_LABELS[oldStatus]||oldStatus) + '</strong> a <strong>' + (OPERATIONAL_LABELS[newStatus]||newStatus) + '</strong>?</div>'
        + (needsReason ? '<textarea id="stReason" placeholder="Motivo (opcional)..."></textarea>' : '')
        + '<div class="st-popover-confirm-actions">'
        + '<button class="st-btn-cancel" onclick="stClosePopover()">Cancel</button>'
        + '<button class="st-btn-confirm" onclick="stConfirmChange(' + shipmentId + ',\'' + newStatus + '\',\'' + reloadFn + '\')">Confirm</button>'
        + '</div></div>';
}
async function stConfirmChange(shipmentId, newStatus, reloadFn) {
    const reasonEl = document.getElementById('stReason');
    const reason = reasonEl ? reasonEl.value : '';
    try {
        const res = await apiFetch('/shipments/shipment_status_api.php', {
            method: 'POST',
            body: JSON.stringify({
                shipment_id: shipmentId,
                new_status:  newStatus,
                reason,
                user_id: currentUser?.id || null
            })
        });
        stClosePopover();
        if (res.success) {
            // Si el nuevo estado excluye la guía de prioridades, reordenar
            // (mismo comportamiento que antes en bkConfirmStatus — solo
            // aplica en Booking, donde bkReorderPriorities existe).
            if (['OFFLOADED','CANCELLED'].includes(newStatus) && typeof window.bkReorderPriorities === 'function') {
                const row = window._stRowsById[shipmentId];
                if (row && row.flt_number && row.flt_date) {
                    await window.bkReorderPriorities(row.flt_number, row.flt_date, null, null);
                }
            }
            if (typeof window[reloadFn] === 'function') window[reloadFn]();
        } else {
            alert(res.message || 'Error al cambiar estado.');
        }
    } catch(e) {
        stClosePopover();
        alert('Error de conexión.');
    }
}
</script>


<style>
/* ── Estilos del módulo Booking (y reutilizados por otros listados) ── */
.module-header {
  display: flex; align-items: center; gap: 10px; margin-bottom: 16px;
}
.module-title {
  font-family: 'Barlow Condensed', sans-serif; font-size: 22px; font-weight: 700;
  color: var(--delta-navy); letter-spacing: .03em;
}
.module-badge {
  background: #E8F0FE; color: var(--delta-sky); font-size: 12px; font-weight: 700;
  padding: 3px 10px; border-radius: 10px; margin-left: auto;
}
.toolbar {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  padding: 12px 14px; margin-bottom: 14px;
}
.toolbar-sep { width: 1px; height: 24px; background: var(--border); }
.toolbar label.tb-lbl {
  font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-mid);
  letter-spacing: .04em; margin-right: -4px;
}
.toolbar select, .toolbar input[type=text], .toolbar input[type=date] {
  height: 34px; border: 1px solid var(--border); border-radius: 4px; padding: 0 9px;
  font-size: 13px; color: var(--text-dark); background: #fff; outline: none;
}
.col-mgr-panel {
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  padding: 14px 18px; margin-bottom: 14px;
}
.col-mgr-panel .checks { display: flex; flex-wrap: wrap; gap: 8px 20px; margin-top: 10px; }
.col-mgr-panel label {
  display: flex; align-items: center; gap: 6px; font-size: 12px; cursor: pointer;
}
.action-btn-sm {
  height: 28px; padding: 0 10px; border: 1px solid var(--border); background: #fff;
  border-radius: 4px; font-size: 11px; font-weight: 700; cursor: pointer; color: var(--text-mid);
}
.action-btn-sm:hover { border-color: var(--delta-sky); color: var(--delta-sky); }
.action-btn-sm.danger:hover { border-color: var(--delta-red); color: var(--delta-red); }
.emb-menu-item { padding:8px 14px;font-size:12px;font-weight:600;cursor:pointer;white-space:nowrap;border-bottom:1px solid #F3F4F6; }
.emb-menu-item:last-child { border-bottom:none; }
.emb-menu-item:hover { background:#F8FAFC; }
.frozen-badge {
  display: inline-flex; align-items: center; gap: 4px; font-size: 10px; font-weight: 700;
  color: var(--text-light);
}
.pagination { display: flex; align-items: center; gap: 6px; padding: 12px 0; }
.page-btn {
  width: 28px; height: 28px; border: 1px solid var(--border); background: #fff;
  border-radius: 4px; font-size: 12px; font-weight: 700; cursor: pointer; color: var(--text-mid);
}
.page-btn.active { background: var(--delta-sky); color: #fff; border-color: var(--delta-sky); }

/* ── Modal genérico de cambio de estado ── */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.5); z-index: 7000;
  display: flex; align-items: center; justify-content: center;
}
.modal-box {
  background: #fff; border-radius: 8px; width: 420px; max-width: 92vw;
  box-shadow: var(--shadow-md); overflow: hidden;
}
.modal-box-header {
  padding: 14px 18px; border-bottom: 1px solid var(--border);
  font-family: 'Barlow Condensed', sans-serif; font-size: 16px; font-weight: 700;
  color: var(--delta-navy); display: flex; align-items: center; justify-content: space-between;
}
.modal-box-body { padding: 16px 18px; }
.modal-box-footer { padding: 12px 18px; border-top: 1px solid var(--border); display: flex; gap: 8px; justify-content: flex-end; }
.status-option {
  padding: 10px 14px; border: 1px solid var(--border); border-radius: 5px; margin-bottom: 6px;
  cursor: pointer; font-size: 13px; font-weight: 600; transition: all .12s;
}
.status-option:hover { border-color: var(--delta-sky); background: #F4F9FF; }
.status-option.sel { border-color: var(--delta-sky); background: #E8F0FE; color: var(--delta-sky); }
</style>

<style>
/* ── Motor genérico de Report Profiles (columnas configurables) ── */
.colmgr-panel {
  position: fixed; top: 0; right: 0; bottom: 0; width: 360px; max-width: 92vw;
  background: #fff; box-shadow: -4px 0 24px rgba(0,0,0,.18); z-index: 8000;
  display: flex; flex-direction: column; transform: translateX(100%);
  transition: transform .25s cubic-bezier(.4,0,.2,1);
}
.colmgr-panel.open { transform: translateX(0); }
.colmgr-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.3); z-index: 7999;
  opacity: 0; pointer-events: none; transition: opacity .2s;
}
.colmgr-overlay.open { opacity: 1; pointer-events: auto; }
.colmgr-header {
  padding: 16px 20px; border-bottom: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between;
}
.colmgr-header h3 {
  font-family: 'Barlow Condensed', sans-serif; font-size: 16px; font-weight: 700; color: var(--delta-navy);
  text-transform: uppercase; letter-spacing: .04em;
}
.colmgr-body { flex: 1; overflow-y: auto; padding: 16px 20px; }
.colmgr-section { margin-bottom: 20px; }
.colmgr-section-label {
  font-size: 10px; font-weight: 700; text-transform: uppercase; color: var(--text-light);
  letter-spacing: .07em; margin-bottom: 8px; display: block;
}
.colmgr-row { display: flex; gap: 8px; align-items: center; margin-bottom: 8px; }
.colmgr-row select, .colmgr-row input {
  height: 36px; border: 1px solid var(--border); border-radius: 4px; padding: 0 10px; font-size: 13px; flex: 1;
}
.colmgr-row .colmgr-del {
  width: 36px; height: 36px; border: 1px solid #FCE4E4; background: #fff; color: var(--delta-red);
  border-radius: 4px; cursor: pointer; flex-shrink: 0;
}
.colmgr-check-row {
  display: flex; align-items: center; gap: 8px; padding: 7px 0; font-size: 13px; cursor: pointer;
  border-bottom: 1px solid #F4F6F9;
}
.colmgr-check-row.fixed { opacity: .55; cursor: default; }
.colmgr-check-row input[type=checkbox] { width: 16px; height: 16px; }
.colmgr-footer { padding: 14px 20px; border-top: 1px solid var(--border); display: flex; gap: 8px; }
</style>

<script>
// ══════════════════════════════════════════════════════════════
//  MOTOR GENÉRICO: REPORT PROFILES (colmgr*)
//  Reutilizable por Booking, AWB Entry, Manifest, Control de
//  Calidad — cada vista define su propio set de columnas
//  (fijas + opcionales) y aplica este panel encima.
// ══════════════════════════════════════════════════════════════
let colmgrActiveContext = null; // { storageKey, columns, onApply }

/**
 * Abre el panel de columnas para un contexto dado.
 * @param {string} storageKey  prefijo único por módulo, ej. 'booking', 'awbentry'
 * @param {Array}  columns     [{key,label,def,fixed}], fixed=true no se puede ocultar
 * @param {Function} onApply   callback(visibleCols:Object) al aplicar
 */
function colmgrOpen(storageKey, columns, onApply) {
    colmgrActiveContext = { storageKey, columns, onApply };

    let overlay = document.getElementById('colmgrOverlay');
    let panel = document.getElementById('colmgrPanel');
    if (!overlay) {
        overlay = document.createElement('div');
        overlay.className = 'colmgr-overlay';
        overlay.id = 'colmgrOverlay';
        overlay.onclick = colmgrClose;
        document.body.appendChild(overlay);
    }
    if (!panel) {
        panel = document.createElement('div');
        panel.className = 'colmgr-panel';
        panel.id = 'colmgrPanel';
        document.body.appendChild(panel);
    }

    const settings = colmgrLoadSettings(storageKey, columns);
    const profiles = colmgrLoadProfiles(storageKey);

    panel.innerHTML = `
        <div class="colmgr-header">
            <h3>Report Profiles</h3>
            <button onclick="colmgrClose()" style="background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-mid);">&times;</button>
        </div>
        <div class="colmgr-body">
            <div class="colmgr-section">
                <span class="colmgr-section-label">Perfiles Guardados</span>
                <div class="colmgr-row">
                    <select id="colmgrProfileSel" onchange="colmgrLoadProfile(this.value)">
                        <option value="">— Select profile —</option>
                        ${Object.keys(profiles).map(name => `<option value="${esc(name)}">${esc(name)}</option>`).join('')}
                    </select>
                    <button class="colmgr-del" onclick="colmgrDeleteProfile()" title="Eliminar perfil seleccionado">✕</button>
                </div>
                <div class="colmgr-row">
                    <input type="text" id="colmgrProfileName" placeholder="New profile name...">
                    <button class="btn-primary" style="height:36px;flex-shrink:0;" onclick="colmgrSaveProfile()">Save</button>
                </div>
            </div>

            <div class="colmgr-section">
                <span class="colmgr-section-label">Report Title</span>
                <label style="font-size:11px;color:var(--text-mid);display:block;margin-bottom:4px;">Title (for print &amp; export)</label>
                <input type="text" id="colmgrTitle" placeholder="e.g. LIM Monthly Cargo Report" value="${esc(settings.title||'')}" style="width:100%;height:36px;border:1px solid var(--border);border-radius:4px;padding:0 10px;font-size:13px;margin-bottom:10px;">
                <label style="font-size:11px;color:var(--text-mid);display:block;margin-bottom:4px;">Subtitle</label>
                <input type="text" id="colmgrSubtitle" placeholder="Report Sales" value="${esc(settings.subtitle||'')}" style="width:100%;height:36px;border:1px solid var(--border);border-radius:4px;padding:0 10px;font-size:13px;">
            </div>

            <div class="colmgr-section">
                <span class="colmgr-section-label">Visible Columns</span>
                <div id="colmgrChecks"></div>
            </div>
        </div>
        <div class="colmgr-footer">
            <button class="btn-secondary" style="flex:1;" onclick="colmgrReset()">Reset</button>
            <button class="btn-primary" style="flex:2;" onclick="colmgrApplyAndSave()">Apply &amp; Save</button>
        </div>
    `;

    colmgrRenderChecks(settings.cols);
    requestAnimationFrame(() => { overlay.classList.add('open'); panel.classList.add('open'); });
}

function colmgrClose() {
    document.getElementById('colmgrOverlay')?.classList.remove('open');
    document.getElementById('colmgrPanel')?.classList.remove('open');
    colmgrActiveContext = null;
}

function colmgrRenderChecks(currentCols) {
    const cont = document.getElementById('colmgrChecks');
    if (!cont || !colmgrActiveContext) return;
    cont.innerHTML = colmgrActiveContext.columns.map(c => {
        const checked = c.fixed ? true : !!currentCols[c.key];
        return `<label class="colmgr-check-row ${c.fixed?'fixed':''}">
            <input type="checkbox" data-key="${c.key}" ${checked?'checked':''} ${c.fixed?'disabled':''}>
            ${esc(c.label)}${c.fixed?' <span style="font-size:10px;color:var(--text-light);">(always visible)</span>':''}
        </label>`;
    }).join('');
}

function colmgrStorageKey(storageKey) { return 'colmgrCols_' + storageKey + '_' + (currentUser?.email || 'default'); }
function colmgrProfilesKey(storageKey) { return 'colmgrProfiles_' + storageKey + '_' + (currentUser?.email || 'default'); }

function colmgrLoadSettings(storageKey, columns) {
    try {
        const raw = localStorage.getItem(colmgrStorageKey(storageKey));
        if (raw) return JSON.parse(raw);
    } catch(e) {}
    return { cols: columns.reduce((o,c) => { o[c.key] = c.fixed ? true : c.def; return o; }, {}), title: '', subtitle: '' };
}
function colmgrSaveSettings(storageKey, settings) {
    try { localStorage.setItem(colmgrStorageKey(storageKey), JSON.stringify(settings)); } catch(e) {}
}
function colmgrLoadProfiles(storageKey) {
    try {
        const raw = localStorage.getItem(colmgrProfilesKey(storageKey));
        if (raw) return JSON.parse(raw);
    } catch(e) {}
    return {};
}
function colmgrSaveProfiles(storageKey, profiles) {
    try { localStorage.setItem(colmgrProfilesKey(storageKey), JSON.stringify(profiles)); } catch(e) {}
}

function colmgrCollectCurrent() {
    const cols = {};
    document.querySelectorAll('#colmgrChecks input[type=checkbox]').forEach(cb => { cols[cb.dataset.key] = cb.checked; });
    return {
        cols,
        title: document.getElementById('colmgrTitle')?.value || '',
        subtitle: document.getElementById('colmgrSubtitle')?.value || '',
    };
}

function colmgrApplyAndSave() {
    if (!colmgrActiveContext) return;
    const settings = colmgrCollectCurrent();
    colmgrSaveSettings(colmgrActiveContext.storageKey, settings);
    colmgrActiveContext.onApply(settings);
    colmgrClose();
}

function colmgrReset() {
    if (!colmgrActiveContext) return;
    const defaultCols = colmgrActiveContext.columns.reduce((o,c) => { o[c.key] = c.fixed ? true : c.def; return o; }, {});
    colmgrRenderChecks(defaultCols);
    document.getElementById('colmgrTitle').value = '';
    document.getElementById('colmgrSubtitle').value = '';
}

function colmgrSaveProfile() {
    if (!colmgrActiveContext) return;
    const name = document.getElementById('colmgrProfileName').value.trim();
    if (!name) return;
    const settings = colmgrCollectCurrent();
    const profiles = colmgrLoadProfiles(colmgrActiveContext.storageKey);
    profiles[name] = settings;
    colmgrSaveProfiles(colmgrActiveContext.storageKey, profiles);

    const sel = document.getElementById('colmgrProfileSel');
    if (!Array.from(sel.options).some(o => o.value === name)) {
        const o = document.createElement('option');
        o.value = name; o.textContent = name;
        sel.appendChild(o);
    }
    sel.value = name;
    document.getElementById('colmgrProfileName').value = '';
}

function colmgrLoadProfile(name) {
    if (!name || !colmgrActiveContext) return;
    const profiles = colmgrLoadProfiles(colmgrActiveContext.storageKey);
    const profile = profiles[name];
    if (!profile) return;
    colmgrRenderChecks(profile.cols);
    document.getElementById('colmgrTitle').value = profile.title || '';
    document.getElementById('colmgrSubtitle').value = profile.subtitle || '';
}

function colmgrDeleteProfile() {
    if (!colmgrActiveContext) return;
    const sel = document.getElementById('colmgrProfileSel');
    const name = sel.value;
    if (!name) return;
    const profiles = colmgrLoadProfiles(colmgrActiveContext.storageKey);
    delete profiles[name];
    colmgrSaveProfiles(colmgrActiveContext.storageKey, profiles);
    sel.querySelector(`option[value="${CSS.escape(name)}"]`)?.remove();
    sel.value = '';
}
</script>

<script>
// ══════════════════════════════════════════════════════════════
//  MÓDULO BOOKING (bk*)
// ══════════════════════════════════════════════════════════════
let bkList = [];
let bkPage = 1;
const BK_PER_PAGE = 15;
let bkDatePicker    = null;
let bkSavedFilters  = {};   // persiste filtros entre navegaciones
let bkOpsCache      = {};   // cache de flight_operations (payload_kg) por "flt|date", usado por el indicador de capacidad
let bkDragId         = null;   // id de la guía que se está arrastrando (reordenar prioridades)
let bkDragGroupKey   = null;   // "flt|date" del grupo de la guía arrastrada — solo se puede soltar dentro del mismo grupo

// Columnas del listado: fijas (siempre visibles) + opcionales
// FLT y Date ya no son columnas: esa información vive en el encabezado azul
// de cada grupo de vuelo (ver bkRender), así que se quitan de aquí también
// para no ofrecerlas como columnas redundantes en el gestor de columnas.
const BK_COLS = [
    { key:'awb',        label:'AWB',         def:true,  fixed:true  },
    { key:'priority',   label:'Priority',    def:true,  fixed:true  },
    { key:'agent',      label:'Agent / FF',  def:true,  fixed:false },
    { key:'shipper',    label:'Shipper',     def:false, fixed:false },
    { key:'consignee',  label:'Consignee',   def:false, fixed:false },
    { key:'origin',      label:'Origin',      def:false, fixed:false },
    { key:'destination', label:'Destination', def:false, fixed:false },
    { key:'pieces',     label:'Pcs',         def:true,  fixed:false },
    { key:'chg_wgt',    label:'CHG WGT (KG)',   def:true,  fixed:false },
    { key:'gross_wgt',  label:'GROSS WGT (KG)', def:false, fixed:false },
    { key:'tarifa',     label:'NET RATE',    def:true,  fixed:false },
    { key:'description',label:'Description', def:false, fixed:false },
    { key:'volume',     label:'VOL (m³)',     def:false, fixed:false },
    { key:'uld',        label:'ULD',          def:false, fixed:false },
    { key:'warehouse',  label:'Warehouse',   def:true,  fixed:false },
    { key:'manifest',   label:'Manifest',    def:true,  fixed:false },
    { key:'status',     label:'Status',      def:true,  fixed:true  },
    { key:'actions',    label:'Actions',     def:true,  fixed:true  },
];

function bkLoadColSettings() { return colmgrLoadSettings('booking', BK_COLS); }

// ── Render principal del módulo ──────────────────────────────
function bookingInit() {
    const canCreate = hasPermission('Booking', 'create');

    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-navy)"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>
            <div class="module-title">Booking — Lista de Guías</div>
            <div class="module-badge" id="bkBadge">0 guías</div>
        </div>

        <div class="toolbar">
            <label class="tb-lbl">Station</label>
            <select id="bkFilterStation" onchange="bkLoad()" style="width:80px;"></select>
            <div class="toolbar-sep"></div>
            <label class="tb-lbl">Status</label>
            <select id="bkFilterStatus" onchange="bkLoad()" style="width:140px;">
                <option value="">All</option>
                <option value="RECEIVED">Received</option>
                <option value="CONFIRMED">Confirmed</option>
                <option value="PRE_MANIFEST">Pre-Manifest</option>
                <option value="LOADED">Loaded</option>
                <option value="OFFLOADED">Offloaded</option>
                <option value="CANCELLED">Cancelled</option>
            </select>
            <div class="toolbar-sep"></div>
            <label class="tb-lbl">FLT</label>
            <input type="text" id="bkFilterFlt" placeholder="DL150..." oninput="bkLoad()" style="width:90px;">
            <div class="toolbar-sep"></div>
            <label class="tb-lbl">Date</label>
            <input type="text" id="bkFilterDateRange" placeholder="Date or range..." readonly style="width:190px;cursor:pointer;">
            <div class="toolbar-sep"></div>
            <input type="text" id="bkFilterQ" placeholder="AWB / agent / description..." oninput="bkLoad()" style="width:220px;">
            <div style="margin-left:auto;display:flex;gap:8px;align-items:center;">
                <label style="display:flex;align-items:center;gap:5px;font-size:12px;cursor:pointer;white-space:nowrap;padding:0 8px;border:1px solid var(--border);border-radius:4px;height:30px;background:#fff;">
                    <input type="checkbox" id="bkShowEmbarked" onchange="bkLoad()" style="width:14px;height:14px;cursor:pointer;">
                    Mostrar Embarcadas
                </label>
                <button class="btn-secondary" onclick="bkPrint()" title="Print current list">
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 6 2 18 2 18 9"/><path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/><rect x="6" y="14" width="12" height="8"/></svg>
                    Print
                </button>
                <button class="btn-secondary" onclick="bkOpenColMgr()">
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
                    Columns
                </button>
                <button class="btn-secondary" onclick="bkLoad()">&#8635; Refresh</button>
                ${canCreate ? '<button class="btn-primary" onclick="awbBookingOpenModal()">&#9992; New AWB</button>' : ''}
            </div>
        </div>

        <div id="bkCapacityBar" style="display:none;margin:0 0 10px;padding:10px 16px;background:#F4F6F9;border:1px solid var(--border);border-radius:6px;">
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;font-size:12px;">
                <span style="font-weight:700;color:var(--delta-navy);" id="bkCapLabel">Flight Capacity</span>
                <span style="color:var(--text-mid);" id="bkCapNumbers"></span>
            </div>
            <div style="height:10px;background:#E2E6EB;border-radius:5px;overflow:hidden;">
                <div id="bkCapFill" style="height:100%;border-radius:5px;background:var(--delta-navy);transition:width .4s;width:0%;"></div>
            </div>
            <div style="display:flex;justify-content:space-between;font-size:11px;color:var(--text-mid);margin-top:4px;">
                <span id="bkCapUsed"></span>
                <span id="bkCapFree"></span>
                <span id="bkCapPct" style="font-weight:700;color:var(--delta-navy);"></span>
            </div>
        </div>

        <div class="card" style="overflow-x:auto;">
            <div id="bkTableWrap"></div>
        </div>
        <div class="pagination" id="bkPagination" style="display:none;justify-content:space-between;">
            <span id="bkPageInfo" style="font-size:12px;color:var(--text-mid);"></span>
            <div id="bkPageBtns" style="display:flex;gap:6px;"></div>
        </div>
    `;

    bkInitDateRangePicker();
    populateStationSelect('bkFilterStation').then(() => {
        if (bkSavedFilters.station) document.getElementById('bkFilterStation').value = bkSavedFilters.station;
        if (bkSavedFilters.status)  document.getElementById('bkFilterStatus').value  = bkSavedFilters.status;
        if (bkSavedFilters.flt)     document.getElementById('bkFilterFlt').value     = bkSavedFilters.flt;
        if (bkSavedFilters.q)       document.getElementById('bkFilterQ').value       = bkSavedFilters.q;
        if (bkSavedFilters.dates && bkDatePicker) bkDatePicker.setDate(bkSavedFilters.dates, false);
        bkLoad();
    });
}

function bkInitDateRangePicker() {
    const el = document.getElementById('bkFilterDateRange');
    if (!el) return;
    const t = new Date();
    const today = t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0');
    const defaultDate = (bkSavedFilters.dates && bkSavedFilters.dates.length) ? bkSavedFilters.dates : [today];
    bkDatePicker = flatpickr(el, {
        mode: 'range', dateFormat: 'Y-m-d', locale: 'es',
        defaultDate: defaultDate,
        onChange: (selectedDates) => {
            if (selectedDates.length === 1 || selectedDates.length === 2) {
                bkSavedFilters.dates = selectedDates.map(d =>
                    d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0')
                );
                bkLoad();
            }
        }
    });
}

// ── Cargar datos ──────────────────────────────────────────────
async function bkLoad() {
    let fromDate = '', toDate = '';
    const fmt = d => d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');

    if (bkDatePicker && bkDatePicker.selectedDates.length >= 1) {
        fromDate = fmt(bkDatePicker.selectedDates[0]);
        toDate   = bkDatePicker.selectedDates.length >= 2 ? fmt(bkDatePicker.selectedDates[1]) : fromDate;
    } else if (bkSavedFilters.dates && bkSavedFilters.dates.length >= 1) {
        // Fallback: usar fechas guardadas cuando el picker no tiene selección
        fromDate = bkSavedFilters.dates[0];
        toDate   = bkSavedFilters.dates.length >= 2 ? bkSavedFilters.dates[1] : fromDate;
        // Restaurar en el picker si existe
        if (bkDatePicker) bkDatePicker.setDate(bkSavedFilters.dates, false);
    }

    // Guardar filtros para persistencia
    bkSavedFilters.station = document.getElementById('bkFilterStation')?.value || '';
    bkSavedFilters.status  = document.getElementById('bkFilterStatus')?.value  || '';
    bkSavedFilters.flt     = document.getElementById('bkFilterFlt')?.value     || '';
    bkSavedFilters.q       = document.getElementById('bkFilterQ')?.value       || '';
    const showEmbarked = document.getElementById('bkShowEmbarked')?.checked || false;

    const params = new URLSearchParams({
        station: bkSavedFilters.station || 'LIM',
        status:  bkSavedFilters.status,
        from:    fromDate, to: toDate,
        flt:     bkSavedFilters.flt,
        q:       bkSavedFilters.q,
        exclude_status: showEmbarked ? '' : 'LOADED',
    });

    try {
        const res = await apiFetch('/shipments/shipments_api.php?' + params.toString());
        bkList = res.success ? res.data : [];
        // Orden ascendente por fecha de vuelo (más antiguo → más reciente).
        bkList.sort(function(a, b) {
            const da = a.flt_date || '';
            const db = b.flt_date || '';
            return da < db ? -1 : (da > db ? 1 : 0);
        });
        bkPage = 1;
        bkRender();
        bkUpdateCapacityBar(bkSavedFilters.flt, fromDate, toDate, bkList);
    } catch(e) {
        document.getElementById('bkTableWrap').innerHTML = '<div class="empty-state">Error de conexión.</div>';
    }
}

// ── Columnas (Report Profiles) ───────────────────────────────
function bkOpenColMgr() {
    colmgrOpen('booking', BK_COLS, () => bkRender());
}

// ── Render de la tabla ────────────────────────────────────────
async function bkRender() {
    document.getElementById('bkBadge').textContent = bkList.length + ' guía' + (bkList.length !== 1 ? 's' : '');

    const wrap = document.getElementById('bkTableWrap');
    if (!bkList.length) {
        wrap.innerHTML = '<div class="empty-state">No hay guías para los filtros seleccionados.</div>';
        document.getElementById('bkPagination').style.display = 'none';
        return;
    }

    const settings = bkLoadColSettings().cols;
    const totalPages = Math.ceil(bkList.length / BK_PER_PAGE);
    const start = (bkPage - 1) * BK_PER_PAGE;
    const page  = bkList.slice(start, start + BK_PER_PAGE);

    // FLT y Date ya no son columnas propias — esa información vive en el
    // encabezado azul de cada grupo de vuelo. Encabezados con más tamaño y
    // peso tipográfico para mejorar la legibilidad; los anchos mínimos dan
    // más espacio a las columnas con más contenido (AWB, Agent/FF,
    // Description, Manifest, Status, Actions).
    const bkTh = function(label, extra) {
        return '<th style="font-size:11px;font-weight:800;font-family:\'Arial Narrow\',\'Roboto Condensed\',sans-serif;letter-spacing:.02em;padding:8px 8px;' + (extra || '') + '">' + label + '</th>';
    };
    let html = '<table class="data-table"><thead><tr>';
    html += bkTh('AWB', 'min-width:140px;');
    html += bkTh('PRI', 'text-align:center;width:50px;');
    if (settings.agent)       html += bkTh('Agent / FF', 'min-width:200px;');
    if (settings.shipper)     html += bkTh('Shipper', 'min-width:160px;');
    if (settings.consignee)   html += bkTh('Consignee', 'min-width:160px;');
    if (settings.origin)      html += bkTh('Origin', 'text-align:center;width:70px;');
    if (settings.destination) html += bkTh('DEST', 'text-align:center;width:70px;');
    if (settings.description) html += bkTh('Description', 'min-width:180px;');
    if (settings.pieces)      html += bkTh('Pcs', 'text-align:right;');
    if (settings.chg_wgt)     html += bkTh('CHG WGT (KG)', 'text-align:right;');
    if (settings.gross_wgt)   html += bkTh('GROSS WGT (KG)', 'text-align:right;');
    if (settings.tarifa)      html += bkTh('NET RATE', 'text-align:right;');
    if (settings.volume)      html += bkTh('VOL (M3)', 'text-align:right;');
    if (settings.uld)         html += bkTh('ULD', 'text-align:center;width:70px;');
    if (settings.warehouse)   html += bkTh('Warehouse', 'text-align:center;');
    if (settings.manifest)    html += bkTh('Manifest', 'text-align:center;min-width:110px;');
    html += bkTh('Status', 'min-width:130px;');
    html += bkTh('Actions', 'min-width:240px;');
    html += '</tr></thead><tbody>';

    // Group page items by flt_number + flt_date
    const screenGroups = {};
    const screenGroupOrder = [];
    page.forEach(function(s) {
        const key = (s.flt_number||'') + '|' + (s.flt_date||'');
        if (!screenGroups[key]) {
            screenGroups[key] = { flt:s.flt_number, date:s.flt_date, orig:s.flt_orig, dest:s.flt_dest, items:[], key:key };
            screenGroupOrder.push(key);
        }
        screenGroups[key].items.push(s);
    });

    // Precargar Payload (flight_operations) de los grupos que aún no están
    // en caché, para el indicador de capacidad de cada fila azul.
    const missingOps = screenGroupOrder.filter(function(k){ return !(k in bkOpsCache); });
    if (missingOps.length) {
        await Promise.all(missingOps.map(async function(k) {
            const grp = screenGroups[k];
            if (!grp.flt || !grp.date) { bkOpsCache[k] = null; return; }
            try {
                const station = grp.orig || document.getElementById('bkFilterStation')?.value || 'LIM';
                const res = await apiFetch('/flights/flight_operations_api.php?action=get_ops'
                    + '&flt_number=' + encodeURIComponent(grp.flt)
                    + '&flt_date='   + encodeURIComponent(grp.date)
                    + '&station='    + encodeURIComponent(station));
                bkOpsCache[k] = (res.success && res.data) ? res.data : null;
            } catch(e) { bkOpsCache[k] = null; }
        }));
    }

    screenGroupOrder.forEach(function(gkey) {
        const grp = screenGroups[gkey];
        // ── Flight group header — banda azul que ahora cubre el ancho
        // completo de la tabla (colCount ya no se queda corto porque ya no
        // hay columnas FLT/Date sueltas que restar, y sí cuenta Quality y
        // Actions). Agrupa visualmente todas las guías del mismo vuelo. ──
        const route = (grp.orig||'') + (grp.dest ? ' → '+grp.dest : '');
        const colCount = 4
            + (settings.agent?1:0) + (settings.shipper?1:0) + (settings.consignee?1:0)
            + (settings.origin?1:0) + (settings.destination?1:0)
            + (settings.description?1:0) + (settings.pieces?1:0) + (settings.chg_wgt?1:0)
            + (settings.gross_wgt?1:0) + (settings.tarifa?1:0) + (settings.volume?1:0)
            + (settings.uld?1:0)
            + (settings.warehouse?1:0) + (settings.manifest?1:0);

        // ── Indicador de capacidad: Payload (flight_operations) vs Peso
        // comprometido = SUM(GROSS WGT) de las AWBs activas de este grupo
        // (Gross Wgt, no Chg Wgt: es lo que ocupa peso real en la nave).
        // Centrado, ~25% del ancho de la fila azul. ──
        const ops = bkOpsCache[gkey];
        const payloadKg = ops && ops.payload_kg ? parseFloat(ops.payload_kg) : 0;
        const grossActive = grp.items
            .filter(function(s){ return !['CANCELLED','OFFLOADED'].includes(s.operational_status); })
            .reduce(function(a,s){ return a + parseFloat(s.gross_weight||0); }, 0);
        const capHTML = payloadKg
            ? '<div style="position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:26%;min-width:190px;max-width:300px;">'
              + capIndicatorHTML(payloadKg, grossActive) + '</div>'
            : '';

        html += '<tr style="background:var(--delta-navy);color:#fff;">'
            + '<td colspan="' + colCount + '" style="padding:8px 14px;font-size:12px;font-weight:800;position:relative;">'
            + '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"'
            + ' style="margin-right:7px;vertical-align:middle;opacity:.9;">'
            + '<path d="M22 2L11 13"/><path d="M22 2L15 22l-4-9-9-4 20-7z"/></svg>'
            + esc(grp.flt||'') + ' &nbsp;&nbsp; ' + route
            + ' &nbsp;&nbsp; <span style="font-weight:400;opacity:.8;">' + fmtDate(grp.date) + '</span>'
            + ' &nbsp; <span style="opacity:.6;font-size:11px;font-weight:400;">' + grp.items.length + ' guía(s)</span>'
            + capHTML
            + '</td></tr>';

        // ── Rows for this group ───────────────────────────────────
        grp.items.forEach(function(s) {
        const isLocked    = s.operational_status === 'LOADED';
        const isMfClosed  = s.manifest_status === 'DEFINITIVE';  // manifiesto cerrado
        const isFinal     = ['LOADED','CANCELLED'].includes(s.operational_status);
        // Fondo verde para guías embarcadas con manifiesto (manifiesto cerrado)
        const rowBg = (s.operational_status === 'LOADED' && s.manifest_number)
            ? ' style="background:#E8F8F0;"' : '';
        // Reordenamiento manual por Drag & Drop: solo guías del mismo vuelo/fecha
        // (gkey), con manifiesto no cerrado y que no estén Closed/Frozen/
        // Cancelled/Rejected/Offloaded (esas ya ni muestran badge de prioridad).
        const canDragRow = !isMfClosed && !isLocked
            && !['CANCELLED','OFFLOADED'].includes(s.operational_status);
        const dragAttrs = canDragRow
            ? ' draggable="true" class="bk-draggable-row" data-id="' + s.id + '" data-group="' + esc(gkey) + '" data-priority="' + (s.priority||'') + '"'
              + ' ondragstart="bkDragStart(event)" ondragover="bkDragOver(event)" ondragleave="bkDragLeave(event)" ondrop="bkDrop(event)" ondragend="bkDragEnd(event)"'
            : '';
        html += '<tr' + rowBg + dragAttrs + '>';
        // AWB: solo el número (ya no el número interno de reserva debajo),
        // con más tamaño y negrita para identificarlo más rápido.
        html += `<td><strong style="font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:var(--delta-navy);font-size:14px;font-weight:800;">${esc(s.awb || '—')}</strong></td>`;
        // Priority badge — color scale + inline edit
        const prio = s.priority;
        const prioColor = !prio           ? '#9AA3AF'   // null/sin prioridad
                        : prio === 1      ? '#0066B2'   // azul — máxima
                        : prio === 2      ? '#1A7A4A'   // verde
                        : prio === 3      ? '#E07B00'   // naranja
                        : prio <= 6       ? '#B8860B'   // amarillo oscuro
                        :                  '#9AA3AF';   // gris
        const prioBg   = !prio           ? '#F4F6F9'
                        : prio === 1      ? '#DCE8F5'
                        : prio === 2      ? '#E1F5EE'
                        : prio === 3      ? '#FFF0DC'
                        : prio <= 6       ? '#FFF8DC'
                        :                  '#F4F6F9';
        html += `<td style="text-align:center;">`;
        if (!['CANCELLED','OFFLOADED'].includes(s.operational_status)) {
            if (isMfClosed) {
                // Manifiesto cerrado: badge sin click, con candado
                html += `<span title="La prioridad no puede modificarse: la guía pertenece a un manifiesto cerrado."
                    style="display:inline-flex;align-items:center;justify-content:center;
                           min-width:26px;height:26px;padding:0 6px;border-radius:4px;
                           background:${prioBg};color:${prioColor};
                           font-size:12px;font-weight:800;cursor:not-allowed;
                           border:1px solid ${prioColor}40;opacity:.7;">
                    ${prio || '—'}
                </span>`;
            } else {
                html += `<span class="bk-prio-badge" data-id="${s.id}"
                    data-flt="${esc(s.flt_number||'')}" data-date="${esc(s.flt_date||'')}"
                    onclick="bkEditPriority(this)"
                    title="Click para editar prioridad"
                    style="display:inline-flex;align-items:center;justify-content:center;
                           min-width:26px;height:26px;padding:0 6px;border-radius:4px;
                           background:${prioBg};color:${prioColor};
                           font-size:12px;font-weight:800;cursor:pointer;
                           border:1px solid ${prioColor}40;user-select:none;">
                    ${prio || '—'}
                </span>`;
            }
        } else {
            html += `<span style="color:var(--text-light);font-size:11px;">—</span>`;
        }
        html += `</td>`;

        if (settings.agent)       html += `<td style="max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(s.agent_name || '—')}</td>`;
        if (settings.shipper)     html += `<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(s.shipper_name || '—')}</td>`;
        if (settings.consignee)   html += `<td style="max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(s.consignee_name || '—')}</td>`;
        if (settings.origin)      html += `<td style="text-align:center;font-family:'Courier New',monospace;font-weight:700;">${esc(s.routing_orig || s.flt_orig || '—')}</td>`;
        if (settings.destination) html += `<td style="text-align:center;font-family:'Courier New',monospace;font-weight:700;">${esc(s.routing_dest || s.flt_dest || '—')}</td>`;
        if (settings.description) html += `<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(s.description || '—')}</td>`;
        if (settings.pieces)      html += `<td style="text-align:right;">${fmtWgt0(s.pieces)}</td>`;
        if (settings.chg_wgt)     html += `<td style="text-align:right;">${fmtWgt0(s.chg_weight)}</td>`;
        if (settings.gross_wgt)   html += `<td style="text-align:right;">${fmtWgt0(s.gross_weight)}</td>`;
        if (settings.tarifa)      html += `<td style="text-align:right;">${fmtWgt0(s.net_rate)}</td>`;
        if (settings.volume) {
            // VOL (M3): únicamente el valor numérico de volumen almacenado
            // en la guía — no mezcla información de ULD (ver columna ULD aparte).
            html += `<td style="text-align:right;">${s.volume_m3 ? fmtVol1(s.volume_m3) : '—'}</td>`;
        }
        if (settings.uld) {
            // ULD: cantidad + tipo de ULD asignado (ej. "1AKE", "3PMC").
            // Vacío (sin guion) si la guía no tiene ULD asignado.
            const uldQty  = parseInt(s.uld_qty) || 0;
            const uldType = (s.uld_type || '').trim();
            const uldTxt  = (uldQty && uldType) ? (uldQty + uldType) : (uldType || '');
            html += `<td style="text-align:center;font-weight:700;">${uldTxt ? esc(uldTxt) : ''}</td>`;
        }
        if (settings.warehouse) {
            const wh = s.warehouse || '';
            html += `<td style="text-align:center;">
                <select class="mf-warehouse bk-warehouse" data-id="${s.id}"
                    onchange="bkSaveWarehouse(${s.id}, this.value)"
                    ${isMfClosed ? 'disabled title="Manifiesto cerrado — almacén bloqueado"' : ''}
                    style="font-size:11px;font-weight:700;border:1px solid var(--border);border-radius:4px;
                           padding:2px 4px;background:#fff;
                           ${isMfClosed ? 'opacity:.6;cursor:not-allowed;' : 'cursor:pointer;'}
                           color:${wh==='TALMA'?'#0066B2':wh==='FRIO_AEREO'?'#0F6E56':'var(--text-mid)'};">
                    <option value="">—</option>
                    <option value="TALMA"     ${wh==='TALMA'     ?'selected':''} style="color:#0066B2;font-weight:700;">TALMA</option>
                    <option value="FRIO_AEREO"${wh==='FRIO_AEREO'?'selected':''} style="color:#0F6E56;font-weight:700;">FRIO AEREO</option>
                </select>
            </td>`;
        }
        if (settings.manifest) {
            html += `<td style="text-align:center;">${s.manifest_number ? '<span style="font-family:\'Courier New\',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:#7B2D8B;font-size:12px;font-weight:700;">'+esc(s.manifest_number)+'</span>' : '<span style="color:var(--text-light);font-size:11px;">—</span>'}</td>`;
        }
        html += `<td>${operationalStatusBadge(s.operational_status, s, 'bkLoad', !isMfClosed && !isLocked && !isFinal && hasPermission('Booking','edit'))}</td>`;

        let actions = '';
        if (isMfClosed) {
            // Manifiesto cerrado: solo badge, sin acciones operativas
            actions = '<span class="frozen-badge">🔒 Closed</span>';
        } else {
            if (!isLocked && hasPermission('Booking','edit')) {
                // Mismo estilo visual que tenía el botón "Change" (borde/color
                // sky) — Correcciones Booking A.6. El botón Change se eliminó
                // (A.7): reasignar vuelo/fecha ya se hace desde este mismo modal.
                actions += `<button class="action-btn-sm" style="border-color:var(--delta-sky);color:var(--delta-sky);" onclick="awbBookingOpenModal(${s.id})" title="Edit AWB">
                <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg> Edit</button>`;
            }
            if (s.operational_status === 'RECEIVED' && hasPermission('Booking','delete')) {
                actions += `<button class="action-btn-sm danger" onclick="bkDelete(${s.id})" title="Delete AWB">🗑</button>`;
            }
            if (isLocked) {
                actions += '<span class="frozen-badge">🔒 Closed</span>';
            }
            if (!actions) actions = '<span style="color:var(--text-light);font-size:11px;">—</span>';
        }
        html += `<td><div style="display:flex;gap:5px;flex-wrap:wrap;">${actions}</div></td>`;
        html += '</tr>';
        }); // end grp.items.forEach

        // ── Subtotal row ─────────────────────────────────────────
        const stPcs    = grp.items.reduce(function(a,s){ return a+parseInt(s.pieces||0); }, 0);
        const stGross  = grp.items.reduce(function(a,s){ return a+parseFloat(s.gross_weight||0); }, 0);
        const stChg    = grp.items.reduce(function(a,s){ return a+parseFloat(s.chg_weight||0); }, 0);
        const stTarifa = grp.items.reduce(function(a,s){ return a+parseFloat(s.net_rate||0); }, 0);
        const stVolume = grp.items.reduce(function(a,s){ return a+parseFloat(s.volume_m3||0); }, 0);
        html += '<tr style="background:#DCE8F5;font-weight:700;border-top:2px solid var(--delta-navy);border-bottom:2px solid var(--delta-navy);">'
            + '<td colspan="2" style="padding:7px 14px;font-size:11px;color:var(--delta-navy);">'
            + 'Subtotal &nbsp;—&nbsp;'
            + grp.items.length + ' AWB(s)</td>'
            + (settings.agent       ? '<td></td>' : '')
            + (settings.shipper     ? '<td></td>' : '')
            + (settings.consignee   ? '<td></td>' : '')
            + (settings.origin      ? '<td></td>' : '')
            + (settings.destination ? '<td></td>' : '')
            + (settings.description ? '<td></td>' : '')
            + (settings.pieces      ? '<td style="text-align:right;padding:7px 10px;">' + fmtWgt0(stPcs) + '</td>' : '')
            + (settings.chg_wgt     ? '<td style="text-align:right;padding:7px 10px;">' + fmtWgt0(stChg) + '</td>' : '')
            + (settings.gross_wgt   ? '<td style="text-align:right;padding:7px 10px;">' + fmtWgt0(stGross) + '</td>' : '')
            + (settings.tarifa      ? '<td style="text-align:right;padding:7px 10px;">' + fmtWgt0(stTarifa) + '</td>' : '')
            + (settings.volume      ? '<td style="text-align:right;padding:7px 10px;">' + fmtVol1(stVolume) + '</td>' : '')
            + (settings.uld         ? '<td></td>' : '')
            + (settings.warehouse   ? '<td></td>' : '')
            + (settings.manifest    ? '<td></td>' : '')
            + '<td colspan="2"></td>'
            + '</tr>'
            + '<tr><td colspan="99" style="height:5px;background:#E8ECF2;border:none;"></td></tr>';

    }); // end screenGroupOrder.forEach

    html += '</tbody></table>';
    wrap.innerHTML = html;

    const pag = document.getElementById('bkPagination');
    if (totalPages > 1) {
        pag.style.display = 'flex';
        document.getElementById('bkPageInfo').textContent =
            'Mostrando ' + (start+1) + '–' + Math.min(start+BK_PER_PAGE, bkList.length) + ' de ' + bkList.length;
        let btns = '';
        for (let i = 1; i <= totalPages; i++) {
            btns += `<button class="page-btn ${i===bkPage?'active':''}" onclick="bkGoPage(${i})">${i}</button>`;
        }
        document.getElementById('bkPageBtns').innerHTML = btns;
    } else {
        pag.style.display = 'none';
    }
}
function bkGoPage(p) { bkPage = p; bkRender(); }

// ── Inline edit de prioridad ──────────────────────────────────
function bkEditPriority(badge) {
    if (badge.querySelector('input')) return; // ya está editando
    const id   = badge.dataset.id;
    const flt  = badge.dataset.flt;
    const date = badge.dataset.date;
    const cur  = parseInt(badge.textContent.trim()) || '';

    const input = document.createElement('input');
    input.type  = 'number'; input.min = '1'; input.max = '99';
    input.value = cur;
    input.style.cssText = 'width:44px;text-align:center;font-size:12px;font-weight:700;'
                        + 'border:none;outline:none;background:transparent;padding:0;';

    badge.innerHTML = '';
    badge.appendChild(input);
    input.focus(); input.select();

    const commit = async () => {
        const newPrio = parseInt(input.value);
        if (!newPrio || newPrio < 1 || newPrio === cur) { bkLoad(); return; }
        await bkReorderPriorities(flt, date, parseInt(id), newPrio);
        bkLoad();
    };
    input.addEventListener('blur',   commit);
    input.addEventListener('keydown', e => {
        if (e.key === 'Enter')  { e.preventDefault(); commit(); }
        if (e.key === 'Escape') { bkLoad(); }
    });
}

// ── Reordenar prioridades del vuelo ──────────────────────────
// movingId / newPrio: si se mueve una guía específica a una posición
// Si movingId=null, solo compacta (elimina huecos)
async function bkReorderPriorities(fltNumber, fltDate, movingId, newPrio) {
    try {
        await apiFetch('/shipments/priority_api.php', {
            method: 'POST',
            body: JSON.stringify({
                flt_number: fltNumber,
                flt_date:   fltDate,
                moving_id:  movingId,
                new_priority: newPrio,
                user_id: currentUser?.id || null,
            })
        });
    } catch(e) { console.error('Priority reorder error:', e); }
}

// ══════════════════════════════════════════════════════════════
//  Reordenamiento de prioridades por Drag & Drop (Booking)
//  priority_api.php reordena así: saca moving_id de la secuencia
//  actual y lo vuelve a insertar en la posición new_priority
//  (1-indexed) de la secuencia YA SIN ese elemento, recorriendo
//  el resto. Por eso, al soltar sobre otra fila, hay que calcular
//  la posición del objetivo EN ESA SECUENCIA SIN LA GUÍA ARRASTRADA
//  (se resta 1 si el objetivo estaba después de la guía arrastrada).
// ══════════════════════════════════════════════════════════════
function bkDragStart(e) {
    const tr = e.currentTarget;
    bkDragId = tr.dataset.id;
    bkDragGroupKey = tr.dataset.group;
    tr.classList.add('bk-dragging');
    e.dataTransfer.effectAllowed = 'move';
    try { e.dataTransfer.setData('text/plain', bkDragId); } catch(err) {}
}

function bkDragOver(e) {
    const tr = e.currentTarget;
    if (!bkDragId || tr.dataset.group !== bkDragGroupKey) return; // solo se reordena dentro del mismo vuelo/fecha
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    if (String(tr.dataset.id) === String(bkDragId)) return; // no marcar sobre sí misma
    const rect = tr.getBoundingClientRect();
    const isTopHalf = (e.clientY - rect.top) < rect.height / 2;
    tr.classList.toggle('bk-drag-over-top', isTopHalf);
    tr.classList.toggle('bk-drag-over-bottom', !isTopHalf);
}

function bkDragLeave(e) {
    e.currentTarget.classList.remove('bk-drag-over-top', 'bk-drag-over-bottom');
}

async function bkDrop(e) {
    e.preventDefault();
    const tr = e.currentTarget;
    tr.classList.remove('bk-drag-over-top', 'bk-drag-over-bottom');

    const targetId = tr.dataset.id;
    const draggedId = bkDragId;
    const groupKey = bkDragGroupKey;
    document.querySelectorAll('.bk-draggable-row').forEach(function(r){ r.classList.remove('bk-dragging'); });

    if (!draggedId || tr.dataset.group !== groupKey || String(targetId) === String(draggedId)) {
        bkDragId = null; bkDragGroupKey = null;
        return;
    }

    const rect = tr.getBoundingClientRect();
    const isTopHalf = (e.clientY - rect.top) < rect.height / 2;

    const [flt, date] = groupKey.split('|');
    const draggedItem = bkList.find(function(s){ return String(s.id) === String(draggedId); });
    const targetItem   = bkList.find(function(s){ return String(s.id) === String(targetId); });
    bkDragId = null; bkDragGroupKey = null;
    if (!draggedItem || !targetItem) return;

    const pDrag   = parseInt(draggedItem.priority) || 0;
    const pTarget = parseInt(targetItem.priority)  || 0;
    // Posición del objetivo en la secuencia sin la guía arrastrada:
    const targetPos = pTarget > pDrag ? pTarget - 1 : pTarget;
    const newPrio = isTopHalf ? targetPos : targetPos + 1;

    await bkReorderPriorities(flt, date, parseInt(draggedId), newPrio);
    bkLoad();
}

function bkDragEnd(e) {
    document.querySelectorAll('.bk-draggable-row').forEach(function(r){
        r.classList.remove('bk-dragging', 'bk-drag-over-top', 'bk-drag-over-bottom');
    });
    bkDragId = null; bkDragGroupKey = null;
}

function bkPrint() {
    const flt     = bkSavedFilters.flt     || 'All flights';
    const station = bkSavedFilters.station || 'LIM';
    const status  = bkSavedFilters.status  || 'All';
    const dates   = (bkSavedFilters.dates  || []).join(' → ') || 'All dates';

    const settings = bkLoadColSettings().cols;
    let rows = '';

    // Group by flt_number + flt_date
    const groups = {};
    const groupOrder = [];
    bkList.forEach(function(s) {
        const key = (s.flt_number||'') + '|' + (s.flt_date||'');
        if (!groups[key]) {
            groups[key] = { flt: s.flt_number, date: s.flt_date, orig: s.flt_orig, dest: s.flt_dest, items: [] };
            groupOrder.push(key);
        }
        groups[key].items.push(s);
    });

    const multiGroup = true; // siempre mostrar agrupación por vuelo

    groupOrder.forEach(function(key) {
        const g = groups[key];

        // Group header row
        const route = (g.orig||'') + (g.dest ? ' → '+g.dest : '');
        rows += '<tr style="background:var(--delta-navy);color:#fff;">'
            + '<td colspan="99" style="padding:8px 14px;font-size:12px;font-weight:800;letter-spacing:.04em;">'
            + '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="margin-right:7px;vertical-align:middle;"><path d="M22 2L11 13"/><path d="M22 2L15 22l-4-9-9-4 20-7z"/></svg>'
            + esc(g.flt||'') + ' &nbsp;&nbsp; ' + route
            + ' &nbsp;&nbsp; <span style="font-weight:400;opacity:.85;">' + fmtDate(g.date) + '</span>'
            + ' &nbsp;&nbsp; <span style="font-weight:400;opacity:.7;font-size:11px;">' + g.items.length + ' guía(s)</span>'
            + '</td></tr>';

        g.items.forEach(function(s, i) {
            const bg = i % 2 === 0 ? '#fff' : '#F4F6F9';
        const wh = s.warehouse ? s.warehouse.replace('_',' ') : '—';
        rows += `<tr style="background:${bg};">
            <td style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-weight:700;color:#002244;">${esc(s.awb||'—')}</td>
            <td>${esc(s.flt_number||'—')}</td>
            <td>${fmtDate(s.flt_date)}</td>
            <td style="text-align:center;">${s.priority||5}</td>
            ${settings.agent       ? `<td>${esc(s.agent_name||'—')}</td>` : ''}
            ${settings.description ? `<td>${esc(s.description||'—')}</td>` : ''}
            ${settings.pieces      ? `<td style="text-align:right;">${s.pieces||0}</td>` : ''}
            ${settings.gross_wgt   ? `<td style="text-align:right;">${fmtNum(s.gross_weight)} kg</td>` : ''}
            ${settings.chg_wgt     ? `<td style="text-align:right;">${fmtNum(s.chg_weight)} kg</td>` : ''}
            ${settings.warehouse   ? `<td style="text-align:center;font-weight:700;">${wh}</td>` : ''}
            <td style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-size:11px;">${s.manifest_number||'—'}</td>
            <td>${OPERATIONAL_LABELS[s.operational_status]||s.operational_status}</td>
        </tr>`;
        }); // end g.items.forEach

        // Subtotal row per flight group
        const totPcs   = g.items.reduce(function(a,s){ return a + parseInt(s.pieces||0); }, 0);
        const totGross = g.items.reduce(function(a,s){ return a + parseFloat(s.gross_weight||0); }, 0);
        const totChg   = g.items.reduce(function(a,s){ return a + parseFloat(s.chg_weight||0); }, 0);
        const totVol   = g.items.reduce(function(a,s){ return a + parseFloat(s.volume_m3||0); }, 0);
        rows += '<tr style="background:#DCE8F5;font-weight:700;border-top:2px solid var(--delta-navy);border-bottom:3px solid var(--delta-navy);">'
            + '<td colspan="4" style="padding:7px 14px;font-size:11px;color:var(--delta-navy);">'
            + 'Subtotal &nbsp; <strong>' + esc(g.flt||'') + '</strong> &nbsp;—&nbsp; '
            + g.items.length + ' AWB(s)</td>'
            + (settings.agent       ? '<td></td>' : '')
            + (settings.description ? '<td></td>' : '')
            + (settings.pieces      ? '<td style="text-align:right;padding:7px 10px;">' + totPcs + ' pzas</td>' : '')
            + (settings.gross_wgt   ? '<td style="text-align:right;padding:7px 10px;">' + fmtNum(totGross) + ' kg</td>' : '')
            + (settings.chg_wgt     ? '<td style="text-align:right;padding:7px 10px;">' + fmtNum(totChg) + ' kg</td>' : '')
            + (settings.warehouse   ? '<td></td>' : '')
            + '<td colspan="2"></td>'
            + '</tr>'
            + '<tr><td colspan="99" style="height:6px;background:#F0F2F5;"></td></tr>';

    }); // end groupOrder.forEach

    const html = `<!DOCTYPE html><html><head><meta charset="UTF-8">
    <title>Booking Report — ${station} ${flt}</title>
    <style>
        body { font-family: Arial, sans-serif; font-size: 11px; margin: 16px; color: #000; }
        h2   { color: #002244; font-size: 14px; margin: 0 0 4px; }
        .meta { font-size: 11px; color: #5B6472; margin-bottom: 12px; }
        table { width: 100%; border-collapse: collapse; }
        th { background: #002244; color: #fff; padding: 5px 7px; text-align: left; font-size: 11px; border: 1px solid #002244; }
        td { padding: 4px 7px; border: 1px solid #ddd; font-size: 11px; }
        .footer { margin-top: 12px; font-size: 10px; color: #9AA3AF; border-top: 1px solid #eee; padding-top: 8px; }
        @media print { @page { size:A4 landscape; margin:10mm; } body { margin:0; } }
    </style></head><body>
    <h2>Booking Report — SIGHA</h2>
    <div class="meta">
        Station: <strong>${station}</strong> &nbsp;|&nbsp;
        FLT: <strong>${esc(flt)}</strong> &nbsp;|&nbsp;
        Date: <strong>${dates}</strong> &nbsp;|&nbsp;
        Status: <strong>${status||'All'}</strong> &nbsp;|&nbsp;
        Total: <strong>${bkList.length} AWBs</strong>
    </div>
    <table>
        <thead><tr>
            <th>AWB</th><th>FLT</th><th>Date</th><th>Prior.</th>
            ${settings.agent       ? '<th>Agent</th>' : ''}
            ${settings.description ? '<th>Description</th>' : ''}
            ${settings.pieces      ? '<th>Pcs</th>' : ''}
            ${settings.gross_wgt   ? '<th>GROSS WGT</th>' : ''}
            ${settings.chg_wgt     ? '<th>CHG WGT</th>' : ''}
            ${settings.warehouse   ? '<th>Warehouse</th>' : ''}
            <th>Manifest</th><th>Status</th>
        </tr></thead>
        <tbody>${rows}</tbody>
    </table>
    <div class="footer">SIGHA · Generado: ${new Date().toLocaleString('es-PE')} · ${bkList.length} guías</div>
    </body></html>`;

    const win = window.open('', '_blank');
    win.document.write(html);
    win.document.close();
    setTimeout(() => win.print(), 500);
}

// Guardar warehouse desde el dropdown inline de Booking
async function bkSaveWarehouse(shipmentId, warehouse) {
    // Bloquear si el manifiesto está cerrado
    const row = bkList.find(r => r.id == shipmentId);
    if (row && row.manifest_status === 'DEFINITIVE') {
        const sel = document.querySelector(`.bk-warehouse[data-id="${shipmentId}"]`);
        if (sel && row.warehouse) sel.value = row.warehouse;
        alert('No se puede modificar el almacén: el manifiesto ' + (row.manifest_number||'') + ' está cerrado.');
        return;
    }

    const sel = document.querySelector(`.bk-warehouse[data-id="${shipmentId}"]`);

    // Visual: color inmediato + spinner
    if (sel) {
        sel.style.color = warehouse === 'TALMA'      ? '#0066B2'
                        : warehouse === 'FRIO_AEREO' ? '#0F6E56'
                        : 'var(--text-mid)';
        sel.disabled = true;
        sel.style.opacity = '0.7';
    }

    try {
        const res = await apiFetch('/shipments/shipments_patch_api.php', {
            method: 'POST',
            body: JSON.stringify({ id: shipmentId, field: 'warehouse', value: warehouse || null })
        });

        if (sel) { sel.disabled = false; sel.style.opacity = '1'; }

        if (res && res.success) {
            const row = bkList.find(r => r.id == shipmentId);
            if (row) row.warehouse = warehouse;
            // Toast de confirmación
            bkShowToast('✓ Almacén guardado: ' + (warehouse ? warehouse.replace('_',' ') : '—'), '#0F6E56');
            if (sel) {
                sel.style.outline = '2px solid #2ECC71';
                setTimeout(() => { sel.style.outline = ''; }, 1500);
            }
        } else {
            bkShowToast('Error al guardar almacén: ' + (res?.message || 'Error desconocido'), '#C8102E');
            // Revert value
            if (sel && row) sel.value = row.warehouse || '';
        }
    } catch(e) {
        if (sel) { sel.disabled = false; sel.style.opacity = '1'; }
        bkShowToast('Error de conexión al guardar almacén', '#C8102E');
        if (sel && row) sel.value = row.warehouse || '';
    }
}

function bkShowToast(message, color) {
    // Remove existing toast
    const existing = document.getElementById('bkToast');
    if (existing) existing.remove();

    const toast = document.createElement('div');
    toast.id = 'bkToast';
    toast.style.cssText = [
        'position:fixed',
        'bottom:24px',
        'right:24px',
        'background:' + (color || '#1E3A5F'),
        'color:#fff',
        'padding:10px 18px',
        'border-radius:6px',
        'font-size:13px',
        'font-weight:600',
        'box-shadow:0 4px 16px rgba(0,0,0,.25)',
        'z-index:9999',
        'transition:opacity .3s',
        'opacity:1',
        'display:flex',
        'align-items:center',
        'gap:8px',
        'max-width:320px',
    ].join(';');
    toast.textContent = message;
    document.body.appendChild(toast);

    // Auto-dismiss after 2.5s
    setTimeout(function() {
        toast.style.opacity = '0';
        setTimeout(function() { toast.remove(); }, 300);
    }, 2500);
}
function bkUpdateCapacityBar(flt, fromDate, toDate, list) {
    const bar = document.getElementById('bkCapacityBar');
    if (!bar) return;

    // Solo mostrar cuando hay vuelo específico + fecha única
    const hasSingleFlt  = flt && flt.trim() && !flt.includes(',');
    const hasSingleDate = fromDate && toDate && fromDate === toDate;
    if (!hasSingleFlt || !hasSingleDate) { bar.style.display = 'none'; return; }

    // Calcular kg usados de las guías activas (excluir CANCELLED/REJECTED)
    const active = list.filter(s => !['CANCELLED','OFFLOADED'].includes(s.operational_status));
    const usedKg = active.reduce((sum, s) => sum + (parseFloat(s.gross_weight) || 0), 0);

    // Capacidad del vuelo: buscar en caché global o fetch directo
    let capacityKg = 0;
    const fltUpper = flt.trim().toUpperCase();

    // Buscar en cualquier cache disponible
    const caches = [
        typeof mfFlightsCache !== 'undefined' ? mfFlightsCache : [],
        typeof bkFlightsCache !== 'undefined' ? bkFlightsCache : [],
    ];
    for (const cache of caches) {
        const fltData = cache.find(f => f.flt_number === fltUpper);
        if (fltData) {
            capacityKg = parseFloat(fltData.capacity_kg || fltData.payload_kg || 0);
            if (capacityKg) break;
        }
    }

    // Si no hay cache, fetch rápido al API
    if (!capacityKg) {
        try {
            const station = document.getElementById('bkFilterStation')?.value || 'LIM';
            apiFetch('/flights/flights_api.php?station=' + station).then(function(res) {
                if (!res.success) return;
                window.bkFlightsCache = res.data || [];
                const fd = (res.data||[]).find(function(f){ return f.flt_number === fltUpper; });
                if (fd && (fd.capacity_kg || fd.payload_kg)) {
                    bkUpdateCapacityBar(flt, fromDate, toDate, list);
                }
            });
        } catch(e) {}
    }
    // Fallback: si no hay dato de capacidad, mostrar solo kg usados sin porcentaje
    if (!capacityKg) {
        bar.style.display = 'block';
        document.getElementById('bkCapLabel').textContent   = fltUpper + '  ·  ' + fmtDate(fromDate);
        document.getElementById('bkCapNumbers').textContent = fmtNum(usedKg) + ' kg booked (' + active.length + ' AWBs)';
        document.getElementById('bkCapFill').style.width    = '0%';
        document.getElementById('bkCapFill').style.background = 'var(--delta-navy)';
        document.getElementById('bkCapUsed').textContent   = 'Booked: ' + fmtNum(usedKg) + ' kg';
        document.getElementById('bkCapFree').textContent   = '';
        document.getElementById('bkCapPct').textContent    = '';
        return;
    }

    const freeKg = Math.max(0, capacityKg - usedKg);
    const pct    = Math.min(100, Math.round((usedKg / capacityKg) * 100));
    const fillColor = pct >= 90 ? '#C8102E' : pct >= 70 ? '#FFC72C' : '#0F6E56';

    bar.style.display = 'block';
    document.getElementById('bkCapLabel').textContent   = fltUpper + '  ·  ' + fmtDate(fromDate);
    document.getElementById('bkCapNumbers').textContent = fmtNum(usedKg) + ' / ' + fmtNum(capacityKg) + ' kg';
    document.getElementById('bkCapFill').style.width    = pct + '%';
    document.getElementById('bkCapFill').style.background = fillColor;
    document.getElementById('bkCapUsed').textContent   = 'Booked: ' + fmtNum(usedKg) + ' kg';
    document.getElementById('bkCapFree').textContent   = 'Available: ' + fmtNum(freeKg) + ' kg';
    document.getElementById('bkCapPct').textContent    = pct + '%';
}

// ── Eliminar ───────────────────────────────────────────────────
async function bkDelete(id) {
    if (!confirm('¿Eliminar esta guía? Esta acción no se puede deshacer.')) return;
    try {
        const res = await apiFetch('/shipments/shipments_api.php?id=' + id, { method: 'DELETE' });
        if (res.success) bkLoad();
        else alert(res.message || 'No se pudo eliminar.');
    } catch(e) { alert('Error de conexión.'); }
}

// ── Reasignar Vuelo ────────────────────────────────────────────
// Mueve la guía a otro vuelo/fecha y reinicia todo el ciclo
// (operational_status -> CONFIRMED, quality_status -> NOT_READY,
// se borra el manifiesto anterior). Bloqueado si quality_status
// ya es VALIDATED.
let bkReassignShipmentId = null;
let bkReassignFlightsCache = [];

async function bkOpenReassignModal(shipmentId, currentFlt, currentDate, qualityStatus) {
    if (qualityStatus === 'VALIDATED') {
        alert('No se puede reasignar: esta guía ya fue Validada por Calidad. Revierta primero su estado de Calidad en Control de Calidad.');
        return;
    }
    bkReassignShipmentId = shipmentId;

    const overlay = document.createElement('div');
    overlay.className = 'modal-overlay';
    overlay.id = 'bkReassignModal';
    overlay.innerHTML = `
        <div class="modal-box" style="width:460px;">
            <div class="modal-box-header">
                Reasignar Vuelo
                <button onclick="bkCloseReassignModal()" style="background:none;border:none;font-size:20px;cursor:pointer;color:var(--text-mid);">&times;</button>
            </div>
            <div class="modal-box-body">
                <div style="font-size:12px;color:var(--text-mid);margin-bottom:12px;">
                    Vuelo actual: <strong>${esc(currentFlt||'—')}</strong> &middot; ${fmtDate(currentDate)}
                </div>
                <div style="background:#FFF8E1;border:1px solid #FFE082;border-radius:6px;padding:8px 10px;font-size:11px;color:#946C00;margin-bottom:14px;">
                    Al reasignar, el estado de la guía volverá a <b>Confirmada</b> y se borrará cualquier dato de manifiesto previo (Nº manifiesto, almacén, ULD, skids). El ciclo de Calidad también se reinicia.
                </div>
                <div class="awb-field" style="margin-bottom:10px;">
                    <label>Nuevo Vuelo *</label>
                    <select id="bkReassignFlight" class="awb-input" style="width:100%;" onchange="bkOnReassignFlightChange()">
                        <option value="">— Seleccionar —</option>
                    </select>
                </div>
                <div class="awb-field" style="margin-bottom:10px;">
                    <label>Nueva Fecha *</label>
                    <input type="date" id="bkReassignDate" class="awb-input" style="width:100%;">
                </div>
                <div style="display:flex;gap:14px;margin-bottom:10px;">
                    <div class="awb-field" style="flex:1;"><label>Orig</label><input id="bkReassignOrig" class="awb-input" type="text" readonly style="width:100%;background:#EEF1F5;text-align:center;font-weight:700;"></div>
                    <div class="awb-field" style="flex:1;"><label>Dest</label><input id="bkReassignDest" class="awb-input" type="text" readonly style="width:100%;background:#EEF1F5;text-align:center;font-weight:700;"></div>
                </div>
                <textarea id="bkReassignReason" placeholder="Motivo del cambio (opcional)..." style="width:100%;height:50px;border:1px solid var(--border);border-radius:4px;padding:8px;font-size:13px;outline:none;resize:vertical;"></textarea>
                <div id="bkReassignMsg" style="font-size:12px;font-weight:600;margin-top:8px;"></div>
            </div>
            <div class="modal-box-footer">
                <button class="btn-secondary" onclick="bkCloseReassignModal()">Cancelar</button>
                <button class="btn-primary" onclick="bkConfirmReassign()">Reasignar</button>
            </div>
        </div>
    `;
    document.body.appendChild(overlay);

    const station = document.getElementById('bkFilterStation')?.value || 'LIM';
    try {
        const res = await apiFetch('/flights/flights_api.php?station=' + station);
        bkReassignFlightsCache = res.success ? res.data : [];
        const sel = document.getElementById('bkReassignFlight');
        bkReassignFlightsCache.forEach(f => {
            const o = document.createElement('option');
            o.value = f.id;
            o.textContent = f.flt_number + '  ' + f.flt_orig + '→' + f.flt_dest;
            o.dataset.fltNumber = f.flt_number;
            o.dataset.orig = f.flt_orig;
            o.dataset.dest = f.flt_dest;
            sel.appendChild(o);
        });
    } catch(e) {}
}

function bkOnReassignFlightChange() {
    const sel = document.getElementById('bkReassignFlight');
    const opt = sel.options[sel.selectedIndex];
    document.getElementById('bkReassignOrig').value = opt?.dataset.orig || '';
    document.getElementById('bkReassignDest').value = opt?.dataset.dest || '';
}

function bkCloseReassignModal() {
    const el = document.getElementById('bkReassignModal');
    if (el) el.remove();
    bkReassignShipmentId = null;
}

async function bkConfirmReassign() {
    const sel = document.getElementById('bkReassignFlight');
    const opt = sel.options[sel.selectedIndex];
    const date = document.getElementById('bkReassignDate').value;
    const msg = document.getElementById('bkReassignMsg');

    if (!sel.value || !date) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Selecciona el nuevo vuelo y la fecha.';
        return;
    }

    msg.style.color = 'var(--text-mid)';
    msg.textContent = 'Reasignando...';

    try {
        const res = await apiFetch('/shipments/shipment_reassign_flight_api.php', {
            method: 'POST',
            body: JSON.stringify({
                shipment_id:  bkReassignShipmentId,
                flight_id:    parseInt(sel.value),
                flt_number:   opt.dataset.fltNumber,
                flt_date:     date,
                flt_orig:     opt.dataset.orig,
                flt_dest:     opt.dataset.dest,
                reason:       document.getElementById('bkReassignReason').value.trim(),
                user_id:      currentUser?.id || null,
                reset_status: true,  // siempre reiniciar a CONFIRMED al reasignar
            })
        });
        if (res.success) {
            // Reordenar prioridades del vuelo ORIGINAL (compactar hueco)
            const origRow = bkList.find(r => r.id == bkReassignShipmentId);
            if (origRow) {
                await bkReorderPriorities(origRow.flt_number, origRow.flt_date, null, null);
            }
            // Asignar prioridad en el nuevo vuelo (al final)
            const newFlt  = opt.dataset.fltNumber || sel.options[sel.selectedIndex]?.dataset?.fltNumber || '';
            const newDate = date;
            if (newFlt && newDate) {
                await bkReorderPriorities(newFlt, newDate, null, null);
            }
            bkCloseReassignModal();
            bkLoad();
        } else {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = res.message || 'Error al reasignar.';
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Error de conexión.';
    }
}
</script>


<style>
/* ── Estándar visual de número AWB (###-########) ──────────────────────
   Fuente monoespaciada + alineación numérica tabular en todos los módulos:
   Booking, Pre-Manifest, ULD Builder, Post-Flight Reports, Outbound Report,
   Departure Notice, Flight Summary y cualquier modal/tabla que muestre AWB. */
.awb-mono {
    font-family: 'Courier New', monospace;
    font-variant-numeric: tabular-nums;
    letter-spacing: .03em;
    white-space: nowrap;
}
/* ── Indicador de capacidad de vuelo (Payload vs Gross Wgt comprometido) ── */
.cap-bar-wrap { cursor: help; }
.cap-bar-track {
    position: relative; height: 24px; border-radius: 12px;
    background: rgba(255,255,255,.16);
    box-shadow: inset 0 1px 4px rgba(0,0,0,.3), inset 0 0 0 1px rgba(255,255,255,.15);
    overflow: hidden;
}
.cap-bar-fill {
    position: absolute; left: 0; top: 0; bottom: 0;
    border-radius: 12px;
    transition: width .5s cubic-bezier(.4,0,.2,1), background-color .3s;
}
.cap-bar-gloss {
    position: absolute; left: 0; top: 0; height: 50%;
    background: linear-gradient(180deg, rgba(255,255,255,.35), rgba(255,255,255,0));
    border-radius: 12px 12px 0 0;
    pointer-events: none;
}
.cap-bar-label {
    position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
    font-size: 11px; font-weight: 800; color: #fff; letter-spacing: .02em; white-space: nowrap;
    text-shadow: 0 1px 3px rgba(0,0,0,.65);
    pointer-events: none;
}
.cap-bar-pulse { animation: capBarPulse 1.4s ease-in-out infinite; }
@keyframes capBarPulse {
    0%, 100% { filter: brightness(1); }
    50%      { filter: brightness(1.25); }
}
.cap-bar-over .cap-bar-track { box-shadow: inset 0 1px 4px rgba(0,0,0,.3), 0 0 0 2px #E8384F66; }

/* ── Booking: reordenamiento de prioridades por Drag & Drop ── */
.bk-draggable-row { cursor: move; }
/* Sin el prefijo .bk-draggable-row para que Pre-Manifest (donde solo el badge
   de PRIOR es "draggable", no toda la fila) pueda reusar el mismo feedback
   visual de arrastre sin heredar el cursor "move" en toda la fila. */
.bk-dragging { opacity: .35; }
.bk-drag-over-top    { border-top: 3px solid var(--delta-sky) !important; background: #EAF4FC !important; }
.bk-drag-over-bottom { border-bottom: 3px solid var(--delta-sky) !important; background: #EAF4FC !important; }

/* ── AWB Entry: modal pantalla completa ── */
.awb-required-empty {
    background: #FFF8E1 !important;
    border-color: #F59E0B !important;
    box-shadow: 0 0 0 2px rgba(245,158,11,.15) !important;
}
/* Casilla con valor cargado: fondo verde claro como referencia visual rápida
   de qué campos ya se completaron. No se aplica sobre campos de solo lectura
   (Chg Weight, Charges Summary, etc.) que ya tienen su propio estilo. */
.awb-has-value {
    background: #E8F8F0 !important;
}
.awb-rate-warn {
    background: #FFF0DC !important;
    border-color: #E07B00 !important;
}
.awb-input-err {
    background: #FFF0F0 !important;
    border-color: #C8102E !important;
}
.awb-modal-overlay {
  position: fixed; inset: 0; background: #fff; z-index: 6000;
  display: flex; flex-direction: column; overflow: hidden;
}
.awb-modal-header {
  background: linear-gradient(100deg, var(--delta-navy) 0%, #004080 100%);
  padding: 10px 18px; display: flex; align-items: center; justify-content: space-between;
  flex-shrink: 0; border-bottom: 3px solid var(--delta-accent);
}
.awb-modal-title {
  font-family: 'Barlow Condensed', sans-serif; font-size: 17px; font-weight: 700;
  color: #fff; letter-spacing: .06em; text-transform: uppercase;
  display: flex; align-items: center; gap: 10px;
}
.awb-modal-badge {
  font-family: 'Courier New', monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em; font-size: 18px; font-weight: 700;
  color: var(--delta-accent); background: rgba(0,0,0,.25); padding: 3px 14px;
  border-radius: 3px; letter-spacing: .1em;
}
.awb-modal-close {
  background: none; border: none; color: rgba(255,255,255,.7); font-size: 22px;
  cursor: pointer; padding: 0 4px; line-height: 1;
}
.awb-modal-close:hover { color: #fff; }
.awb-modal-body { overflow-y: auto; flex: 1; padding: 16px 20px; background: #fff; }
.awb-modal-footer {
  padding: 10px 18px; display: flex; align-items: center; gap: 10px;
  border-top: 1px solid var(--border); background: #fff; flex-shrink: 0;
}

.awb-section {
  border: 1px solid var(--border); border-radius: 6px; overflow: hidden; margin-bottom: 12px;
}
.awb-section-hdr {
  background: #F4F7FB; padding: 9px 14px; font-family: 'Barlow Condensed', sans-serif;
  font-size: 13px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--delta-navy); border-bottom: 1px solid var(--border);
  display: flex; align-items: center; gap: 8px;
}
.awb-section-body { padding: 12px 14px; }
.awb-field { display: flex; flex-direction: column; gap: 4px; }
.awb-field label {
  font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-mid);
  letter-spacing: .05em;
}
.awb-input {
  height: 36px; border: 1px solid var(--border); border-radius: 4px; padding: 0 10px;
  font-family: 'Barlow', sans-serif; font-size: 14px; color: var(--text-dark);
  background: #fff; outline: none;
}
.awb-input:focus { border-color: var(--delta-sky); }

.charges-row {
  display: grid; grid-template-columns: 1fr 100px 60px 32px; gap: 5px; align-items: center;
  margin-bottom: 6px;
}
.charges-row select, .charges-row input { height: 32px; font-size: 12px; }
.toggle-dc {
  height: 32px; border: none; border-radius: 4px; font-size: 11px; font-weight: 700;
  cursor: pointer; color: #fff; background: var(--delta-navy);
}
.toggle-dc[data-type="DA"] { background: var(--delta-red); }
.charge-remove-btn {
  width: 32px; height: 32px; border: 1px solid var(--border); background: #fff;
  border-radius: 4px; cursor: pointer; color: var(--text-light); font-size: 14px;
}
.charge-remove-btn:hover { color: var(--delta-red); border-color: var(--delta-red); }
.charge-add-btn {
  width: 100%; height: 32px; border: 1px dashed var(--border); background: #fff;
  border-radius: 4px; cursor: pointer; color: var(--delta-sky); font-size: 12px; font-weight: 700;
  margin-top: 4px;
}
.charge-add-btn:hover { background: #F4F9FF; }
</style>

<script>
// ══════════════════════════════════════════════════════════════
//  MODAL AWB ENTRY (awb*) — formulario completo de la guía.
//  Ya no es una vista de menú independiente; se invoca como
//  modal desde Booking ("New AWB" / "Editar") y desde
//  Control de Calidad ("Revisar/Editar").
// ══════════════════════════════════════════════════════════════
let awbEditingId = null;
let awbCharges = []; // [{charge_code, amount, charge_type}]
let awbLegs = [];    // [{flt_number, flt_orig, flt_dest, dep_date, dep_time, arr_date, arr_time}] — itinerario (Flight Details)
let awbLegsFirstRowLocked = true;  // true = fila 1 viene de FLT* (modal completo); false = fila 1 editable (modal Booking)
let awbSimpleMode = false;         // true cuando está abierto el modal simplificado "SIGHA – Booking"
let awbVolumeManuallyEdited = false; // true si el usuario ya tocó Volume a mano (deja de copiarse desde Weight)
let awbChgWgtManuallyEdited = false; // true si el usuario ya editó CHG WGT (K) a mano (deja de copiarse desde Weight)
let awbCatalogsCache = { agent: [], shipper: [], consignee: [], airport: [], charges: [], flights: [], goods: [], products: [] };
let awbDuplicateCheckToken = 0; // evita que una respuesta tardía de una verificación de AWB duplicado pise a la más reciente
let awbCatalogsLoaded = false;

// (Vista de lista 'AWB Entry' eliminada — el modal awbOpenModal/
// awbEntryEdit se sigue usando, invocado desde Booking y
// Control de Calidad. Ver awbModalHTML() más abajo.)


// ── Catálogos (compartidos por todo el modal) ────────────────
async function awbLoadCatalogs() {
    if (awbCatalogsLoaded) return;
    try {
        const station = document.getElementById('awbm_station')?.value
            || document.getElementById('bkFilterStation')?.value
            || document.getElementById('mfStation')?.value
            || 'LIM';
        const sq = '&station=' + encodeURIComponent(station);

        const [agents, shippers, consignees, airports, charges, flights, goods, products] = await Promise.all([
            apiFetch('/catalog/catalog_api.php?table=freight_forwarder' + sq),
            apiFetch('/catalog/catalog_api.php?table=shipper' + sq),
            apiFetch('/catalog/catalog_api.php?table=consignee' + sq),
            apiFetch('/catalog/catalog_api.php?table=airport'),
            apiFetch('/catalog/catalog_api.php?table=other_charges_catalog'),
            apiFetch('/flights/flights_api.php?station=' + encodeURIComponent(station)),
            apiFetch('/catalog/catalog_api.php?table=goods_catalog'),
            apiFetch('/catalog/catalog_api.php?table=product_code'),
        ]);
        awbCatalogsCache.agent     = agents.success ? agents.data : [];
        awbCatalogsCache.shipper   = shippers.success ? shippers.data : [];
        awbCatalogsCache.consignee = consignees.success ? consignees.data : [];
        awbCatalogsCache.airport   = airports.success ? airports.data : [];
        awbCatalogsCache.charges   = charges.success ? charges.data : [];
        awbCatalogsCache.flights   = flights.success ? flights.data : [];
        awbCatalogsCache.goods     = goods.success ? goods.data : [];
        awbCatalogsCache.products  = products.success ? products.data : [];
        awbCatalogsLoaded = true;
    } catch(e) { console.warn('Error cargando catálogos:', e); }
}

// ULD types — loaded dynamically from catalog, fallback to hardcoded
let AWB_ULD_TYPES = ['AKE','AKH','AKN','ALF','AMA','AMF','AMJ','APE','AQ6','AQF','LD3','LD6','LD7','LD8','LD9','LD11','PAG','PMC','PLA','PLB','RAP','RKN'];
let MF_ULD_TYPES  = AWB_ULD_TYPES.slice();
let MF_ULD_OPTIONS = AWB_ULD_TYPES.map(u => [u, u]); // [value, label] pairs
let AWB_ULD_CAPACITY = {}; // { PMC: 10 (m3), ... } — usado para calcular Qty ULD automático

(async function loadUldTypes() {
    try {
        const res = await apiFetch('/catalog/catalog_api.php?table=uld_types&show_inactive=0');
        if (res.success && res.data && res.data.length) {
            const sorted = res.data.sort(function(a,b){ return (a.sort_order||99)-(b.sort_order||99); });
            const codes = sorted.map(function(u){ return u.uld_code; });
            AWB_ULD_TYPES = codes;
            MF_ULD_TYPES  = codes;
            // Full options for Pre-Manifest: "PMC — Pallet 88x125 Aluminio"
            MF_ULD_OPTIONS = sorted.map(function(u){
                return [u.uld_code, u.description || u.uld_code];
            });
            AWB_ULD_CAPACITY = {};
            sorted.forEach(function(u){
                if (u.capacity_m3) AWB_ULD_CAPACITY[u.uld_code] = parseFloat(u.capacity_m3);
            });
            // Update MF_EDITABLE_COLS with rich options
            if (typeof MF_EDITABLE_COLS !== 'undefined') {
                MF_EDITABLE_COLS.uld_type.options = MF_ULD_OPTIONS;
            }
        }
    } catch(e) {}
})();

// ── Abrir modal (crear o editar) ──────────────────────────────
async function awbOpenModal(id = null) {
    // Guardar filtros de Booking ANTES de abrir el modal (el overlay cubre el DOM)
    if (currentView === 'booking') {
        if (document.getElementById('bkFilterStation'))
            bkSavedFilters.station = document.getElementById('bkFilterStation').value;
        if (document.getElementById('bkFilterStatus'))
            bkSavedFilters.status  = document.getElementById('bkFilterStatus').value;
        if (document.getElementById('bkFilterFlt'))
            bkSavedFilters.flt     = document.getElementById('bkFilterFlt').value;
        if (document.getElementById('bkFilterQ'))
            bkSavedFilters.q       = document.getElementById('bkFilterQ').value;
        if (bkDatePicker && bkDatePicker.selectedDates.length)
            bkSavedFilters.dates   = bkDatePicker.selectedDates.map(d =>
                d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0')
            );
    }

    awbEditingId = id;
    awbCharges = [];
    awbLegs = [];
    awbLegsFirstRowLocked = true;
    awbSimpleMode = false;
    awbVolumeManuallyEdited = false;
    awbChgWgtManuallyEdited = false;

    // Always reset catalog cache when opening from Quality Control or other views
    // so catalogs reload with the correct station from the shipment
    if (currentView !== 'booking') {
        awbCatalogsLoaded = false;
    }

    // Remove any existing modal first (prevents stacking)
    const existingModal = document.getElementById('awbModal');
    if (existingModal) existingModal.remove();

    // Append modal HTML FIRST so awbm_station element exists before loadCatalogs reads it
    const modal = document.createElement('div');
    modal.className = 'awb-modal-overlay';
    modal.id = 'awbModal';
    modal.innerHTML = awbModalHTML();
    document.body.appendChild(modal);

    // If editing existing shipment, pre-set station before loading catalogs
    let preloadedShipment = null;
    if (id) {
        try {
            const preRes = await apiFetch('/shipments/shipments_api.php?id=' + id);
            if (preRes.success) {
                preloadedShipment = preRes.data;
                const stEl = document.getElementById('awbm_station');
                if (stEl && preloadedShipment.station) stEl.value = preloadedShipment.station;
            }
        } catch(e) { console.error('Pre-fetch error:', e); }
    }

    await awbLoadCatalogs();
    awbPopulateSelects();

    if (id) {
        await awbFillFromShipment(id, preloadedShipment);
    } else {
        document.getElementById('awbm_station').value = document.getElementById('bkFilterStation')?.value || document.getElementById('mfStation')?.value || 'LIM';
        // Origin por defecto = estación del usuario autenticado (Correcciones
        // Booking #3); el usuario puede cambiarlo manualmente igual.
        const origSelDefault = document.getElementById('awbm_routing_orig');
        if (origSelDefault && !origSelDefault.value) origSelDefault.value = awbGetUserHomeStation();
        awbRefreshDestinationOptions();
        // Reset catalog cache so it reloads with the correct station
        awbCatalogsLoaded = false;
        awbAddChargeRow(); // arranca con 1 fila vacía
        awbRenderCharges();
        // Fila 1 de Flight Details en blanco desde el arranque — ya no depende
        // de elegir antes un vuelo en FLT * (misma lógica que el modal Booking).
        if (!awbLegs.length) {
            awbLegs = [{ flt_number:'', flt_orig:'', flt_dest:'', dep_date:'', dep_time:'', arr_date:'', arr_time:'' }];
        }
        awbRenderLegs();
    }

    // Resaltar en verde claro los campos que ya vienen con un valor cargado
    // (defaults como STA/WT-VAL/Other/Rate al crear, o todos los datos
    // existentes al editar una guía).
    awbHighlightFilled(document.getElementById('awbModal'));
}

function awbEntryEdit(id) {
    try {
        awbOpenModal(id);
    } catch(e) {
        console.error('awbEntryEdit error:', e);
        // Fallback: reload catalogs and try again
        awbCatalogsLoaded = false;
        setTimeout(function() {
            awbOpenModal(id).catch(function(e2) {
                alert('No se pudo abrir la guia: ' + e2.message);
            });
        }, 100);
    }
}

// Make globally accessible
window.awbEntryEdit = awbEntryEdit;

// Make globally accessible
window.awbEntryEdit = awbEntryEdit;

function awbCloseModal() {
    // Guardar filtros del Booking ANTES de cerrar el modal
    // (los elementos del DOM todavía existen debajo del overlay)
    if (currentView === 'booking') {
        if (document.getElementById('bkFilterStation'))
            bkSavedFilters.station = document.getElementById('bkFilterStation').value;
        if (document.getElementById('bkFilterStatus'))
            bkSavedFilters.status  = document.getElementById('bkFilterStatus').value;
        if (document.getElementById('bkFilterFlt'))
            bkSavedFilters.flt     = document.getElementById('bkFilterFlt').value;
        if (document.getElementById('bkFilterQ'))
            bkSavedFilters.q       = document.getElementById('bkFilterQ').value;
        if (bkDatePicker && bkDatePicker.selectedDates.length)
            bkSavedFilters.dates   = bkDatePicker.selectedDates.map(d =>
                d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0')
            );
    }
    const el = document.getElementById('awbModal');
    if (el) el.remove();
    awbEditingId = null;
    awbCharges = [];
}

function awbModalHTML() {
    return `
        <div class="awb-modal-header">
            <div class="awb-modal-title">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                <span>AWB Entry</span>
                <span class="awb-modal-badge" id="awbm_badge">&mdash;</span>
            </div>
            <button class="awb-modal-close" onclick="awbCloseModal()">&#x2715;</button>
        </div>

        <div class="awb-modal-body">

            <!-- Fila 1: AWB# + FLT + DATE (ORIG/DEST del vuelo ocultos: ya se ven en FLT*, pero se siguen guardando internamente) -->
            <div style="display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap;padding:12px 14px;background:#F8FAFC;border:1px solid var(--border);border-radius:6px;margin-bottom:12px;">
                <div class="awb-field">
                    <label>AWB # *</label>
                    <div id="awbm_awb_group" style="display:flex;align-items:stretch;height:46px;" onfocusout="awbOnAwbGroupBlur(event)">
                        <input id="awbm_prefix" type="text" maxlength="3" value="006" oninput="this.value=this.value.replace(/[^0-9]/g,'');awbUpdateBadge()" style="width:60px;text-align:center;font-weight:800;font-size:20px;border:1px solid var(--delta-navy);border-radius:4px 0 0 4px;border-right:none;color:var(--delta-navy);">
                        <span style="background:#EEF1F5;border-top:1px solid var(--delta-navy);border-bottom:1px solid var(--delta-navy);display:flex;align-items:center;padding:0 5px;color:var(--text-light);font-size:18px;">-</span>
                        <input id="awbm_serial" type="text" maxlength="8" placeholder="00000000" oninput="awbUpdateBadge()" style="width:150px;font-family:'Courier New',monospace;font-weight:800;font-size:20px;letter-spacing:.08em;text-align:center;border:1px solid var(--delta-navy);border-radius:0 4px 4px 0;border-left:none;color:var(--delta-navy);">
                    </div>
                </div>
                <div class="awb-field"><label>STA</label><input id="awbm_station" class="awb-input" type="text" value="LIM" style="width:60px;text-align:center;font-weight:700;" readonly></div>
                <div class="awb-field">
                    <label>FLT *</label>
                    <select id="awbm_flt_select" class="awb-input" onchange="awbOnFlightChange()" style="width:210px;">
                        <option value="">— Select —</option>
                    </select>
                </div>
                <div class="awb-field"><label>DATE *</label><input id="awbm_flt_date" class="awb-input" type="date" style="width:150px;" onchange="awbOnFlightDateChange()"></div>
                <!-- ORIG/DEST del vuelo: ya no se muestran (redundantes con FLT*), pero se conservan como
                     inputs ocultos porque awbOnFlightChange/awbSave/awbValidateAll siguen leyendo su valor. -->
                <input type="hidden" id="awbm_flt_orig">
                <input type="hidden" id="awbm_flt_dest">
                <input type="hidden" id="awbm_priority" value="0">
                <input type="hidden" id="awbm_flight_id">
            </div>

            <!-- Basic Details -->
            <div class="awb-section" style="max-width:60%;">
                <div class="awb-section-hdr">
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                    Basic Details
                </div>
                <div class="awb-section-body">
                    <!-- Fila 1: Agent / Origin / Destination / Shipper / Consignee.
                         Los info-line (IATA/CASS, Code) van con position:absolute para
                         que NO sumen altura a la caja del campo — así los 5 selects
                         quedan siempre alineados, se muestre o no el info-line. El
                         margin-bottom se agranda para que ese info-line no se
                         superponga con la fila de Commodity Group de abajo. -->
                    <div style="display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end;margin-bottom:34px;">
                        <div class="awb-field" style="flex:1;min-width:210px;position:relative;">
                            <div style="display:flex;align-items:center;justify-content:space-between;height:20px;margin-bottom:4px;">
                                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Agent *</label>
                                <button type="button" class="action-btn-sm" onclick="catQuickCreate('freight_forwarder')" title="Crear nuevo Agent" style="width:20px;height:20px;padding:0;border-radius:50%;">+</button>
                            </div>
                            <select id="awbm_agent" class="awb-input" style="width:100%;" onchange="awbOnAgentChange();awbOnDropdownChange(this)"><option value="">— Select —</option></select>
                            <div id="awbm_agent_info" style="position:absolute;top:100%;left:0;margin-top:8px;font-size:11px;color:var(--text-mid);white-space:nowrap;display:none;">
                                IATA: <b id="awbm_agent_iata"></b> &nbsp;|&nbsp; CASS: <b id="awbm_agent_cass"></b>
                            </div>
                        </div>
                        <div class="awb-field" style="min-width:100px;">
                            <div style="height:20px;margin-bottom:4px;display:flex;align-items:center;"><label style="margin:0;">Origin *</label></div>
                            <select id="awbm_routing_orig" class="awb-input" style="width:100%;" onchange="awbOnRoutingChange()"><option value="">—</option></select>
                        </div>
                        <div class="awb-field" style="min-width:100px;">
                            <div style="height:20px;margin-bottom:4px;display:flex;align-items:center;"><label style="margin:0;">Destination *</label></div>
                            <select id="awbm_routing_dest" class="awb-input" style="width:100%;" onchange="awbOnRoutingChange()"><option value="">—</option></select>
                        </div>
                        <div class="awb-field" style="flex:1;min-width:210px;position:relative;">
                            <div style="display:flex;align-items:center;justify-content:space-between;height:20px;margin-bottom:4px;">
                                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Shipper</label>
                                <button type="button" class="action-btn-sm" onclick="catQuickCreate('shipper')" title="Crear nuevo Shipper" style="width:20px;height:20px;padding:0;border-radius:50%;">+</button>
                            </div>
                            <select id="awbm_shipper" class="awb-input" style="width:100%;" onchange="awbOnShipperChange();awbOnDropdownChange(this)"><option value="">— Select —</option></select>
                            <div id="awbm_shipper_info" style="position:absolute;top:100%;left:0;margin-top:8px;font-size:11px;color:var(--text-mid);white-space:nowrap;display:none;">
                                Code: <b id="awbm_shipper_code"></b>
                            </div>
                        </div>
                        <div class="awb-field" style="flex:1;min-width:210px;position:relative;">
                            <div style="display:flex;align-items:center;justify-content:space-between;height:20px;margin-bottom:4px;">
                                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Consignee</label>
                                <button type="button" class="action-btn-sm" onclick="catQuickCreate('consignee')" title="Crear nuevo Consignee" style="width:20px;height:20px;padding:0;border-radius:50%;">+</button>
                            </div>
                            <select id="awbm_consignee" class="awb-input" style="width:100%;" onchange="awbOnConsigneeChange();awbOnDropdownChange(this)"><option value="">— Select —</option></select>
                            <div id="awbm_consignee_info" style="position:absolute;top:100%;left:0;margin-top:8px;font-size:11px;color:var(--text-mid);white-space:nowrap;display:none;">
                                Code: <b id="awbm_consignee_code"></b>
                            </div>
                        </div>
                    </div>

                    <!-- Fila 2: Commodity Group → Qty ULD, debajo de la fila de Agent -->
                    <div style="display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end;">
                        <div class="awb-field" style="flex:1;min-width:230px;">
                            <label>Commodity Group *</label>
                            <select id="awbm_description" class="awb-input" style="width:100%;" onchange="awbOnDropdownChange(this)">
                                <option value="">— Select —</option>
                            </select>
                        </div>
                        <div class="awb-field" style="min-width:130px;">
                            <label>Product *</label>
                            <select id="awbm_product_code" class="awb-input" style="width:100%;" onchange="awbOnDropdownChange(this)">
                                <option value="">—</option>
                            </select>
                        </div>
                        <div class="awb-field"><label>Pieces *</label><input id="awbm_pieces" class="awb-input" type="number" min="0" style="width:75px;text-align:center;" oninput="awbValidateAll()"></div>
                        <div class="awb-field"><label>Weight (K) *</label><input id="awbm_gross_wgt" class="awb-input" type="number" min="0" step="0.1" oninput="awbOnWeightInput()" style="width:100px;"></div>
                        <div class="awb-field">
                            <label>CHG WGT (K)</label>
                            <input id="awbm_chg_wgt" class="awb-input" type="number" min="0" step="0.1" oninput="awbOnChgWgtInput()" title="Sugerido = Weight (K); editable manualmente." style="width:105px;font-weight:700;color:var(--delta-navy);">
                        </div>
                        <div class="awb-field"><label>VOL (M&sup3;)</label><input id="awbm_volume" class="awb-input" type="number" min="0" step="0.001" oninput="awbOnVolumeInput()" style="width:95px;"></div>
                        <div class="awb-field"><label>ULD</label>
                            <select id="awbm_uld_type" class="awb-input" style="width:85px;" onchange="awbCalcQtyUldFor('awbm_volume','awbm_uld_type','awbm_uld_qty')">
                                <option value="">—</option>
                                ${AWB_ULD_TYPES.map(u => `<option value="${u}">${u}</option>`).join('')}
                            </select>
                        </div>
                        <div class="awb-field"><label>Qty ULD</label><input id="awbm_uld_qty" class="awb-input" type="number" min="0" style="width:75px;text-align:center;"></div>
                    </div>
                </div>
            </div>

            <!-- Flight Details: itinerario de la guía (1 fila = 1 tramo). La fila 1 se
                 completa sola desde FLT*; las siguientes se agregan/quitan solas según
                 si el Destination de cada tramo ya coincide con el Destination de
                 Basic Details (ver awbReconcileLegs). -->
            <div class="awb-section" style="max-width:60%;">
                <div class="awb-section-hdr" style="justify-content:space-between;">
                    <span style="display:flex;align-items:center;gap:6px;">
                        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-1 .1-1.3.5l-.7.7c-.4.4-.3 1.1.2 1.4L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.4 5.8c.3.5 1 .6 1.4.2l.7-.7c.4-.3.6-.8.5-1.3z"/></svg>
                        Flight Details
                    </span>
                    <button type="button" class="charge-add-btn" style="width:auto;padding:0 12px;margin-top:0;" onclick="awbAddLegRow()">+ Add Flight</button>
                </div>
                <div class="awb-section-body" style="overflow-x:auto;">
                    <div id="awbm_legs_table"></div>
                    <div id="awbm_legs_hint" style="margin-top:6px;font-size:11px;color:var(--text-mid);"></div>
                </div>
            </div>

            <!-- Additional Details: todo lo comercial/tarifario que ya existía,
                 agrupado aparte de Basic Details / Flight Details. -->
            <div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-mid);border-top:1px solid var(--border);padding-top:14px;margin-bottom:10px;">
                Additional Details
            </div>

            <!-- Rate (incluye WT/VAL y Other, antes en Charges Declaration) —
                 ancho ajustado al contenido (no ocupa todo el modal). -->
            <div style="margin-bottom:12px;">
                <div class="awb-section" style="width:fit-content;">
                    <div class="awb-section-hdr">Rate</div>
                    <div class="awb-section-body" style="display:flex;gap:14px;align-items:flex-end;flex-wrap:nowrap;">
                        <div class="awb-field">
                            <label>Calculation</label>
                            <select id="awbm_rate_class" class="awb-input" style="width:110px;" onchange="awbOnRateClassChange()">
                                <option value="RATE" selected>Rate</option>
                                <option value="MINIMUM">Minimum</option>
                            </select>
                        </div>
                        <div class="awb-field"><label id="awbm_net_rate_label">Rate Net (USD) *</label><input id="awbm_net_rate" class="awb-input" type="number" min="0" step="0.01" oninput="awbCalc();awbValidateAll()" style="width:105px;"></div>
                        <div class="awb-field"><label id="awbm_rate_agent_label">Rate AWB (USD)</label><input id="awbm_rate_agent" class="awb-input" type="number" min="0" step="0.01" oninput="awbCalc();awbValidateAll()" style="width:105px;"></div>
                        <!-- Separación visual clara respecto del grupo Rate -->
                        <div class="awb-field" style="margin-left:32px;"><label>WT/VAL</label><select id="awbm_wt_val" class="awb-input" style="width:80px;" onchange="awbCalc()"><option value="PPD" selected>PPD</option><option value="COLL">COLL</option></select></div>
                        <div class="awb-field"><label>Other</label><select id="awbm_other_ind" class="awb-input" style="width:80px;" onchange="awbCalc()"><option value="PPD" selected>PPD</option><option value="COLL">COLL</option></select></div>
                    </div>
                </div>
            </div>

            <!-- Other Charges | Charges Summary | Control -->
            <div style="display:grid;grid-template-columns:1.1fr 1fr 0.8fr;gap:12px;margin-bottom:14px;">
                <div class="awb-section">
                    <div class="awb-section-hdr" style="justify-content:space-between;">
                        <span>Other Charges</span>
                        <span id="awbm_xc_total" style="font-size:15px;font-weight:800;color:var(--delta-navy);">0.00</span>
                    </div>
                    <div class="awb-section-body">
                        <div id="awbm_charges_list"></div>
                        <button type="button" class="charge-add-btn" onclick="awbAddChargeRow(); awbRenderCharges();">+ Agregar Cargo</button>
                    </div>
                </div>

                <div class="awb-section">
                    <div class="awb-section-hdr">Charges Summary</div>
                    <div class="awb-section-body">
                        <div style="display:grid;grid-template-columns:1fr 75px 75px;gap:5px;align-items:center;">
                            <div></div>
                            <div style="font-size:11px;font-weight:700;text-align:center;color:var(--text-light);border-bottom:1px solid #ddd;padding-bottom:4px;">Prepaid</div>
                            <div style="font-size:11px;font-weight:700;text-align:center;color:var(--text-light);border-bottom:1px solid #ddd;padding-bottom:4px;">Collect</div>
                            <div style="font-size:14px;">Weight Charge</div>
                            <input id="awbm_sum_w_ppd" class="awb-input" readonly style="height:30px;background:#F5F5F5;text-align:right;font-size:14px;">
                            <input id="awbm_sum_w_coll" class="awb-input" readonly style="height:30px;background:#F5F5F5;text-align:right;font-size:14px;">
                            <div style="font-size:14px;">Due Agent</div>
                            <input id="awbm_sum_da_ppd" class="awb-input" readonly style="height:30px;background:#F5F5F5;text-align:right;font-size:14px;">
                            <input id="awbm_sum_da_coll" class="awb-input" readonly style="height:30px;background:#F5F5F5;text-align:right;font-size:14px;">
                            <div style="font-size:14px;">Due Carrier</div>
                            <input id="awbm_sum_dc_ppd" class="awb-input" readonly style="height:30px;background:#F5F5F5;text-align:right;font-size:14px;">
                            <input id="awbm_sum_dc_coll" class="awb-input" readonly style="height:30px;background:#F5F5F5;text-align:right;font-size:14px;">
                            <div style="font-size:14px;font-weight:700;color:var(--delta-navy);padding-top:6px;border-top:2px solid var(--delta-navy);">Total</div>
                            <input id="awbm_sum_tot_ppd" class="awb-input" readonly style="height:32px;background:#DCE8F5;text-align:right;font-weight:700;font-size:14px;color:var(--delta-navy);padding-top:6px;border-top:2px solid var(--delta-navy);">
                            <input id="awbm_sum_tot_coll" class="awb-input" readonly style="height:32px;background:#DCE8F5;text-align:right;font-weight:700;font-size:14px;color:var(--delta-navy);padding-top:6px;border-top:2px solid var(--delta-navy);">
                        </div>
                    </div>
                </div>

                <div class="awb-section">
                    <div class="awb-section-hdr">Control</div>
                    <div class="awb-section-body" style="display:flex;flex-direction:column;gap:10px;">
                        <div class="awb-field"><label>Cut Date</label><input id="awbm_cut_date" class="awb-input" type="date" onchange="awbValidateAll()"></div>
                        <div class="awb-field"><label>Notas</label><input id="awbm_notes" class="awb-input" type="text" placeholder="Observaciones..."></div>
                    </div>
                </div>
            </div>

        </div>

        <div class="awb-modal-footer">
            <div id="awbm_msg" style="font-size:13px;font-weight:600;flex:1;"></div>
            <button class="btn-secondary" onclick="awbCloseModal()">Cancelar</button>
            <button class="btn-primary" id="awbm_save_btn" onclick="awbSave()">Guardar AWB</button>
        </div>
    `;
}


// ── Poblar selects de catálogo ────────────────────────────────
// ── Estación "home" del usuario autenticado, usada como default de
// Origin y como valor prohibido de Destination (Correcciones Booking #3 y #4).
// Si el usuario tiene una sola estación asignada, se usa esa. Si tiene
// varias (o 'ALL', como Admin), se prioriza el filtro de estación activo
// en la lista de Booking, y si no hay, la primera estación asignada. ──
function awbGetUserHomeStation() {
    const stations = (currentUser && Array.isArray(currentUser.stations)) ? currentUser.stations : [];
    if (stations.length === 1 && stations[0] !== 'ALL') return stations[0];
    const filterSta = document.getElementById('bkFilterStation')?.value;
    if (filterSta) return filterSta;
    const firstAssigned = stations.find(s => s !== 'ALL');
    if (firstAssigned) return firstAssigned;
    return document.getElementById('awbm_station')?.value || 'LIM';
}

// Reconstruye el <select> de Destination excluyendo el valor actual de
// Origin y la estación del usuario (nunca pueden ser el destino de la
// guía). Conserva el destino ya elegido salvo que ahora choque con esa
// exclusión, en cuyo caso lo limpia y avisa.
function awbRefreshDestinationOptions() {
    const origSel = document.getElementById('awbm_routing_orig');
    const destSel  = document.getElementById('awbm_routing_dest');
    if (!destSel) return;

    const origin = (origSel?.value || '').trim().toUpperCase();
    const homeStation = (awbGetUserHomeStation() || '').trim().toUpperCase();
    const excluded = new Set([origin, homeStation].filter(Boolean));
    const currentDest = destSel.value;

    destSel.innerHTML = '<option value="">—</option>';
    (awbCatalogsCache.airport || []).forEach(a => {
        if (excluded.has((a.iata_code || '').toUpperCase())) return;
        const o = document.createElement('option');
        o.value = a.iata_code; o.textContent = a.iata_code;
        destSel.appendChild(o);
    });

    if (currentDest && !excluded.has(currentDest.toUpperCase())) {
        destSel.value = currentDest;
    } else if (currentDest && excluded.has(currentDest.toUpperCase())) {
        destSel.value = '';
        const msg = document.getElementById('awbm_msg');
        if (msg) { msg.style.color = 'var(--delta-red)'; msg.textContent = 'Destination no puede ser igual a Origin ni a la estación del usuario. Selecciona otro destino.'; }
    }
}

function awbPopulateSelects() {
    // Vuelo (de la tabla flights de la estación) — no existe en el modal
    // Booking (ahí el itinerario se arma directo en Flight Details).
    const fltSel = document.getElementById('awbm_flt_select');
    if (fltSel) {
        awbCatalogsCache.flights.forEach(f => {
            const o = document.createElement('option');
            o.value = f.id;
            o.textContent = f.flt_number + '  ' + f.flt_orig + '→' + f.flt_dest;
            o.dataset.fltNumber = f.flt_number;
            o.dataset.orig = f.flt_orig;
            o.dataset.dest = f.flt_dest;
            fltSel.appendChild(o);
        });
    }

    const agentSel = document.getElementById('awbm_agent');
    awbCatalogsCache.agent.forEach(a => {
        const o = document.createElement('option');
        o.value = a.ff_id; o.textContent = a.legal_name;
        o.dataset.iata = a.iata_agent_code || '—'; o.dataset.cass = a.cass_code || '—';
        agentSel.appendChild(o);
    });

    const shipperSel = document.getElementById('awbm_shipper');
    awbCatalogsCache.shipper.forEach(s => {
        const o = document.createElement('option');
        o.value = s.shipper_id; o.textContent = s.legal_name;
        o.dataset.code = s.shipper_code || '—';
        shipperSel.appendChild(o);
    });

    const consigneeSel = document.getElementById('awbm_consignee');
    awbCatalogsCache.consignee.forEach(c => {
        const o = document.createElement('option');
        o.value = c.consignee_id; o.textContent = c.legal_name;
        o.dataset.code = c.consignee_code || '—';
        consigneeSel.appendChild(o);
    });

    const origSel = document.getElementById('awbm_routing_orig');
    if (origSel) {
        awbCatalogsCache.airport.forEach(a => {
            const o = document.createElement('option');
            o.value = a.iata_code; o.textContent = a.iata_code;
            origSel.appendChild(o);
        });
    }
    // Destination se puebla aparte, excluyendo Origin / estación del usuario (Correcciones Booking #4)
    awbRefreshDestinationOptions();

    // Nature Goods -> goods_catalog.goods_code
    const goodsSel = document.getElementById('awbm_description');
    awbCatalogsCache.goods.forEach(g => {
        const o = document.createElement('option');
        o.value = g.goods_code || g.goods_description;
        o.textContent = (g.goods_code ? g.goods_code + ' — ' : '') + g.goods_description;
        o.dataset.description = g.goods_description;
        goodsSel.appendChild(o);
    });

    // Producto -> product_code.product_code
    const prodSel = document.getElementById('awbm_product_code');
    awbCatalogsCache.products.forEach(p => {
        const o = document.createElement('option');
        o.value = p.product_code; o.textContent = p.product_code;
        prodSel.appendChild(o);
    });
}

// ── Vuelo: al elegir, rellena Orig/Dest y precarga Routing AWB ──
function awbOnFlightChange() {
    const sel = document.getElementById('awbm_flt_select');
    const opt = sel.options[sel.selectedIndex];
    if (!opt || !opt.value) {
        document.getElementById('awbm_flight_id').value = '';
        document.getElementById('awbm_flt_orig').value = '';
        document.getElementById('awbm_flt_dest').value = '';
        return;
    }
    document.getElementById('awbm_flight_id').value = opt.value;
    document.getElementById('awbm_flt_orig').value = opt.dataset.orig;
    document.getElementById('awbm_flt_dest').value = opt.dataset.dest;

    // Basic Details Origin/Destination por defecto = Orig/Dest del vuelo elegido
    const routingOrig = document.getElementById('awbm_routing_orig');
    const routingDest = document.getElementById('awbm_routing_dest');
    routingOrig.value = opt.dataset.orig;
    routingDest.value = opt.dataset.dest;

    // Flight Details, fila 1: se completa sola desde el vuelo elegido en FLT *
    const fltDate = document.getElementById('awbm_flt_date').value;
    awbLegs = [{
        flt_number: opt.dataset.fltNumber || opt.text.split(' ')[0] || '',
        flt_orig:   opt.dataset.orig || '',
        flt_dest:   opt.dataset.dest || '',
        dep_date:   fltDate || '',
        dep_time:   '',
        arr_date:   fltDate || '',
        arr_time:   '',
    }];
    awbReconcileLegs(); // Reglas 1 y 2: agrega la 2da fila si el destino del vuelo != destino final
    awbFetchLeg1Times(); // trae dep/arr time desde Flight Profile (flight_operations) si ya existen

    // Auto-asignar prioridad para guías NUEVAS (awbEditingId === null)
    if (!awbEditingId) {
        const fltNum = opt.dataset.fltNumber || opt.text.split(' ')[0];
        if (fltDate && fltNum) {
            apiFetch('/shipments/priority_api.php?action=next&flt_number='
                + encodeURIComponent(fltNum) + '&flt_date=' + fltDate)
            .then(function(res) {
                if (res.success && res.next_priority) {
                    document.getElementById('awbm_priority').value = res.next_priority;
                }
            }).catch(function(){});
        }
    }
}

// ── Cambia la fecha del vuelo: propaga a la fila 1 de Flight Details ──
function awbOnFlightDateChange() {
    const date = document.getElementById('awbm_flt_date')?.value || '';
    if (awbLegs[0]) {
        awbLegs[0].dep_date = date;
        if (!awbLegs[0].arr_date || awbLegs[0].arr_date === awbLegs[0].dep_date) awbLegs[0].arr_date = date;
    }
    awbRenderLegs();
    awbFetchLeg1Times();
}

// ── Trae dep_time/arr_time ya registrados en Flight Profile (flight_operations) ──
async function awbFetchLeg1Times() {
    const leg = awbLegs[0];
    const date = document.getElementById('awbm_flt_date')?.value || '';
    const station = document.getElementById('awbm_station')?.value || 'LIM';
    if (!leg || !leg.flt_number || !date) return;
    try {
        const res = await apiFetch('/flights/flight_operations_api.php?action=get_ops'
            + '&flt_number=' + encodeURIComponent(leg.flt_number)
            + '&flt_date='   + encodeURIComponent(date)
            + '&station='    + encodeURIComponent(station));
        if (res.success && res.data && awbLegs[0]) {
            if (res.data.dep_time && !awbLegs[0].dep_time) awbLegs[0].dep_time = String(res.data.dep_time).substring(0,5);
            if (res.data.arr_time && !awbLegs[0].arr_time) awbLegs[0].arr_time = String(res.data.arr_time).substring(0,5);
            awbRenderLegs();
        }
    } catch(e) { /* no bloquear si Flight Profile no tiene datos aún */ }
}

// ── Basic Details Origin/Destination cambiaron manualmente: reevaluar tramos ──
function awbOnRoutingChange() {
    // Origin/estación del usuario no pueden aparecer como Destination
    // (Correcciones Booking #4) — recalcula las opciones disponibles.
    awbRefreshDestinationOptions();

    // En el modal Booking (fila 1 no viene de FLT*) el Origin de Basic
    // Details alimenta directo el origen del primer tramo.
    if (!awbLegsFirstRowLocked && awbLegs[0]) {
        awbLegs[0].flt_orig = document.getElementById('awbm_routing_orig')?.value || awbLegs[0].flt_orig;
    }
    awbReconcileLegs();
    awbValidateAll();
}

// ══════════════════════════════════════════════════════════════
//  FLIGHT DETAILS — tabla dinámica de tramos (Reglas 1-4)
// ══════════════════════════════════════════════════════════════

// Regla 1: si el destino de algún tramo ya coincide con el destino
// final (Basic Details > Destination), todo lo posterior sobra.
// Regla 2/3: si el último tramo tiene destino propio distinto al
// destino final, se agrega un tramo nuevo para continuar la ruta.
function awbReconcileLegs() {
    const basicDest = (document.getElementById('awbm_routing_dest')?.value || '').toUpperCase();

    if (basicDest) {
        const matchIdx = awbLegs.findIndex(function(l){ return l.flt_dest && l.flt_dest.toUpperCase() === basicDest; });
        if (matchIdx !== -1 && matchIdx < awbLegs.length - 1) {
            awbLegs = awbLegs.slice(0, matchIdx + 1); // Regla 4: quita los tramos que ya no hacen falta
        }
    }

    const last = awbLegs[awbLegs.length - 1];
    if (last && last.flt_dest && (!basicDest || last.flt_dest.toUpperCase() !== basicDest)) {
        awbLegs.push({
            // Destino final de la guía (Basic Details) precargado como valor
            // por defecto del tramo — el usuario lo puede corregir si la
            // ruta operacional real difiere.
            flt_number: '', flt_orig: last.flt_dest, flt_dest: basicDest || '',
            dep_date: '', dep_time: '', arr_date: '', arr_time: ''
        });
    }

    awbRenderLegs();
}

// Edición de una celda de la tabla de tramos
function awbOnLegFieldChange(idx, field, value, selectEl) {
    if (!awbLegs[idx]) return;
    const isFlightField = field === 'flt_number' || field === 'flt_orig' || field === 'flt_dest';
    awbLegs[idx][field] = isFlightField ? value.toUpperCase().trim() : value;

    if (field === 'flt_number' && selectEl) {
        // Al elegir el vuelo del tramo se autocompletan Origin (solo fila 1;
        // en las demás el Origin sigue viniendo del Destination del tramo
        // anterior) y Destination con los datos del vuelo — el usuario los
        // puede corregir después, ambos siguen siendo selects editables.
        const opt = selectEl.selectedOptions[0];
        if (opt && opt.value) {
            if (idx === 0) awbLegs[idx].flt_orig = opt.dataset.orig || awbLegs[idx].flt_orig;
            awbLegs[idx].flt_dest = opt.dataset.dest || awbLegs[idx].flt_dest;
        }
    }

    if (field === 'flt_dest' || field === 'flt_number') {
        if (awbLegs[idx + 1]) awbLegs[idx + 1].flt_orig = awbLegs[idx].flt_dest; // mantiene continuidad del tramo siguiente
        awbReconcileLegs(); // Reglas 3 y 4
    } else if (field === 'dep_date' || field === 'arr_date') {
        // NO reconstruir la tabla (awbRenderLegs) para campos de fecha: cada
        // fila de fecha ya tiene su propio id fijo y awbValidateAll() los
        // referencia directamente por id, así que no hace falta recrear el
        // <input>. Reconstruirlo aquí destruía el elemento mientras el
        // usuario todavía estaba escribiendo el año (evento 'change' del
        // navegador dispara con cada segmento), haciéndole perder el foco
        // a mitad de la escritura y truncando el año (ej. quedaba "0002"
        // en vez de "2026").
    } else {
        awbRenderLegs();
    }
    awbValidateAll();
}

// Botón "+ Add Flight": agrega un tramo manualmente al final.
// Destination se precarga con el destino final de la guía (Basic Details) —
// el usuario lo puede modificar si el tramo real llega a otro lado.
function awbAddLegRow() {
    const last = awbLegs[awbLegs.length - 1];
    const basicDest = (document.getElementById('awbm_routing_dest')?.value || '').toUpperCase();
    awbLegs.push({
        flt_number: '', flt_orig: last ? (last.flt_dest || '') : '', flt_dest: basicDest || '',
        dep_date: '', dep_time: '', arr_date: '', arr_time: ''
    });
    awbRenderLegs();
    awbValidateAll();
}

// Quita un tramo (y todo lo posterior, para no dejar huecos en la continuidad).
// El primer tramo (fila 1) ya no está atado a un selector superior — si se
// necesita rehacerlo, se edita directo en la fila en vez de removerla.
function awbRemoveLegRow(idx) {
    if (idx <= 0) return;
    awbLegs.splice(idx);
    awbRenderLegs();
    awbValidateAll();
}

function awbRenderLegs() {
    const wrap = document.getElementById('awbm_legs_table');
    if (!wrap) return;

    if (!awbLegs.length) {
        wrap.innerHTML = '<div style="font-size:14px;color:var(--text-light);">Agrega el primer tramo del itinerario (Flight, Origin, Destination y Dep Date).</div>';
        const hintEmpty = document.getElementById('awbm_legs_hint');
        if (hintEmpty) hintEmpty.textContent = '';
        return;
    }

    const th = function(label){ return '<th style="text-align:left;font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-light);padding:4px 6px;border-bottom:1px solid var(--border);">' + label + '</th>'; };
    let html = '<table style="width:100%;border-collapse:collapse;font-size:14px;"><thead><tr>'
        + th('#') + th('Flight') + th('Origin') + th('Dep Date') + th('Destination') + th('Arr Date') + th('')
        + '</tr></thead><tbody>';

    // Vuelos de la estación (Flight *) y catálogo de aeropuertos (Origin/Destination),
    // ya cargados en awbCatalogsCache por awbLoadCatalogs() al abrir el modal.
    const stationFlights = awbCatalogsCache.flights || [];
    const airports       = awbCatalogsCache.airport  || [];

    awbLegs.forEach(function(leg, i) {
        const first = i === 0;

        // Flight: solo la fila 1 es un select vinculado a la tabla de vuelos
        // de la estación (obligatoria). Desde la fila 2 el vuelo de conexión
        // puede no conocerse aún al momento de la reserva, así que vuelve a
        // ser un campo de texto libre, editable y sin catálogo detrás.
        const fltSelectHTML = function() {
            const cur = (leg.flt_number || '').toUpperCase();
            let opts = '<option value="">—</option>';
            let matched = false;
            stationFlights.forEach(function(f) {
                const sel = f.flt_number.toUpperCase() === cur;
                if (sel) matched = true;
                opts += '<option value="' + esc(f.flt_number) + '" data-orig="' + esc(f.flt_orig) + '" data-dest="' + esc(f.flt_dest) + '"' + (sel ? ' selected' : '') + '>'
                     + esc(f.flt_number) + ' ' + esc(f.flt_orig) + '\u2192' + esc(f.flt_dest) + '</option>';
            });
            // Si el vuelo guardado ya no está en el catálogo de la estación actual, se
            // agrega igual como opción para no perder el dato existente.
            if (cur && !matched) opts += '<option value="' + esc(cur) + '" selected>' + esc(cur) + ' (no listado)</option>';
            return '<select id="awbm_leg0_flt" style="width:100%;border:1px solid var(--border);border-radius:4px;padding:4px 6px;font-size:14px;text-transform:uppercase;"'
                + ' onchange="awbOnLegFieldChange(' + i + ',\'flt_number\',this.value,this)">' + opts + '</select>';
        };
        const fltTextHTML = function() {
            const val = esc(leg.flt_number || '');
            return '<input type="text" value="' + val + '" placeholder="Ej: DL456"'
                + ' style="width:100%;border:1px solid var(--border);border-radius:4px;padding:4px 6px;font-size:14px;text-transform:uppercase;"'
                + ' onchange="awbOnLegFieldChange(' + i + ',\'flt_number\',this.value)">';
        };

        // Origin/Destination: select vinculado al catálogo de aeropuertos.
        // El Origin de la fila 1 es editable; el de las filas siguientes
        // sigue derivándose del Destination del tramo anterior (solo lectura).
        const airportSelectHTML = function(field, elId) {
            const cur = (leg[field] || '').toUpperCase();
            let opts = '<option value="">—</option>';
            let matched = false;
            airports.forEach(function(a) {
                const sel = (a.iata_code || '').toUpperCase() === cur;
                if (sel) matched = true;
                opts += '<option value="' + esc(a.iata_code) + '"' + (sel ? ' selected' : '') + '>' + esc(a.iata_code) + '</option>';
            });
            if (cur && !matched) opts += '<option value="' + esc(cur) + '" selected>' + esc(cur) + '</option>';
            return '<select' + (elId ? ' id="' + elId + '"' : '') + ' style="width:100%;border:1px solid var(--border);border-radius:4px;padding:4px 6px;font-size:14px;text-align:center;"'
                + ' onchange="awbOnLegFieldChange(' + i + ',\'' + field + '\',this.value)">' + opts + '</select>';
        };

        const originCellHTML = function() {
            if (first) return airportSelectHTML('flt_orig', 'awbm_leg0_orig');
            const val = esc(leg.flt_orig || '');
            return '<input type="text" value="' + val + '" readonly'
                + ' style="width:100%;border:1px solid var(--border);border-radius:4px;padding:4px 6px;font-size:14px;background:#EEF1F5;text-transform:uppercase;text-align:center;"'
                + ' onchange="awbOnLegFieldChange(' + i + ',\'flt_orig\',this.value)">';
        };

        const inp = function(field, type, elId) {
            const val = esc(leg[field] || '');
            return '<input type="' + type + '" value="' + val + '"' + (elId ? ' id="' + elId + '"' : '')
                + ' style="width:100%;border:1px solid var(--border);border-radius:4px;padding:4px 6px;font-size:14px;"'
                + ' onchange="awbOnLegFieldChange(' + i + ',\'' + field + '\',this.value)">';
        };

        html += '<tr>'
            + '<td style="padding:4px 6px;color:var(--text-light);font-weight:700;">' + (i + 1) + '</td>'
            + '<td style="padding:4px 6px;min-width:150px;">' + (first ? fltSelectHTML() : fltTextHTML()) + '</td>'
            + '<td style="padding:4px 6px;min-width:90px;">'  + originCellHTML() + '</td>'
            + '<td style="padding:4px 6px;min-width:135px;">' + inp('dep_date',   'date', first ? 'awbm_leg0_date' : ('awbm_leg' + i + '_dep')) + '</td>'
            + '<td style="padding:4px 6px;min-width:90px;">'  + airportSelectHTML('flt_dest', first ? 'awbm_leg0_dest' : null) + '</td>'
            + '<td style="padding:4px 6px;min-width:135px;">' + inp('arr_date',   'date', 'awbm_leg' + i + '_arr') + '</td>'
            + '<td style="padding:4px 6px;text-align:center;">'
            + (i > 0 ? '<button type="button" class="charge-remove-btn" style="width:24px;height:24px;" onclick="awbRemoveLegRow(' + i + ')" title="Quitar tramo">&times;</button>' : '')
            + '</td></tr>';
    });
    html += '</tbody></table>';
    wrap.innerHTML = html;

    const hint = document.getElementById('awbm_legs_hint');
    if (hint) {
        const basicDest = (document.getElementById('awbm_routing_dest')?.value || '').toUpperCase();
        const lastLeg = awbLegs[awbLegs.length - 1];
        if (basicDest && lastLeg && lastLeg.flt_dest && lastLeg.flt_dest.toUpperCase() === basicDest) {
            hint.innerHTML = '<span style="color:#1E7E34;font-weight:700;">&#10003; Itinerario completo</span> — el último tramo llega al destino final (' + basicDest + ').';
        } else if (basicDest) {
            hint.innerHTML = 'Completa el <b>Destination</b> de cada tramo hasta llegar a <b>' + basicDest + '</b> (destino final de la guía).';
        } else {
            hint.textContent = 'Define el Destination en Basic Details para armar el itinerario.';
        }
    }
}

// ── Validate all AWB fields, return array of error strings ────────
// Helper: check if a value is a valid non-placeholder selection
function awbIsEmpty(val) {
    if (!val) return true;
    const trimmed = val.trim();
    return trimmed === ''
        || trimmed === '-'
        || trimmed === '--'
        || trimmed === '—'
        || trimmed === '——'
        || /^[\-\u2014\s]*select[\-\u2014\s]*$/i.test(trimmed)
        || /^[\-\u2014\s]+$/.test(trimmed);
}
window.awbIsEmpty = awbIsEmpty;

// Clear highlight when a dropdown gets a valid selection
function awbOnDropdownChange(el) {
    if (!awbIsEmpty(el.value)) {
        el.classList.remove('awb-required-empty');
    }
    awbValidateAll();
}

function awbValidateAll() {
    const errors = [];

    // ── Helpers ───────────────────────────────────────────────────
    function chkNum(id, label) {
        const el = document.getElementById(id);
        if (!el) return;
        const empty = !el.value || parseFloat(el.value) <= 0;
        el.classList.toggle('awb-required-empty', empty);
        if (empty) errors.push(label + ' es obligatorio y debe ser mayor a 0.');
    }
    function chkDrop(id, label) {
        const el = document.getElementById(id);
        if (!el) return;
        const empty = awbIsEmpty(el.value);
        el.classList.toggle('awb-required-empty', empty);
        if (empty) errors.push(label + ' es obligatorio — selecciona una opcion valida.');
    }
    function chkText(id, label) {
        const el = document.getElementById(id);
        if (!el) return;
        const empty = !el.value || !el.value.trim();
        el.classList.toggle('awb-required-empty', empty);
        if (empty) errors.push(label + ' es obligatorio.');
    }

    // ── BASIC DETAILS ─────────────────────────────────────────────
    chkDrop('awbm_agent',     'Agent');
    chkDrop('awbm_routing_orig', 'Origin');
    chkDrop('awbm_routing_dest', 'Destination');

    // Destination nunca puede ser igual a Origin ni a la estación del
    // usuario (Correcciones Booking #4). El <select> ya excluye esas
    // opciones (awbRefreshDestinationOptions), pero se revalida aquí como
    // último resguardo por si el valor llegó por otro medio (fill de una
    // guía existente, edición directa del DOM, etc.).
    const origEl = document.getElementById('awbm_routing_orig');
    const destEl = document.getElementById('awbm_routing_dest');
    const origVal = (origEl?.value || '').trim().toUpperCase();
    const destVal = (destEl?.value || '').trim().toUpperCase();
    const homeStationVal = (awbGetUserHomeStation() || '').trim().toUpperCase();
    if (destVal && (destVal === origVal || destVal === homeStationVal)) {
        destEl?.classList.add('awb-required-empty');
        errors.push('Destination no puede ser igual a Origin ni a la estación del usuario.');
    }

    // Shipper/Consignee solo son obligatorios en el modal completo de
    // Operaciones — en el modal Booking son opcionales (quedan tras el
    // toggle "+ Add Shipper/Consignee").
    if (!awbSimpleMode) {
        chkDrop('awbm_shipper',   'Shipper');
        chkDrop('awbm_consignee', 'Consignee');
    }
    chkText('awbm_description',  'Commodity Group');
    chkDrop('awbm_product_code', 'Product');
    chkNum('awbm_pieces',    'Pieces');
    chkNum('awbm_gross_wgt', 'Weight');
    // CHG WGT (K) ya no se valida aquí: se sugiere automáticamente a partir
    // de Weight y el usuario puede editarla a mano (ver awbCalcChgWeight).

    // ── RATES (Additional Details) ─────────────────────────────────
    const rateClassForValidation = document.getElementById('awbm_rate_class')?.value || 'RATE';
    const rateClassLbl = rateClassForValidation === 'MINIMUM' ? 'Min' : 'Rate';
    chkNum('awbm_net_rate',  rateClassLbl + ' NET');
    chkNum('awbm_rate_agent', rateClassLbl + ' AWB');

    // ── RATE AWB >= RATE NET (o MIN AWB >= MIN NET) ────────────────
    const rateNet     = parseFloat(document.getElementById('awbm_net_rate')?.value)   || 0;
    const rateAgent   = parseFloat(document.getElementById('awbm_rate_agent')?.value) || 0;
    const rateNetEl   = document.getElementById('awbm_net_rate');
    const rateAgentEl = document.getElementById('awbm_rate_agent');
    if (rateNet > 0 && rateAgent > 0 && rateNet > rateAgent) {
        rateNetEl?.classList.add('awb-rate-warn');
        rateAgentEl?.classList.add('awb-rate-warn');
        errors.push('La tarifa ' + rateClassLbl.toUpperCase() + ' NET (' + rateNet + ') no puede ser mayor que la tarifa ' + rateClassLbl.toUpperCase() + ' AWB (' + rateAgent + ').');
    } else {
        rateNetEl?.classList.remove('awb-rate-warn');
        rateAgentEl?.classList.remove('awb-rate-warn');
    }

    // ── FLIGHT DETAILS: solo la fila 1 es obligatoria (Flight, Origin,
    // Destination y Dep Date). A partir de la fila 2 el vuelo de conexión
    // puede no conocerse todavía al momento de la reserva — esos tramos son
    // informativos y nunca bloquean el guardado. En vez de un mensaje aparte,
    // se resalta directo la casilla de la fila 1 que falte completar.
    const leg0 = awbLegs[0];
    const leg0FltEl  = document.getElementById('awbm_leg0_flt');
    const leg0OrigEl = document.getElementById('awbm_leg0_orig');
    const leg0DestEl = document.getElementById('awbm_leg0_dest');
    const leg0DateEl = document.getElementById('awbm_leg0_date');
    const leg0FltEmpty  = !leg0 || !leg0.flt_number;
    const leg0OrigEmpty = !leg0 || !leg0.flt_orig;
    const leg0DestEmpty = !leg0 || !leg0.flt_dest;
    const leg0DateEmpty = !leg0 || !leg0.dep_date;
    leg0FltEl?.classList.toggle('awb-required-empty', leg0FltEmpty);
    leg0OrigEl?.classList.toggle('awb-required-empty', leg0OrigEmpty);
    leg0DestEl?.classList.toggle('awb-required-empty', leg0DestEmpty);
    leg0DateEl?.classList.toggle('awb-required-empty', leg0DateEmpty);
    if (leg0FltEmpty || leg0OrigEmpty || leg0DestEmpty || leg0DateEmpty) {
        errors.push('Completa el primer tramo en Flight Details (Flight, Origin, Destination y Dep Date son obligatorios).');
    }

    // ── FLIGHT DETAILS: itinerario como línea de tiempo continua ─────
    // DEP1 <= ARR1 <= DEP2 <= ARR2 <= DEP3 <= ARR3 ... Dos reglas por
    // tramo: (a) ARR DATE no puede ser anterior a su propia DEP DATE, y
    // (b) a partir del tramo 2, DEP DATE no puede ser anterior al ARR
    // DATE del tramo inmediatamente anterior. Se valida apenas se
    // completa cada fecha y solo se resalta la casilla que rompe la
    // secuencia — nunca se modifican fechas automáticamente. Comparación
    // por string funciona porque <input type="date"> siempre entrega
    // formato ISO YYYY-MM-DD.
    const awbLegDepElId = function(idx) { return idx === 0 ? 'awbm_leg0_date' : 'awbm_leg' + idx + '_dep'; };
    for (let i = 0; i < awbLegs.length; i++) {
        const dep   = awbLegs[i].dep_date || '';
        const arr   = awbLegs[i].arr_date || '';
        const depEl = document.getElementById(awbLegDepElId(i));
        const arrEl = document.getElementById('awbm_leg' + i + '_arr');

        // (a) ARR DATE >= DEP DATE del mismo tramo
        if (dep && arr && arr < dep) {
            arrEl?.classList.add('awb-input-err');
            if (arrEl) arrEl.title = 'ARR DATE no puede ser anterior a la fecha DEP DATE del mismo vuelo.';
            errors.push('ARR DATE del tramo ' + (i + 1) + ' (' + arr + ') no puede ser anterior a su propia DEP DATE (' + dep + ').');
        } else {
            arrEl?.classList.remove('awb-input-err');
            if (arrEl) arrEl.removeAttribute('title');
        }

        // (b) DEP DATE >= DEP DATE Y >= ARR DATE del tramo anterior (a partir
        // del tramo 2). Deben comprobarse explícitamente ambas fechas del
        // tramo precedente — no basta con comparar solo contra ARR DATE,
        // porque si el tramo anterior tiene su propia inconsistencia
        // (ARR DATE < DEP DATE) eso no debe "tapar" un DEP DATE posterior
        // que igual queda antes del DEP DATE del tramo anterior.
        if (i > 0) {
            const prevDep = awbLegs[i - 1].dep_date || '';
            const prevArr = awbLegs[i - 1].arr_date || '';
            const failsPrevDep = dep && prevDep && dep < prevDep;
            const failsPrevArr = dep && prevArr && dep < prevArr;
            if (failsPrevDep || failsPrevArr) {
                depEl?.classList.add('awb-input-err');
                if (depEl) depEl.title = 'La fecha DEP DATE no puede ser anterior a la fecha DEP DATE ni a la fecha ARR DATE del vuelo anterior.';
                errors.push('DEP DATE del tramo ' + (i + 1) + ' (' + dep + ') no puede ser anterior a DEP DATE (' + prevDep + ') ni a ARR DATE (' + prevArr + ') del tramo ' + i + '.');
            } else {
                depEl?.classList.remove('awb-input-err');
                if (depEl) depEl.removeAttribute('title');
            }
        }
    }

    // ── CUT DATE <= FLT DATE ──────────────────────────────────────
    const fltDate = document.getElementById('awbm_flt_date')?.value || '';
    const cutDate = document.getElementById('awbm_cut_date')?.value  || '';
    const cutEl   = document.getElementById('awbm_cut_date');
    if (cutDate && fltDate && cutDate > fltDate) {
        cutEl?.classList.add('awb-input-err');
        errors.push('CUT DATE (' + cutDate + ') no puede ser posterior a la fecha del vuelo (' + fltDate + ').');
    } else {
        cutEl?.classList.remove('awb-input-err');
    }

    // El panel de advertencias al pie del modal ya no se usa: ahora basta con
    // el resaltado (clase awb-required-empty) en cada casilla pendiente. Si
    // había quedado uno de una versión anterior en el DOM, se oculta.
    const oldWarnEl = document.getElementById('awbm_validation_warn');
    if (oldWarnEl) oldWarnEl.style.display = 'none';

    return errors;
}

function awbOnAgentChange() {
    const sel = document.getElementById('awbm_agent');
    const opt = sel.options[sel.selectedIndex];
    const info = document.getElementById('awbm_agent_info');
    if (opt && opt.value) {
        document.getElementById('awbm_agent_iata').textContent = opt.dataset.iata || '—';
        document.getElementById('awbm_agent_cass').textContent = opt.dataset.cass || '—';
        info.style.display = 'block';
    } else {
        info.style.display = 'none';
    }
}
function awbOnShipperChange() {
    const sel = document.getElementById('awbm_shipper');
    const opt = sel.options[sel.selectedIndex];
    const info = document.getElementById('awbm_shipper_info');
    if (opt && opt.value) {
        document.getElementById('awbm_shipper_code').textContent = opt.dataset.code || '—';
        info.style.display = 'block';
    } else {
        info.style.display = 'none';
    }
}
function awbOnConsigneeChange() {
    const sel = document.getElementById('awbm_consignee');
    const opt = sel.options[sel.selectedIndex];
    const info = document.getElementById('awbm_consignee_info');
    if (opt && opt.value) {
        document.getElementById('awbm_consignee_code').textContent = opt.dataset.code || '—';
        info.style.display = 'block';
    } else {
        info.style.display = 'none';
    }
}

// ── Quick-create: abre el formulario de Catalog Management para
//    crear FF/Shipper/Consignee sin salir del flujo de AWB Entry ──
function catQuickCreate(table) {
    const popup = window.open('', 'quickCreate', 'width=520,height=640');
    popup.document.write('<p style="font-family:sans-serif;padding:20px;">Use el módulo <b>Catalog Management</b> para crear nuevos registros. Esta ventana se puede cerrar; vuelva al formulario de AWB y actualice la lista tras crear el registro.</p>');
    // Nota: integración completa (crear inline sin perder el AWB en progreso)
    // se puede construir como mejora futura reutilizando catOpenForm()
    // dentro de un modal secundario en lugar de una ventana nueva.
}

function awbUpdateBadge() {
    const prefix = document.getElementById('awbm_prefix')?.value || '';
    const serial = document.getElementById('awbm_serial')?.value || '';
    const badge = document.getElementById('awbm_badge');
    if (badge) badge.textContent = (prefix && serial) ? prefix + '-' + serial : '—';
}

// ══════════════════════════════════════════════════════════════
//  AWB# — validación al perder el foco (Correcciones Booking #1 y #2)
//  1) No permite registrar un AWB ya existente en BD: si la guía
//     encontrada es editable, ofrece cargarla en el modal; si no,
//     bloquea con mensaje.
//  2) El serial debe tener como mínimo 8 dígitos numéricos.
// ══════════════════════════════════════════════════════════════

// ¿Cuál de los dos modales (completo / Booking simplificado) está abierto?
function awbCurrentModalIsBooking() {
    return !!document.getElementById('awbBookingModal');
}
function awbReopenForEdit(id) {
    return awbCurrentModalIsBooking() ? awbBookingOpenModal(id) : awbOpenModal(id);
}

// Regla compartida con la lista de Booking: una guía LOADED o que ya
// pertenece a un manifiesto DEFINITIVE no se puede editar.
function awbIsShipmentEditable(s) {
    if (!s) return false;
    if (s.operational_status === 'LOADED') return false;
    if (s.manifest_status === 'DEFINITIVE') return false;
    return true;
}

// Se dispara al salir del grupo AWB# (prefix + serial). Si el foco se
// mueve entre esos dos campos (prefix <-> serial) todavía no se valida.
function awbOnAwbGroupBlur(e) {
    const group = document.getElementById('awbm_awb_group');
    if (group && e.relatedTarget && group.contains(e.relatedTarget)) return;
    awbValidateAwbNumber();
}

// Valida formato (mín. 8 dígitos) y duplicidad del AWB# actual. Devuelve
// true si se puede continuar/guardar, false si el AWB debe corregirse.
// Se usa tanto al perder el foco como (de nuevo, por seguridad) al hacer
// click en Guardar.
// ── Ventana de 365 días para la búsqueda de AWB duplicado (Correcciones
// Booking — Modal NEW AWB #1): por normativa operativa el número de AWB
// se puede reutilizar pasado un año, así que guías con flt_date más
// antiguo que eso no cuentan como duplicado. Sin fecha registrada no se
// descarta (por precaución, ante datos incompletos). ──
function awbWithinLastYear(dateStr) {
    if (!dateStr) return true;
    const d = new Date(dateStr + 'T00:00:00');
    if (isNaN(d.getTime())) return true;
    const cutoff = new Date();
    cutoff.setDate(cutoff.getDate() - 365);
    return d >= cutoff;
}

async function awbValidateAwbNumber() {
    const msg = document.getElementById('awbm_msg');
    const prefix = (document.getElementById('awbm_prefix')?.value || '').trim();
    const serial = (document.getElementById('awbm_serial')?.value || '').trim();
    if (!prefix && !serial) return true; // aún no empezó a escribir el AWB#

    // 2) Longitud mínima: 8 dígitos numéricos
    const digits = serial.replace(/[^0-9]/g, '');
    if (digits.length < 8) {
        if (msg) { msg.style.color = 'var(--delta-red)'; msg.textContent = 'Formato de AWB inválido: el número debe tener al menos 8 dígitos.'; }
        setTimeout(function() { document.getElementById('awbm_serial')?.focus(); }, 0);
        return false;
    }

    const full = prefix + '-' + serial;
    const station = document.getElementById('awbm_station')?.value || 'LIM';
    const token = ++awbDuplicateCheckToken;
    if (msg) { msg.style.color = 'var(--text-mid)'; msg.textContent = 'Verificando AWB...'; }

    try {
        const res = await apiFetch('/shipments/shipments_api.php?q=' + encodeURIComponent(full) + '&station=' + encodeURIComponent(station));
        if (token !== awbDuplicateCheckToken) return true; // ya no es la verificación más reciente

        const rows = (res && res.success && Array.isArray(res.data)) ? res.data : [];
        // Solo cuentan como posible duplicado las guías de los últimos 365 días.
        const recentRows = rows.filter(function(s) { return awbWithinLastYear(s.flt_date); });
        const match = recentRows.find(function(s) {
            const awbVal = s.awb || ((s.awb_prefix || '') + '-' + (s.awb_serial || ''));
            return awbVal === full;
        });

        // 1) Duplicado — ignorar si el match encontrado es la misma guía que se está editando
        if (match && (!awbEditingId || String(match.id) !== String(awbEditingId))) {
            if (awbIsShipmentEditable(match)) {
                if (msg) { msg.style.color = 'var(--delta-red)'; msg.textContent = 'La guía ' + full + ' ya se encuentra registrada.'; }
                const wantsEdit = confirm('The guide already exists. Do you want to edit it?');
                if (wantsEdit) {
                    await awbReopenForEdit(match.id);
                } else if (msg) {
                    msg.textContent = 'Ingresa un número de AWB distinto para continuar.';
                    setTimeout(function() { document.getElementById('awbm_serial')?.focus(); }, 0);
                }
            } else if (msg) {
                msg.style.color = 'var(--delta-red)';
                msg.textContent = 'La guía ' + full + ' ya existe y no puede ser modificada.';
                setTimeout(function() { document.getElementById('awbm_serial')?.focus(); }, 0);
            }
            return false;
        }

        if (msg && msg.textContent === 'Verificando AWB...') msg.textContent = '';
        return true;
    } catch(e) {
        console.error('Error validando AWB duplicado:', e);
        if (token !== awbDuplicateCheckToken) return true;
        if (msg && msg.textContent === 'Verificando AWB...') msg.textContent = ''; // no bloquear por error de red
        return true;
    }
}

// ── Resaltado "campo con valor": pinta en verde claro cualquier
// input/select del modal AWB que ya tenga un dato cargado (referencia
// visual rápida de avance de captura). No se aplica sobre campos de
// solo lectura (Charges Summary, STA) que ya tienen su propio color de
// fondo intencional. ──
function awbApplyFilledState(el) {
    if (!el || !el.matches || !el.matches('input, select, textarea')) return;
    if (el.type === 'checkbox' || el.type === 'radio' || el.type === 'button' || el.readOnly || el.disabled) return;
    const hasValue = el.value !== null && String(el.value).trim() !== '';
    el.classList.toggle('awb-has-value', hasValue);
}
function awbHighlightFilled(root) {
    (root || document).querySelectorAll('input, select, textarea').forEach(awbApplyFilledState);
}
// Delegación en document: cubre también los campos que se agregan/renderizan
// dinámicamente después de abrir el modal (Flight Details, Other Charges),
// sin necesidad de cablear un listener por cada input nuevo.
document.addEventListener('input', function(e) {
    if (e.target.closest && e.target.closest('#awbModal')) awbApplyFilledState(e.target);
});
document.addEventListener('change', function(e) {
    if (e.target.closest && e.target.closest('#awbModal')) awbApplyFilledState(e.target);
});

// ── Other Charges: filas dinámicas (a diferencia de v1, sin límite) ──
function awbAddChargeRow() {
    awbCharges.push({ charge_code: '', amount: 0, charge_type: 'DC' });
}
function awbRemoveChargeRow(idx) {
    awbCharges.splice(idx, 1);
    awbRenderCharges();
    awbCalc();
}
function awbToggleChargeType(idx) {
    awbCharges[idx].charge_type = awbCharges[idx].charge_type === 'DC' ? 'DA' : 'DC';
    awbRenderCharges();
    awbCalc();
}
function awbUpdateChargeField(idx, field, value) {
    awbCharges[idx][field] = value;
    awbCalc();
}

function awbRenderCharges() {
    const cont = document.getElementById('awbm_charges_list');
    if (!cont) return;
    let html = '';
    awbCharges.forEach((c, idx) => {
        html += '<div class="charges-row">';
        html += `<select onchange="awbUpdateChargeField(${idx},'charge_code',this.value)">`;
        html += '<option value="">—</option>';
        awbCatalogsCache.charges.forEach(ch => {
            html += `<option value="${ch.charge_code}" ${c.charge_code===ch.charge_code?'selected':''}>${ch.charge_code} — ${esc(ch.charge_name)}</option>`;
        });
        html += '</select>';
        html += `<input type="number" step="0.01" value="${c.amount||''}" placeholder="0.00" onblur="awbUpdateChargeField(${idx},'amount',parseFloat(this.value)||0)" style="text-align:right;">`;
        html += `<button type="button" class="toggle-dc" data-type="${c.charge_type}" onclick="awbToggleChargeType(${idx})">${c.charge_type}</button>`;
        html += `<button type="button" class="charge-remove-btn" onclick="awbRemoveChargeRow(${idx})">&times;</button>`;
        html += '</div>';
    });
    cont.innerHTML = html;
}

// ── Cálculo (Weight Charge, Charges Summary) ─────────────────
// ── CHG WGT (K): su valor inicial/sugerido es igual a Weight (K).
// Ya no se calcula como el mayor entre Weight y Volume — el usuario puede
// editarlo manualmente en cualquier momento (Correcciones Booking — Modal
// NEW AWB #2/#3); una vez editado a mano, deja de sobreescribirse. ──
function awbCalcChgWeightFor(weightId, chgId) {
    if (awbChgWgtManuallyEdited) {
        return parseFloat(document.getElementById(chgId)?.value) || 0;
    }
    const weight = parseFloat(document.getElementById(weightId)?.value) || 0;
    const el = document.getElementById(chgId);
    if (el) el.value = weight > 0 ? weight.toFixed(1) : '';
    return weight;
}

function awbCalcChgWeight() {
    const chg = awbCalcChgWeightFor('awbm_gross_wgt', 'awbm_chg_wgt');
    awbCalc();
    return chg;
}

// ── Qty ULD automático: techo(volumen / capacidad_m3 del ULD elegido) ──
function awbCalcQtyUldFor(volumeId, uldTypeId, qtyId) {
    const volume = parseFloat(document.getElementById(volumeId)?.value) || 0;
    const uldType = document.getElementById(uldTypeId)?.value || '';
    const capacity = AWB_ULD_CAPACITY[uldType] || 0;
    const qtyEl = document.getElementById(qtyId);
    if (!qtyEl) return;
    if (volume > 0 && capacity > 0) {
        qtyEl.value = Math.ceil(volume / capacity);
    }
    // Si no hay capacidad definida para ese ULD (catálogo sin dato), se
    // deja el campo tal como está para que el usuario lo complete a mano.
}

// ── Weight: al escribirlo, sugiere CHG WGT (K) (si no fue editado a mano)
// y recalcula Qty ULD. Volume ya NO se autocompleta desde Weight — queda
// vacío hasta que el usuario lo ingrese explícitamente (son magnitudes
// distintas: kg vs m³). ──
function awbOnWeightInput() {
    awbCalcChgWeight();
    awbCalcQtyUldFor('awbm_volume', 'awbm_uld_type', 'awbm_uld_qty');
    awbValidateAll();
}

// ── Volume editado a mano: deja de copiarse desde Weight ──
function awbOnVolumeInput() {
    awbVolumeManuallyEdited = true;
    awbCalcQtyUldFor('awbm_volume', 'awbm_uld_type', 'awbm_uld_qty');
    awbValidateAll();
}

// ── CHG WGT (K) editado a mano: deja de sugerirse automáticamente desde
// Weight (Correcciones Booking — Modal NEW AWB #2) ──
function awbOnChgWgtInput() {
    awbChgWgtManuallyEdited = true;
    awbCalc();
    awbValidateAll();
}

// ── Rate / Minimum toggle: cambia los labels de Net/AWB y recalcula
// el Weight Charge según el modo elegido (ver awbCalc). ──
const AWB_RATE_LABELS = {
    RATE:    { net: 'Rate Net (USD) *', awb: 'Rate AWB (USD)' },
    MINIMUM: { net: 'Min Net (USD) *',  awb: 'Min AWB (USD)' },
};
function awbOnRateClassChange() {
    const cls = document.getElementById('awbm_rate_class')?.value || 'RATE';
    const labels = AWB_RATE_LABELS[cls] || AWB_RATE_LABELS.RATE;
    const netLbl = document.getElementById('awbm_net_rate_label');
    const awbLbl = document.getElementById('awbm_rate_agent_label');
    if (netLbl) netLbl.textContent = labels.net;
    if (awbLbl) awbLbl.textContent = labels.awb;
    awbCalc();
    awbValidateAll();
}

function awbCalc() {
    const chg       = parseFloat(document.getElementById('awbm_chg_wgt')?.value) || 0;
    const rateAgent = parseFloat(document.getElementById('awbm_rate_agent')?.value) || 0;
    const netRate   = parseFloat(document.getElementById('awbm_net_rate')?.value) || 0;
    const rateClass = document.getElementById('awbm_rate_class')?.value || 'RATE';

    // RATE: Weight Charge = RATE AWB × CHG WEIGHT (venta aerolínea = RATE NET × CHG WEIGHT).
    // MINIMUM: Weight Charge = MIN AWB directo, sin multiplicar (venta aerolínea = MIN NET directo).
    const freightAgent = rateClass === 'MINIMUM' ? rateAgent : chg * rateAgent;
    const freightNet    = rateClass === 'MINIMUM' ? netRate   : chg * netRate;

    let totalDA = 0, totalDC = 0;
    awbCharges.forEach(c => {
        const amt = parseFloat(c.amount) || 0;
        if (c.charge_type === 'DA') totalDA += amt; else totalDC += amt;
    });
    const xcTotal = totalDA + totalDC;

    const fmt = v => v > 0 ? v.toLocaleString('en',{minimumFractionDigits:2,maximumFractionDigits:2}) : '';
    const setV = (id, v) => { const e = document.getElementById(id); if (e) e.value = fmt(v); };

    const xcTotalEl = document.getElementById('awbm_xc_total');
    if (xcTotalEl) xcTotalEl.textContent = xcTotal.toLocaleString('en',{minimumFractionDigits:2,maximumFractionDigits:2});

    const wtVal   = document.getElementById('awbm_wt_val')?.value || 'PPD';
    const otherInd = document.getElementById('awbm_other_ind')?.value || 'COLL';
    const isPPD    = wtVal === 'PPD';
    const otherPPD = otherInd === 'PPD';

    setV('awbm_sum_w_ppd',  isPPD ? freightAgent : 0);
    setV('awbm_sum_w_coll', !isPPD ? freightAgent : 0);
    setV('awbm_sum_da_ppd',  otherPPD ? totalDA : 0);
    setV('awbm_sum_da_coll', !otherPPD ? totalDA : 0);
    setV('awbm_sum_dc_ppd',  otherPPD ? totalDC : 0);
    setV('awbm_sum_dc_coll', !otherPPD ? totalDC : 0);

    const totalPPD  = (isPPD ? freightAgent : 0) + (otherPPD ? xcTotal : 0);
    const totalColl = (!isPPD ? freightAgent : 0) + (!otherPPD ? xcTotal : 0);
    setV('awbm_sum_tot_ppd', totalPPD);
    setV('awbm_sum_tot_coll', totalColl);

    return { freightAgent, freightNet, totalDA, totalDC };
}

// ── Cargar datos de un shipment existente ─────────────────────
async function awbFillFromShipment(id, preloadedData) {
    try {
        let r;
        if (preloadedData) {
            r = preloadedData;
            // charges may need separate fetch if not included
            if (!r.charges) {
                const res2 = await apiFetch('/shipments/shipments_api.php?id=' + id);
                if (res2.success) r = res2.data;
            }
        } else {
            const res = await apiFetch('/shipments/shipments_api.php?id=' + id);
            if (!res.success) { document.getElementById('awbm_msg').textContent = 'Error al cargar la guía.'; return; }
            r = res.data;
        }

        awbVolumeManuallyEdited = true; // la guía ya tiene un volumen guardado: no auto-copiar desde Weight
        awbChgWgtManuallyEdited = true; // la guía ya tiene un CHG WGT guardado: no sobreescribirlo desde Weight

        const fill = (id, v) => { const e = document.getElementById(id); if (e) e.value = v ?? ''; };
        fill('awbm_prefix', r.awb_prefix || '006');
        fill('awbm_serial', r.awb_serial || '');
        fill('awbm_station', r.station);
        fill('awbm_flt_date', r.flt_date);
        fill('awbm_flt_orig', r.flt_orig);
        fill('awbm_flt_dest', r.flt_dest);
        fill('awbm_priority', r.priority || 5);
        fill('awbm_agent', r.agent_id);
        fill('awbm_shipper', r.shipper_id);
        fill('awbm_consignee', r.consignee_id);
        fill('awbm_routing_orig', r.routing_orig);
        fill('awbm_routing_dest', r.routing_dest);
        fill('awbm_wt_val', r.wt_val || 'PPD');
        fill('awbm_other_ind', r.other_chg_ind || 'PPD');
        fill('awbm_pieces', parseInt(r.pieces) || 0);
        fill('awbm_gross_wgt', r.gross_weight != null ? (parseFloat(r.gross_weight) || 0) : 0);
        fill('awbm_chg_wgt', r.chg_weight != null ? (parseFloat(r.chg_weight) || 0) : 0);
        fill('awbm_volume', (r.volume_m3 !== null && r.volume_m3 !== undefined && r.volume_m3 !== '') ? parseFloat(r.volume_m3) : '');
        // Normalización defensiva: si la BD trae un resto del antiguo formato
        // char(1) (p.ej. 'R'/'M', truncado antes de terminar la migración a
        // ENUM) o cualquier valor no reconocido, se mapea a RATE/MINIMUM en
        // vez de dejar el <select> sin ninguna opción coincidente (blanco).
        const rcRaw = String(r.rate_class || '').trim().toUpperCase();
        const rcNormalized = (rcRaw === 'MINIMUM' || rcRaw === 'M') ? 'MINIMUM' : 'RATE';
        fill('awbm_rate_class', rcNormalized);
        fill('awbm_net_rate', r.net_rate || 0);
        fill('awbm_rate_agent', r.rate_agent || '');
        awbOnRateClassChange(); // aplica el label Rate/Minimum correcto para el registro cargado
        fill('awbm_uld_type', r.uld_type || '');
        fill('awbm_uld_qty', r.uld_qty || '');
        fill('awbm_description', r.description || '');
        fill('awbm_product_code', r.product_code || '');
        fill('awbm_cut_date', r.cut_date || '');
        fill('awbm_notes', r.handling_info || '');

        // Seleccionar el vuelo correspondiente en el select (por flt_number).
        // El modal Booking no tiene FLT* (awbm_flt_select no existe ahí) —
        // esta parte solo aplica al modal completo.
        const fltSel = document.getElementById('awbm_flt_select');
        if (fltSel) {
            let matchOpt = Array.from(fltSel.options).find(o => o.dataset.fltNumber === r.flt_number);

            // Si no hay opciones para este vuelo, recargar flights de la estación correcta
            if (!matchOpt && r.station) {
                try {
                    const fRes = await apiFetch('/flights/flights_api.php?station=' + encodeURIComponent(r.station));
                    const flights = fRes.success ? fRes.data : [];
                    awbCatalogsCache.flights = flights;
                    // Re-populate the flight select
                    awbPopulateSelects();
                    matchOpt = Array.from(fltSel.options).find(o => o.dataset.fltNumber === r.flt_number);
                } catch(e) {}
            }

            if (matchOpt) {
                fltSel.value = matchOpt.value;
                document.getElementById('awbm_flight_id').value = matchOpt.value;
            }
        }

        // Flight Details: cargar el itinerario guardado (tabla shipment_flight_legs).
        // Si la guía es anterior a esta funcionalidad (no tiene tramos guardados),
        // se reconstruye una fila 1 desde los campos planos de la guía y, si tenía
        // el viejo campo de texto "routing_connection", se agrega como 2do tramo.
        try {
            const legsRes = await apiFetch('/shipments/shipment_legs_api.php?shipment_id=' + id);
            if (legsRes.success && legsRes.data && legsRes.data.length) {
                awbLegs = legsRes.data.map(function(l) {
                    return {
                        flt_number: l.flt_number || '', flt_orig: l.flt_orig || '', flt_dest: l.flt_dest || '',
                        dep_date: l.dep_date || '', dep_time: l.dep_time ? String(l.dep_time).substring(0,5) : '',
                        arr_date: l.arr_date || '', arr_time: l.arr_time ? String(l.arr_time).substring(0,5) : '',
                    };
                });
            } else {
                awbLegs = [{
                    flt_number: r.flt_number || '', flt_orig: r.flt_orig || '', flt_dest: r.flt_dest || '',
                    dep_date: r.flt_date || '', dep_time: '', arr_date: r.flt_date || '', arr_time: '',
                }];
                if (r.routing_connection && r.routing_dest && r.flt_dest !== r.routing_dest) {
                    awbLegs.push({
                        flt_number: r.routing_connection, flt_orig: r.flt_dest || '', flt_dest: r.routing_dest || '',
                        dep_date: '', dep_time: '', arr_date: '', arr_time: '',
                    });
                }
            }
        } catch(e) {
            awbLegs = [{
                flt_number: r.flt_number || '', flt_orig: r.flt_orig || '', flt_dest: r.flt_dest || '',
                dep_date: r.flt_date || '', dep_time: '', arr_date: r.flt_date || '', arr_time: '',
            }];
        }
        awbRenderLegs();
        awbValidateAll();

        awbOnAgentChange();
        awbOnShipperChange();
        awbOnConsigneeChange();

        awbCharges = (r.charges || []).map(c => ({
            charge_code: c.charge_code || '', amount: parseFloat(c.amount) || 0, charge_type: c.charge_type || 'DC'
        }));
        if (!awbCharges.length) awbAddChargeRow();
        awbRenderCharges();

        awbUpdateBadge();
        awbCalc();
    } catch(e) {
        document.getElementById('awbm_msg').textContent = 'Error de conexión.';
    }
}

// ── Guardar ────────────────────────────────────────────────────
async function awbSave() {
    const msg = document.getElementById('awbm_msg');
    const btn = document.getElementById('awbm_save_btn');
    msg.style.color = 'var(--delta-red)'; msg.textContent = '';

    // AWB# — duplicado / formato (Correcciones Booking #1 y #2). Se revalida
    // aquí además del onBlur por si el usuario llegó a Guardar sin que
    // llegara a dispararse (p.ej. pegando el valor y haciendo click directo).
    const awbNumOk = await awbValidateAwbNumber();
    if (!awbNumOk) return;

    // Run all validations
    const validationErrors = awbValidateAll();

    // Block save if critical validation errors
    const criticalErrors = validationErrors.filter(function(e) {
        return e.includes('tarifa NET') || e.includes('itinerario') || e.includes('tramo') || e.includes('posterior a la fecha') || e.includes('Destination no puede ser igual');
    });
    if (criticalErrors.length) {
        msg.textContent = criticalErrors[0];
        return;
    }

    const prefix = document.getElementById('awbm_prefix').value.trim();
    const serial = document.getElementById('awbm_serial').value.trim();
    const fltSel    = document.getElementById('awbm_flt_select');
    const fltOpt    = fltSel.selectedOptions[0];
    const fltNumber = fltOpt ? fltOpt.dataset.fltNumber : '';
    const fltDate   = document.getElementById('awbm_flt_date').value;
    const fltOrig   = document.getElementById('awbm_flt_orig').value.trim().toUpperCase();
    const fltDest   = document.getElementById('awbm_flt_dest').value.trim().toUpperCase();
    const agentId   = document.getElementById('awbm_agent').value;

    if (!fltSel.value || !fltDate || !fltOrig || !fltDest) {
        msg.textContent = 'Vuelo y fecha son obligatorios.';
        return;
    }
    if (!agentId) {
        msg.textContent = 'El Agent es obligatorio.';
        return;
    }

    const calc = awbCalc();
    const agentOpt = document.getElementById('awbm_agent').selectedOptions[0];
    const shipperOpt = document.getElementById('awbm_shipper').selectedOptions[0];
    const consigneeOpt = document.getElementById('awbm_consignee').selectedOptions[0];

    const record = {
        awb_prefix: prefix, awb_serial: serial,
        awb: (prefix && serial) ? prefix + '-' + serial : null,
        station: document.getElementById('awbm_station').value,
        flight_id: parseInt(fltSel.value) || null,
        flt_number: fltNumber, flt_date: fltDate, flt_orig: fltOrig, flt_dest: fltDest,
        priority: parseInt(document.getElementById('awbm_priority').value) || 5,
        agent_id: agentId || null,
        agent_name: agentOpt ? agentOpt.textContent : '',
        shipper_id: document.getElementById('awbm_shipper').value || null,
        shipper_name: shipperOpt ? shipperOpt.textContent : '',
        consignee_id: document.getElementById('awbm_consignee').value || null,
        consignee_name: consigneeOpt ? consigneeOpt.textContent : '',
        routing_orig: document.getElementById('awbm_routing_orig').value || null,
        routing_dest: document.getElementById('awbm_routing_dest').value || null,
        // routing_connection ya no se captura como texto libre: se genera a partir
        // de los tramos 2+ de Flight Details, solo para mantener compatibilidad
        // con reportes/columnas que aún lean este campo directo de la BD.
        routing_connection: awbLegs.slice(1).map(function(l){ return l.flt_number; }).filter(Boolean).join(', ') || null,
        wt_val: document.getElementById('awbm_wt_val').value,
        other_chg_ind: document.getElementById('awbm_other_ind').value,
        // Currency y Val. Carriage ya no se capturan en la UI (ver rediseño
        // de Charges Declaration) — se guardan siempre con su valor por
        // defecto, sin depender de un input.
        currency: 'USD',
        val_carriage: 'NVD',
        pieces: parseInt(document.getElementById('awbm_pieces').value) || 0,
        gross_weight: parseFloat(document.getElementById('awbm_gross_wgt').value) || 0,
        chg_weight: parseFloat(document.getElementById('awbm_chg_wgt').value) || 0,
        volume_m3: parseFloat(document.getElementById('awbm_volume').value) || null,
        // Rate / Minimum: rate_class distingue cómo interpretar net_rate/rate_agent
        // (ver awbCalc). net_rate = venta aerolínea (NET), rate_agent = venta
        // agente (AWB); la ganancia del agente se deriva como AWB − NET, no se
        // almacena. freight_revenue guarda el Weight Charge ya calculado.
        rate_class: document.getElementById('awbm_rate_class')?.value || 'RATE',
        net_rate: parseFloat(document.getElementById('awbm_net_rate').value) || 0,
        rate_agent: parseFloat(document.getElementById('awbm_rate_agent').value) || null,
        uld_type: document.getElementById('awbm_uld_type').value || null,
        uld_qty: parseInt(document.getElementById('awbm_uld_qty').value) || null,
        description: document.getElementById('awbm_description').value.trim() || null,
        product_code: document.getElementById('awbm_product_code').value.trim() || null,
        cut_date: document.getElementById('awbm_cut_date').value || null,
        handling_info: document.getElementById('awbm_notes').value.trim() || null,
        freight_revenue: calc.freightAgent,
        charges: awbCharges.filter(c => c.charge_code || c.amount > 0),
        user_id: currentUser?.id || null,
    };

    btn.disabled = true;
    msg.style.color = 'var(--text-mid)'; msg.textContent = 'Guardando...';

    try {
        const url = awbEditingId ? '/shipments/shipments_api.php?id=' + awbEditingId : '/shipments/shipments_api.php';
        const method = awbEditingId ? 'PUT' : 'POST';
        const res = await apiFetch(url, { method, body: JSON.stringify(record) });

        if (res.success) {
            msg.style.color = '#2ECC71';
            msg.textContent = '✅ AWB guardado correctamente.';

            // Guardar el itinerario (Flight Details) — reemplaza por completo
            // los tramos existentes de esta guía con el contenido de awbLegs.
            const savedShipmentId = awbEditingId || res.id;
            if (savedShipmentId) {
                try {
                    await apiFetch('/shipments/shipment_legs_api.php', {
                        method: 'POST',
                        body: JSON.stringify({ shipment_id: savedShipmentId, legs: awbLegs })
                    });
                } catch(e) { console.warn('Error guardando itinerario:', e); }
            }

            // Auto-asignar prioridad para guías nuevas (el backend devuelve el id)
            const isNew = !awbEditingId;
            if (isNew) {
                const flt  = document.getElementById('awbm_flt_number')?.value || '';
                const date = document.getElementById('awbm_flt_date')?.value   || '';
                if (flt && date) {
                    try {
                        await apiFetch('/shipments/priority_api.php', {
                            method: 'POST',
                            body: JSON.stringify({
                                flt_number:   flt,
                                flt_date:     date,
                                moving_id:    res.id || null,
                                new_priority: null,  // null = agregar al final
                                user_id:      currentUser?.id || null,
                            })
                        });
                    } catch(e) { /* no bloquear si falla */ }
                }
            }
            if (currentView === 'booking' && typeof bkLoad === 'function') bkLoad();
            else if (currentView === 'awbentry' && typeof awbEntryLoad === 'function') awbEntryLoad();
            else if (currentView === 'validate' && typeof valLoad === 'function') valLoad();
            else if (currentView === 'manifest' && typeof mfLoad === 'function') mfLoad();
            setTimeout(awbCloseModal, 900);
        } else {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = res.message || 'Error al guardar.';
            btn.disabled = false;
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Error de conexión.';
        btn.disabled = false;
    }
}

// ══════════════════════════════════════════════════════════════
//  MODAL "SIGHA – BOOKING" (awbBooking*) — versión simplificada
//  del modal de AWB, para el Agente de Reservas. Registra lo
//  mínimo indispensable (Agent/Origin/Destination, carga básica,
//  itinerario). Reutiliza los mismos IDs de campo que el modal
//  completo (awbm_agent, awbm_routing_orig, awbm_pieces, etc.) y
//  por lo tanto las mismas funciones de catálogos/validación/
//  itinerario — solo cambia qué secciones se muestran.
//  Se guarda contra los mismos endpoints (shipments_api.php +
//  shipment_legs_api.php); el resto de la información (tarifas,
//  cargos, control) queda pendiente para que Operaciones la
//  complete después desde el Modal Completo (Pre-Manifest).
// ══════════════════════════════════════════════════════════════

async function awbBookingOpenModal(id = null) {
    if (currentView === 'booking') {
        if (document.getElementById('bkFilterStation'))
            bkSavedFilters.station = document.getElementById('bkFilterStation').value;
        if (document.getElementById('bkFilterStatus'))
            bkSavedFilters.status  = document.getElementById('bkFilterStatus').value;
        if (document.getElementById('bkFilterFlt'))
            bkSavedFilters.flt     = document.getElementById('bkFilterFlt').value;
        if (document.getElementById('bkFilterQ'))
            bkSavedFilters.q       = document.getElementById('bkFilterQ').value;
        if (bkDatePicker && bkDatePicker.selectedDates.length)
            bkSavedFilters.dates   = bkDatePicker.selectedDates.map(d =>
                d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0')
            );
    }

    awbEditingId = id;
    awbCharges = [];
    awbLegs = [];
    awbLegsFirstRowLocked = false;
    awbSimpleMode = true;
    awbVolumeManuallyEdited = false;
    awbChgWgtManuallyEdited = false;

    if (currentView !== 'booking') awbCatalogsLoaded = false;

    // Solo puede haber un modal de AWB abierto a la vez (completo o simple)
    document.getElementById('awbModal')?.remove();
    document.getElementById('awbBookingModal')?.remove();

    const modal = document.createElement('div');
    modal.className = 'awb-modal-overlay';
    modal.id = 'awbBookingModal';
    modal.innerHTML = awbBookingModalHTML();
    document.body.appendChild(modal);

    let preloadedShipment = null;
    if (id) {
        try {
            const preRes = await apiFetch('/shipments/shipments_api.php?id=' + id);
            if (preRes.success) {
                preloadedShipment = preRes.data;
                const stEl = document.getElementById('awbm_station');
                if (stEl && preloadedShipment.station) stEl.value = preloadedShipment.station;
            }
        } catch(e) { console.error('Pre-fetch error:', e); }
    }

    await awbLoadCatalogs();
    awbPopulateSelects();

    if (id) {
        await awbFillFromShipment(id, preloadedShipment);
    } else {
        document.getElementById('awbm_station').value = document.getElementById('bkFilterStation')?.value || 'LIM';
        // Origin por defecto = estación del usuario autenticado (Correcciones
        // Booking #3); el usuario puede cambiarlo manualmente igual.
        const origSelDefault = document.getElementById('awbm_routing_orig');
        if (origSelDefault && !origSelDefault.value) origSelDefault.value = awbGetUserHomeStation();
        awbRefreshDestinationOptions();
        awbCatalogsLoaded = false;
        awbLegs = [{ flt_number:'', flt_orig:'', flt_dest:'', dep_date:'', dep_time:'', arr_date:'', arr_time:'' }];
        awbRenderLegs();
    }
}

function awbBookingCloseModal() {
    if (currentView === 'booking') {
        if (document.getElementById('bkFilterStation'))
            bkSavedFilters.station = document.getElementById('bkFilterStation').value;
        if (document.getElementById('bkFilterStatus'))
            bkSavedFilters.status  = document.getElementById('bkFilterStatus').value;
        if (document.getElementById('bkFilterFlt'))
            bkSavedFilters.flt     = document.getElementById('bkFilterFlt').value;
        if (document.getElementById('bkFilterQ'))
            bkSavedFilters.q       = document.getElementById('bkFilterQ').value;
        if (bkDatePicker && bkDatePicker.selectedDates.length)
            bkSavedFilters.dates   = bkDatePicker.selectedDates.map(d =>
                d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0')
            );
    }
    document.getElementById('awbBookingModal')?.remove();
    awbEditingId = null;
    awbCharges = [];
    awbSimpleMode = false;
}

function awbToggleShipperConsignee() {
    const row = document.getElementById('awbm_shipcons_row');
    const link = document.getElementById('awbm_shipcons_toggle');
    if (!row) return;
    row.style.display = 'flex';
    if (link) link.style.display = 'none';
}

function awbBookingModalHTML() {
    return `
        <div class="awb-modal-header">
            <div class="awb-modal-title">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                <span>SIGHA – Booking</span>
                <span class="awb-modal-badge" id="awbm_badge">&mdash;</span>
            </div>
            <button class="awb-modal-close" onclick="awbBookingCloseModal()">&#x2715;</button>
        </div>

        <div class="awb-modal-body">

            <!-- STA + AWB # -->
            <div style="display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap;padding:12px 14px;background:#F8FAFC;border:1px solid var(--border);border-radius:6px;margin-bottom:12px;">
                <div class="awb-field"><label>STA</label><input id="awbm_station" class="awb-input" type="text" value="LIM" style="width:60px;text-align:center;font-weight:700;" readonly></div>
                <div class="awb-field">
                    <label>AWB # *</label>
                    <div id="awbm_awb_group" style="display:flex;align-items:stretch;height:46px;" onfocusout="awbOnAwbGroupBlur(event)">
                        <input id="awbm_prefix" type="text" maxlength="3" value="006" oninput="this.value=this.value.replace(/[^0-9]/g,'');awbUpdateBadge()" style="width:60px;text-align:center;font-weight:800;font-size:20px;border:1px solid var(--delta-navy);border-radius:4px 0 0 4px;border-right:none;color:var(--delta-navy);">
                        <span style="background:#EEF1F5;border-top:1px solid var(--delta-navy);border-bottom:1px solid var(--delta-navy);display:flex;align-items:center;padding:0 5px;color:var(--text-light);font-size:18px;">-</span>
                        <input id="awbm_serial" type="text" maxlength="8" placeholder="00000000" oninput="awbUpdateBadge()" style="width:150px;font-family:'Courier New',monospace;font-weight:800;font-size:20px;letter-spacing:.08em;text-align:center;border:1px solid var(--delta-navy);border-radius:0 4px 4px 0;border-left:none;color:var(--delta-navy);">
                    </div>
                </div>
                <input type="hidden" id="awbm_priority" value="0">
                <input type="hidden" id="awbm_flight_id">
            </div>

            <!-- Basic Details -->
            <div class="awb-section" style="max-width:60%;">
                <div class="awb-section-hdr">
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                    Basic Details
                </div>
                <div class="awb-section-body">
                    <!-- Info-line con position:absolute (no suma altura a la caja del
                         campo) para que Agent/Origin/Destination queden siempre
                         alineados, se muestre o no el IATA/CASS de Agent. El margin-bottom
                         de esta fila y la de Shipper/Consignee se agranda para dejar
                         espacio a ese info-line y que no se superponga con la fila de abajo. -->
                    <div style="display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end;margin-bottom:34px;">
                        <div class="awb-field" style="flex:1;min-width:210px;position:relative;">
                            <div style="display:flex;align-items:center;justify-content:space-between;height:20px;margin-bottom:4px;">
                                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Agent *</label>
                                <button type="button" class="action-btn-sm" onclick="catQuickCreate('freight_forwarder')" title="Crear nuevo Agent" style="width:20px;height:20px;padding:0;border-radius:50%;">+</button>
                            </div>
                            <select id="awbm_agent" class="awb-input" style="width:100%;" onchange="awbOnAgentChange();awbOnDropdownChange(this)"><option value="">— Select —</option></select>
                            <div id="awbm_agent_info" style="position:absolute;top:100%;left:0;margin-top:8px;font-size:11px;color:var(--text-mid);white-space:nowrap;display:none;">
                                IATA: <b id="awbm_agent_iata"></b> &nbsp;|&nbsp; CASS: <b id="awbm_agent_cass"></b>
                            </div>
                        </div>
                        <div class="awb-field" style="min-width:100px;">
                            <div style="height:20px;margin-bottom:4px;display:flex;align-items:center;"><label style="margin:0;">Origin *</label></div>
                            <select id="awbm_routing_orig" class="awb-input" style="width:100%;" onchange="awbOnRoutingChange()"><option value="">—</option></select>
                        </div>
                        <div class="awb-field" style="min-width:100px;">
                            <div style="height:20px;margin-bottom:4px;display:flex;align-items:center;"><label style="margin:0;">Destination *</label></div>
                            <select id="awbm_routing_dest" class="awb-input" style="width:100%;" onchange="awbOnRoutingChange()"><option value="">—</option></select>
                        </div>
                        <div style="align-self:flex-end;height:36px;display:flex;align-items:center;">
                            <a href="javascript:void(0)" id="awbm_shipcons_toggle" onclick="awbToggleShipperConsignee()"
                               style="font-size:12px;font-weight:700;color:var(--delta-sky);text-decoration:none;white-space:nowrap;">
                               + Add Shipper / Consignee
                            </a>
                        </div>
                    </div>

                    <div id="awbm_shipcons_row" style="display:none;flex-wrap:wrap;gap:14px;align-items:flex-end;margin-bottom:34px;">
                        <div class="awb-field" style="flex:1;min-width:210px;position:relative;">
                            <div style="display:flex;align-items:center;justify-content:space-between;height:20px;margin-bottom:4px;">
                                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Shipper</label>
                                <button type="button" class="action-btn-sm" onclick="catQuickCreate('shipper')" title="Crear nuevo Shipper" style="width:20px;height:20px;padding:0;border-radius:50%;">+</button>
                            </div>
                            <select id="awbm_shipper" class="awb-input" style="width:100%;" onchange="awbOnShipperChange();awbOnDropdownChange(this)"><option value="">— Select —</option></select>
                            <div id="awbm_shipper_info" style="position:absolute;top:100%;left:0;margin-top:8px;font-size:11px;color:var(--text-mid);white-space:nowrap;display:none;">
                                Code: <b id="awbm_shipper_code"></b>
                            </div>
                        </div>
                        <div class="awb-field" style="flex:1;min-width:210px;position:relative;">
                            <div style="display:flex;align-items:center;justify-content:space-between;height:20px;margin-bottom:4px;">
                                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Consignee</label>
                                <button type="button" class="action-btn-sm" onclick="catQuickCreate('consignee')" title="Crear nuevo Consignee" style="width:20px;height:20px;padding:0;border-radius:50%;">+</button>
                            </div>
                            <select id="awbm_consignee" class="awb-input" style="width:100%;" onchange="awbOnConsigneeChange();awbOnDropdownChange(this)"><option value="">— Select —</option></select>
                            <div id="awbm_consignee_info" style="position:absolute;top:100%;left:0;margin-top:8px;font-size:11px;color:var(--text-mid);white-space:nowrap;display:none;">
                                Code: <b id="awbm_consignee_code"></b>
                            </div>
                        </div>
                    </div>

                    <div style="display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end;">
                        <div class="awb-field" style="flex:1;min-width:230px;">
                            <label>Commodity Group *</label>
                            <select id="awbm_description" class="awb-input" style="width:100%;" onchange="awbOnDropdownChange(this)">
                                <option value="">— Select —</option>
                            </select>
                        </div>
                        <div class="awb-field" style="min-width:130px;">
                            <label>Product *</label>
                            <select id="awbm_product_code" class="awb-input" style="width:100%;" onchange="awbOnDropdownChange(this)">
                                <option value="">—</option>
                            </select>
                        </div>
                    </div>
                </div>
            </div>

            <!-- Carga -->
            <div class="awb-section">
                <div class="awb-section-body" style="display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end;">
                    <div class="awb-field"><label>Pieces *</label><input id="awbm_pieces" class="awb-input" type="number" min="0" style="width:75px;text-align:center;" oninput="awbValidateAll()"></div>
                    <div class="awb-field"><label>Weight (K) *</label><input id="awbm_gross_wgt" class="awb-input" type="number" min="0" step="0.1" oninput="awbOnWeightInput()" style="width:100px;"></div>
                    <div class="awb-field">
                        <label>CHG WGT (K)</label>
                        <input id="awbm_chg_wgt" class="awb-input" type="number" min="0" step="0.1" oninput="awbOnChgWgtInput()" title="Sugerido = Weight (K); editable manualmente." style="width:105px;font-weight:700;color:var(--delta-navy);">
                    </div>
                    <div class="awb-field"><label>VOL (M&sup3;)</label><input id="awbm_volume" class="awb-input" type="number" min="0" step="0.001" oninput="awbOnVolumeInput()" style="width:95px;"></div>
                    <div class="awb-field"><label>ULD</label>
                        <select id="awbm_uld_type" class="awb-input" style="width:85px;" onchange="awbCalcQtyUldFor('awbm_volume','awbm_uld_type','awbm_uld_qty')">
                            <option value="">—</option>
                            ${AWB_ULD_TYPES.map(u => `<option value="${u}">${u}</option>`).join('')}
                        </select>
                    </div>
                    <div class="awb-field"><label>Qty ULD</label><input id="awbm_uld_qty" class="awb-input" type="number" min="0" style="width:75px;text-align:center;"></div>
                </div>
            </div>

            <!-- Flight Details -->
            <div class="awb-section" style="max-width:60%;">
                <div class="awb-section-hdr" style="justify-content:space-between;">
                    <span style="display:flex;align-items:center;gap:6px;">
                        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-1 .1-1.3.5l-.7.7c-.4.4-.3 1.1.2 1.4L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.4 5.8c.3.5 1 .6 1.4.2l.7-.7c.4-.3.6-.8.5-1.3z"/></svg>
                        Flight Details
                    </span>
                    <button type="button" class="charge-add-btn" style="width:auto;padding:0 12px;margin-top:0;" onclick="awbAddLegRow()">+ Add Flight</button>
                </div>
                <div class="awb-section-body" style="overflow-x:auto;">
                    <div id="awbm_legs_table"></div>
                    <div id="awbm_legs_hint" style="margin-top:6px;font-size:11px;color:var(--text-mid);"></div>
                </div>
            </div>

        </div>

        <div class="awb-modal-footer">
            <div id="awbm_msg" style="font-size:13px;font-weight:600;flex:1;"></div>
            <button class="btn-secondary" onclick="awbBookingCloseModal()">Cancelar</button>
            <button class="btn-primary" id="awbm_save_btn" onclick="awbBookingSave()">Save</button>
        </div>
    `;
}

// ── Guardar (modal Booking): registro mínimo, station/flt_number/
// flt_orig/flt_dest/flt_date se derivan del tramo 1 de Flight Details ──
async function awbBookingSave() {
    const msg = document.getElementById('awbm_msg');
    const btn = document.getElementById('awbm_save_btn');
    msg.style.color = 'var(--delta-red)'; msg.textContent = '';

    // AWB# — duplicado / formato (Correcciones Booking #1 y #2).
    const awbNumOk = await awbValidateAwbNumber();
    if (!awbNumOk) return;

    const validationErrors = awbValidateAll();
    const criticalErrors = validationErrors.filter(function(e) {
        return e.includes('itinerario') || e.includes('tramo') || e.includes('Destination no puede ser igual');
    });
    if (criticalErrors.length) {
        msg.textContent = criticalErrors[0];
        return;
    }

    const leg1 = awbLegs[0];
    const agentId = document.getElementById('awbm_agent').value;
    const station = document.getElementById('awbm_station').value;

    if (!agentId) { msg.textContent = 'El Agent es obligatorio.'; return; }
    if (!document.getElementById('awbm_routing_orig').value || !document.getElementById('awbm_routing_dest').value) {
        msg.textContent = 'Origin y Destination son obligatorios.';
        return;
    }
    if (!leg1 || !leg1.flt_number || !leg1.flt_orig || !leg1.flt_dest || !leg1.dep_date) {
        msg.textContent = 'Completa el primer tramo en Flight Details (Flight, Origin, Destination y Dep Date).';
        return;
    }

    const prefix = document.getElementById('awbm_prefix').value.trim();
    const serial = document.getElementById('awbm_serial').value.trim();
    const agentOpt = document.getElementById('awbm_agent').selectedOptions[0];
    const shipperOpt = document.getElementById('awbm_shipper')?.selectedOptions[0];
    const consigneeOpt = document.getElementById('awbm_consignee')?.selectedOptions[0];

    const record = {
        awb_prefix: prefix, awb_serial: serial,
        awb: (prefix && serial) ? prefix + '-' + serial : null,
        station: station,
        flight_id: null,
        flt_number: leg1.flt_number, flt_date: leg1.dep_date,
        flt_orig: leg1.flt_orig, flt_dest: leg1.flt_dest,
        priority: parseInt(document.getElementById('awbm_priority').value) || 5,
        agent_id: agentId || null,
        agent_name: agentOpt ? agentOpt.textContent : '',
        shipper_id: document.getElementById('awbm_shipper')?.value || null,
        shipper_name: shipperOpt ? shipperOpt.textContent : '',
        consignee_id: document.getElementById('awbm_consignee')?.value || null,
        consignee_name: consigneeOpt ? consigneeOpt.textContent : '',
        routing_orig: document.getElementById('awbm_routing_orig').value || null,
        routing_dest: document.getElementById('awbm_routing_dest').value || null,
        routing_connection: awbLegs.slice(1).map(function(l){ return l.flt_number; }).filter(Boolean).join(', ') || null,
        pieces: parseInt(document.getElementById('awbm_pieces').value) || 0,
        gross_weight: parseFloat(document.getElementById('awbm_gross_wgt').value) || 0,
        chg_weight: parseFloat(document.getElementById('awbm_chg_wgt').value) || 0,
        volume_m3: parseFloat(document.getElementById('awbm_volume').value) || null,
        uld_type: document.getElementById('awbm_uld_type').value || null,
        uld_qty: parseInt(document.getElementById('awbm_uld_qty').value) || null,
        description: document.getElementById('awbm_description').value.trim() || null,
        product_code: document.getElementById('awbm_product_code').value.trim() || null,
        user_id: currentUser?.id || null,
    };

    btn.disabled = true;
    msg.style.color = 'var(--text-mid)'; msg.textContent = 'Guardando...';

    try {
        const url = awbEditingId ? '/shipments/shipments_api.php?id=' + awbEditingId : '/shipments/shipments_api.php';
        const method = awbEditingId ? 'PUT' : 'POST';
        const res = await apiFetch(url, { method, body: JSON.stringify(record) });

        if (res.success) {
            msg.style.color = '#2ECC71';
            msg.textContent = '✅ Reserva guardada correctamente.';

            const savedShipmentId = awbEditingId || res.id;
            if (savedShipmentId) {
                try {
                    await apiFetch('/shipments/shipment_legs_api.php', {
                        method: 'POST',
                        body: JSON.stringify({ shipment_id: savedShipmentId, legs: awbLegs })
                    });
                } catch(e) { console.warn('Error guardando itinerario:', e); }
            }

            const isNew = !awbEditingId;
            if (isNew && leg1.flt_number && leg1.dep_date) {
                try {
                    await apiFetch('/shipments/priority_api.php', {
                        method: 'POST',
                        body: JSON.stringify({
                            flt_number:   leg1.flt_number,
                            flt_date:     leg1.dep_date,
                            moving_id:    res.id || null,
                            new_priority: null,
                            user_id:      currentUser?.id || null,
                        })
                    });
                } catch(e) { /* no bloquear si falla */ }
            }

            if (currentView === 'booking' && typeof bkLoad === 'function') bkLoad();
            setTimeout(awbBookingCloseModal, 900);
        } else {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = res.message || 'Error al guardar.';
            btn.disabled = false;
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Error de conexión.';
        btn.disabled = false;
    }
}
</script>


<style>
.mf-edit-cell {
  cursor: pointer; min-height: 20px; padding: 2px 4px; border-radius: 3px; transition: background .15s;
}
.mf-edit-cell:hover { background: #F0F4F8; }
.mf-badge-row { display: flex; gap: 10px; flex-wrap: wrap; }
.mf-badge {
  background: #F4F7FB; border: 1px solid var(--border); border-radius: 8px;
  padding: 6px 14px; text-align: center; min-width: 100px;
}
.mf-badge-label {
  font-size: 9px; font-weight: 700; text-transform: uppercase; color: var(--text-light);
  letter-spacing: .06em; margin-bottom: 1px;
}
.mf-badge-value {
  font-family: 'Barlow Condensed', sans-serif; font-size: 20px; font-weight: 800; color: var(--delta-navy);
}

/* ── Tabs Pre-Manifest / ULD Builder rediseñados como pasos de un
   flujo (Step 1 / Step 2) — Correcciones Pre-Manifest #8 ── */
.mf-step-tabs { display: flex; align-items: center; gap: 10px; margin-left: 18px; }
.mf-step-arrow { font-size: 18px; color: var(--text-light); font-weight: 700; }
.mf-step-tab {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  min-width: 190px; padding: 7px 16px 8px; border-radius: 8px; cursor: pointer;
  background: #F4F7FB; border: 2px solid var(--border); text-align: left;
  transition: all .15s;
}
.mf-step-tab:hover { border-color: var(--delta-sky); }
.mf-step-tab-badge {
  font-size: 9px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
  color: var(--text-light);
}
.mf-step-tab-head { display: flex; align-items: center; gap: 6px; }
.mf-step-tab-icon { font-size: 15px; line-height: 1; }
.mf-step-tab-title { font-size: 14px; font-weight: 800; color: var(--delta-navy); }
.mf-step-tab-desc { font-size: 10.5px; font-weight: 500; color: var(--text-mid); }
.mf-step-tab-active {
  background: #EFF6FF; border-color: var(--delta-navy);
  box-shadow: 0 2px 6px rgba(0,34,68,.12);
}
.mf-step-tab-active .mf-step-tab-badge { color: var(--delta-sky); }

/* ── Diagrama de flujo horizontal (stepper) del proceso ULD Builder —
   Correcciones Pre-Manifest #9 ── */
.uld-flow-step { display: flex; align-items: center; gap: 7px; padding: 4px 10px; }
.uld-flow-step-num {
  width: 20px; height: 20px; border-radius: 50%; background: var(--delta-navy); color: #fff;
  font-size: 11px; font-weight: 800; display: flex; align-items: center; justify-content: center; flex-shrink: 0;
}
.uld-flow-step-icon { font-size: 15px; }
.uld-flow-step-label { font-size: 12px; font-weight: 700; color: var(--delta-navy); white-space: nowrap; }
.uld-flow-arrow { font-size: 15px; color: var(--text-light); font-weight: 700; }
</style>

<script>
// ══════════════════════════════════════════════════════════════
//  MÓDULO MANIFEST (mf*)
// ══════════════════════════════════════════════════════════════
let mfRows = [];
let mfFlightsCache = [];
let mfManifestStatus = null;   // 'PRE_MANI' | 'DEFINITIVE' | null
let mfPayloadKgCache = 0;      // payload_kg del vuelo/fecha filtrados (flight_operations), para el indicador de capacidad
let mfDragId         = null;   // id de la guía arrastrada desde el badge de PRIOR (Pre-Manifest)
const MF_WAREHOUSES = ['TALMA', 'FRIO_AEREO'];
// MF_ULD_TYPES is now loaded dynamically (see AWB_ULD_TYPES section above)
const MF_ULD_TYPES_LEGACY = [];
const MF_EDITABLE_COLS = {
    uld_type:  { type: 'select', options: MF_ULD_OPTIONS, allowEmpty: true },
    skids_qty: { type: 'number', integer: true },
    remark:    { type: 'text',   maxlength: 200 },
    dam:       { type: 'text',   maxlength: 50  },
    date_dam:  { type: 'date' },
};
let mfCanEdit = false;

function manifestInit() {
    mfCanEdit = hasPermission('Manifest','create') || hasPermission('Manifest','edit');

    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-navy)"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>
            <div class="module-title">Pre-Manifest</div>
            <div class="mf-step-tabs">
                <button id="mfTabPre" onclick="mfSwitchTab('pre')" class="mf-step-tab mf-step-tab-active">
                    <span class="mf-step-tab-badge">STEP 1</span>
                    <span class="mf-step-tab-head"><span class="mf-step-tab-icon">📋</span><span class="mf-step-tab-title">Pre-Manifest</span></span>
                    <span class="mf-step-tab-desc">Select AWBs and assign warehouse / ULD</span>
                </button>
                <span class="mf-step-arrow">→</span>
                <button id="mfTabUld" onclick="mfSwitchTab('uld')" class="mf-step-tab">
                    <span class="mf-step-tab-badge">STEP 2</span>
                    <span class="mf-step-tab-head"><span class="mf-step-tab-icon">✈</span><span class="mf-step-tab-title">ULD Builder</span></span>
                    <span class="mf-step-tab-desc">Armar ULDs y generar el Outbound Report</span>
                </button>
            </div>
        </div>

        <!-- ── TAB: PRE-MANIFEST ─────────────────────────────────── -->
        <div id="mfTabContentPre">
        <div class="toolbar" style="align-items:flex-end;">
            <div class="awb-field"><label>EST.</label><select id="mfStation" class="awb-input" onchange="mfLoadFlights()" style="width:80px;"></select></div>
            <div class="awb-field"><label>FLT N°</label><select id="mfFlight" class="awb-input" onchange="mfLoad()" style="width:160px;"><option value="">— Select —</option></select></div>
            <div class="awb-field"><label>DATE</label><input type="date" id="mfDate" class="awb-input" onchange="mfLoad()" style="width:150px;border:2px solid var(--delta-sky);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#EFF6FF;"></div>
            <div class="awb-field"><label>ETD</label><input type="time" id="mfEtd" class="awb-input" readonly title="Automatically retrieved from Flight Profile for this flight/date." style="width:110px;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;"></div>
            <div class="awb-field"><label>MANIFEST N°</label><input type="text" id="mfManifestNumber" class="awb-input" maxlength="12" placeholder="${new Date().getFullYear()}-" oninput="mfFormatManifestNumber(this)" style="width:150px;text-align:center;border:2px solid var(--delta-sky);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#EFF6FF;"></div>
            <div class="awb-field"><label>WAREHOUSE DEP.</label><input type="time" id="mfWhExitTime" class="awb-input" onchange="mfAutoSaveGlobal('wh_exit_time',this.value)" style="width:110px;border:2px solid var(--delta-sky);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#EFF6FF;"></div>
            <button class="btn-secondary" onclick="mfOpenColMgr()">
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
                Columnas
            </button>
            ${mfCanEdit ? `<button class="btn-primary" onclick="mfNewAwb()" title="Create a last-minute AWB for this flight/date">&#9992; New AWB</button>` : ''}
            <div class="mf-badge-row" style="margin-left:auto;">
                <div class="mf-badge"><div class="mf-badge-label">Total AWBs</div><div class="mf-badge-value" id="mfBadgeAwbs">—</div></div>
                <div class="mf-badge"><div class="mf-badge-label">Total Pieces</div><div class="mf-badge-value" id="mfBadgePiezas">—</div></div>
                <div class="mf-badge"><div class="mf-badge-label">Gross Wgt</div><div class="mf-badge-value" id="mfBadgeGross">—</div></div>
            </div>
            <!-- Indicador de capacidad de vuelo (mismo diseño/lógica que Booking):
                 Payload (flight_operations) vs Carga comprometida (SUM CHG WGT
                 de las guías activas del vuelo/fecha filtrados). -->
            <div id="mfCapIndicator" style="display:none;min-width:200px;max-width:240px;padding:6px 10px;background:#F4F6F9;border:1px solid var(--border);border-radius:6px;">
                <div style="display:flex;justify-content:space-between;font-size:10px;font-weight:700;text-transform:uppercase;color:var(--text-mid);margin-bottom:4px;">
                    <span>Flight Capacity</span>
                    <span id="mfCapPct">—</span>
                </div>
                <div id="mfCapIndicatorInner"></div>
            </div>
        </div>

        <div class="card" style="overflow-x:auto;margin-bottom:14px;">
            <table class="data-table">
                <thead>
                    <tr id="mfTableHeadRow">
                        <th style="width:36px;text-align:center;"><input type="checkbox" id="mfCheckAll" onchange="mfToggleAll(this)"></th>
                        <th style="width:50px;text-align:center;">Prio</th>
                        <th>AWB No.</th><th>Agent</th><th style="width:50px;text-align:center;">Edit</th>
                    </tr>
                </thead>
                <tbody id="mfTableBody"><tr><td colspan="11" class="empty-state">Select a flight and date.</td></tr></tbody>
                <tfoot id="mfTableFoot" style="display:none;">
                    <tr style="background:#DCE8F5;font-weight:700;color:var(--delta-navy);border-top:2px solid var(--delta-navy);border-bottom:2px solid var(--delta-navy);">
                        <td colspan="7" style="padding:10px;text-align:right;text-transform:uppercase;font-size:11px;">Totals (checked)</td>
                        <td id="mfTotPieces" style="padding:10px;text-align:right;">0</td>
                        <td id="mfTotGross" style="padding:10px;text-align:right;">0</td>
                        <td colspan="3"></td>
                    </tr>
                </tfoot>
            </table>
        </div>

        <div id="mfDistroBar" style="display:none;margin-bottom:14px;padding:12px 14px;background:#F8FAFC;border:1px solid var(--border);border-radius:6px;">
            <div style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);margin-bottom:8px;">Distribution Sheet by Warehouse</div>
            <div id="mfDistroButtons" style="display:flex;gap:10px;flex-wrap:wrap;"></div>
        </div>

        <div style="display:flex;justify-content:flex-end;align-items:center;gap:10px;">
            <div id="mfMsg" style="font-size:13px;font-weight:600;flex:1;"></div>
            <button class="btn-secondary" onclick="mfPrint()" title="Print Pre-Manifest"
                style="display:flex;align-items:center;gap:6px;">
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                    <polyline points="6 9 6 2 18 2 18 9"/>
                    <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/>
                    <rect x="6" y="14" width="12" height="8"/>
                </svg>
                Print
            </button>
        </div>
        </div><!-- /mfTabContentPre -->

        <!-- ── TAB: ULD BUILDER ──────────────────────────────────── -->
        <div id="mfTabContentUld" style="display:none;margin-top:0;">

            <!-- Flight info bar (mirrors Pre-Manifest toolbar) -->
            <div style="background:#F8FAFC;border:1px solid var(--border);border-radius:6px;
                padding:10px 16px;margin-bottom:12px;display:flex;align-items:flex-end;
                gap:20px;flex-wrap:wrap;">
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                        font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Station</label>
                    <div id="uldInfoStation" style="font-size:15px;font-weight:700;color:var(--delta-navy);">—</div>
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                        font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Flight</label>
                    <div id="uldInfoFlt" style="font-size:15px;font-weight:700;color:var(--delta-navy);">—</div>
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                        font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Flight Date</label>
                    <div id="uldInfoDate" style="font-size:15px;font-weight:700;color:var(--delta-navy);">—</div>
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                        font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">ETD</label>
                    <div id="uldInfoEtd" style="font-size:15px;font-weight:700;color:var(--delta-navy);">—</div>
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                        font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Manifest No.</label>
                    <div id="uldInfoMn" style="font-size:15px;font-weight:800;color:var(--delta-navy);">—</div>
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                        font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Warehouse Dep.</label>
                    <div id="uldInfoWhe" style="font-size:15px;font-weight:700;color:var(--delta-navy);">—</div>
                </div>
                <div style="width:1px;background:var(--border);margin:0 4px;align-self:stretch;"></div>
                <!-- Operational flight data — SOLO LECTURA (Corrección arquitectura
                     Flight/Aircraft/Capacity): edición exclusiva desde Flight Profile
                     (fdSave), que escribe en la misma tabla flight_operations que lee
                     mfLoadOpsData() más abajo. Mismo patrón aplicado en Post-Flight. -->
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">A/C TYPE</label>
                    <input type="text" id="mfAcType" class="awb-input" placeholder="—" readonly title="Edit in Flight Profile" style="width:170px;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">SHIP NO.</label>
                    <input type="text" id="mfTailNumber" class="awb-input" placeholder="—" readonly title="Edit in Flight Profile" style="width:80px;text-transform:uppercase;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Payload KG</label>
                    <input type="number" id="mfPayloadKg" class="awb-input" placeholder="—" readonly title="Edit in Flight Profile" style="width:85px;text-align:right;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Pax O/B</label>
                    <input type="number" id="mfPaxOb" class="awb-input" placeholder="—" readonly title="Edit in Flight Profile" style="width:70px;text-align:center;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">NR Pax O/B</label>
                    <input type="number" id="mfNrPax" class="awb-input" placeholder="—" readonly title="Edit in Flight Profile" style="width:70px;text-align:center;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Bags Bulk</label>
                    <input type="number" id="mfBagsInBulk" class="awb-input" placeholder="—" readonly title="Edit in Flight Profile" style="width:70px;text-align:center;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;text-transform:uppercase;color:var(--text-mid);font-weight:700;letter-spacing:.06em;display:block;margin-bottom:3px;">Fuel LBS</label>
                    <input type="number" id="mfFuelLbs" class="awb-input" placeholder="—" readonly title="Edit in Flight Profile" style="width:80px;text-align:right;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;">
                </div>
                <div style="margin-left:auto;display:flex;gap:14px;">
                    <div style="text-align:center;">
                        <div style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                            font-weight:700;letter-spacing:.06em;margin-bottom:3px;">Total AWBs</div>
                        <div id="uldInfoAwbs" style="font-size:20px;font-weight:900;color:var(--delta-navy);">—</div>
                    </div>
                    <div style="text-align:center;">
                        <div style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                            font-weight:700;letter-spacing:.06em;margin-bottom:3px;">Total Pieces</div>
                        <div id="uldInfoPzas" style="font-size:20px;font-weight:900;color:var(--delta-navy);">—</div>
                    </div>
                    <div style="text-align:center;">
                        <div style="font-size:10px;text-transform:uppercase;color:var(--text-mid);
                            font-weight:700;letter-spacing:.06em;margin-bottom:3px;">Gross WGT</div>
                        <div id="uldInfoKg" style="font-size:20px;font-weight:900;color:var(--delta-navy);">—</div>
                    </div>
                </div>
            </div>

            <!-- Diagrama de flujo + resumen de ULDs + botones de acción, todo en
                 la misma línea (Correcciones Pre-Manifest #1): el diagrama ocupa
                 solo el ancho de su contenido — ya no se estira hasta el borde
                 derecho del formulario — dejando espacio visible para los botones. -->
            <div style="display:flex;align-items:center;gap:12px;margin-bottom:12px;flex-wrap:wrap;">
                <!-- Diagrama de flujo del proceso ULD Builder: guía visual de los
                     4 pasos antes de empezar a trabajar en el módulo. -->
                <div style="display:flex;align-items:center;gap:6px;padding:8px 14px;flex:0 0 auto;
                     background:#F8FAFC;border:1px solid var(--border);border-radius:8px;flex-wrap:wrap;">
                    <div class="uld-flow-step">
                        <div class="uld-flow-step-num">1</div>
                        <div class="uld-flow-step-icon">📦</div>
                        <div class="uld-flow-step-label">Crear ULD / Bulk</div>
                    </div>
                    <div class="uld-flow-arrow">→</div>
                    <div class="uld-flow-step">
                        <div class="uld-flow-step-num">2</div>
                        <div class="uld-flow-step-icon">📄</div>
                        <div class="uld-flow-step-label">Assign AWBs (Drag &amp; Drop)</div>
                    </div>
                    <div class="uld-flow-arrow">→</div>
                    <div class="uld-flow-step">
                        <div class="uld-flow-step-num">3</div>
                        <div class="uld-flow-step-icon">⚖️</div>
                        <div class="uld-flow-step-label">Registrar peso del ULD</div>
                    </div>
                    <div class="uld-flow-arrow">→</div>
                    <div class="uld-flow-step">
                        <div class="uld-flow-step-num">4</div>
                        <div class="uld-flow-step-icon">📋</div>
                        <div class="uld-flow-step-label">Generar Outbound Report</div>
                    </div>
                </div>
                <!-- Resumen de ULDs por tipo (dinámico — aparece cuando ya hay ULDs creados) -->
                <div id="uldSummaryBar" style="flex:1;min-width:120px;"></div>
                <!-- Buttons -->
                <div style="display:flex;gap:8px;align-items:center;flex-shrink:0;margin-left:auto;">
                    <button class="btn-secondary" onclick="uldAddNew(false)"
                        style="font-size:14px;padding:7px 18px;font-weight:700;">+ ULD</button>
                    <button class="btn-secondary" onclick="uldAddNew(true)"
                        style="font-size:14px;padding:7px 18px;font-weight:700;
                        border-color:#C8102E;color:#C8102E;">+ BULK</button>
                    <button id="uldBtnReport" onclick="uldShowReportMenu()" disabled
                        style="font-size:14px;padding:7px 20px;font-weight:700;background:#C8102E;
                        color:#fff;border:none;border-radius:5px;cursor:pointer;
                        display:flex;align-items:center;gap:7px;opacity:.45;">
                        <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                            <polyline points="6 9 6 2 18 2 18 9"/>
                            <path d="M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2"/>
                            <rect x="6" y="14" width="12" height="8"/>
                        </svg>
                        OUTBOUND REPORT
                    </button>
                </div>
            </div>

            <!-- Main grid -->
            <div style="display:grid;grid-template-columns:360px 1fr;gap:18px;align-items:start;">
                <!-- LEFT -->
                <div>
                    <div style="font-size:14px;font-weight:800;color:var(--delta-navy);
                        text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px;
                        display:flex;align-items:center;gap:7px;">
                        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
                            <path d="M22 2L11 13"/><path d="M22 2L15 22l-4-9-9-4 20-7z"/>
                        </svg>
                        AWBs del vuelo
                    </div>
                    <div style="font-size:12px;color:var(--text-mid);margin-bottom:12px;font-style:italic;">
                        Drag AWBs to a ULD or click to assign
                    </div>
                    <div id="uldAwbList" style="max-height:calc(100vh - 420px);overflow-y:auto;"></div>
                </div>
                <!-- RIGHT -->
                <div>
                    <div style="font-size:14px;font-weight:800;color:var(--delta-navy);
                        text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px;">
                        Configuracion de ULDs
                    </div>
                    <div id="uldList"></div>
                    <div id="uldMsg" style="font-size:14px;font-weight:600;margin-top:10px;min-height:20px;"></div>
                </div>
            </div>
        </div><!-- /mfTabContentUld -->
    `;

    populateStationSelect('mfStation').then(() => mfLoadFlights());
    const dateEl = document.getElementById('mfDate');
    const t = new Date();
    dateEl.value = t.getFullYear()+'-'+String(t.getMonth()+1).padStart(2,'0')+'-'+String(t.getDate()).padStart(2,'0');
    mfLoadSendHistory();
}

// NOTA: mfLoadAircraftTypes() se eliminó — A/C Type en Pre-Manifest pasó de
// <select> editable a campo de solo lectura (ver Corrección arquitectura
// Flight/Aircraft/Capacity). El valor ahora se pinta directo desde
// flight_operations.aircraft_type vía mfLoadOpsData(), sin poblar catálogo.

// ── "New AWB" desde Pre-Manifest: abre el modal COMPLETO (no el de
// Booking), preseleccionando el vuelo/fecha ya elegidos en la pantalla,
// para que Operaciones pueda crear guías de última hora antes del cierre
// del manifiesto sin tener que volver a buscar el vuelo. ──
async function mfNewAwb() {
    const flt = document.getElementById('mfFlight')?.value;
    const date = document.getElementById('mfDate')?.value;
    if (!flt || !date) { alert('Select a flight and date before creating an AWB.'); return; }
    await awbOpenModal();
    const fltSel = document.getElementById('awbm_flt_select');
    if (fltSel) {
        const opt = Array.from(fltSel.options).find(o => o.dataset.fltNumber === flt);
        if (opt) {
            fltSel.value = opt.value;
            const dateEl = document.getElementById('awbm_flt_date');
            if (dateEl) dateEl.value = date;
            awbOnFlightChange();
        }
    }
}

async function mfLoadFlights() {
    const station = document.getElementById('mfStation')?.value || 'LIM';
    try {
        const res = await apiFetch('/flights/flights_api.php?station=' + station);
        mfFlightsCache = res.success ? res.data : [];
        const sel = document.getElementById('mfFlight');
        sel.innerHTML = '<option value="">— Select —</option>';
        mfFlightsCache.forEach(f => {
            const o = document.createElement('option');
            o.value = f.flt_number;
            o.textContent = f.flt_number + '  ' + f.flt_orig + '→' + f.flt_dest;
            sel.appendChild(o);
        });
    } catch(e) {}
}

// ── Resalta MANIFEST N° en rojo claro mientras el vuelo/fecha seleccionado
// no tenga un número de manifiesto válido asignado (Correcciones Pre-Manifest
// #1). Se considera "asignado" el mismo umbral que dispara la validación de
// duplicados (año + al menos 1 dígito correlativo, sin terminar en '-'). ──
function mfUpdateMnHighlight() {
    const el = document.getElementById('mfManifestNumber');
    if (!el) return;
    const v = (el.value || '').trim();
    const hasNumber = /^\d{4}-\d{1,6}$/.test(v);
    if (hasNumber) {
        el.style.background = '#EFF6FF';
        el.title = '';
    } else {
        el.style.background = '#FEF2F2';
        el.title = 'This flight/date does not have a manifest number assigned yet.';
    }
}

function mfFormatManifestNumber(el) {
    const year = new Date().getFullYear();
    let v = el.value;
    if (!v || v === '') {
        el.value = year + '-';
        el.setSelectionRange(el.value.length, el.value.length);
        mfUpdateMnHighlight();
        return;
    }
    const digits = v.replace(/\D/g, '');
    if (digits.length <= 4) {
        el.value = year + '-';
    } else {
        const numeric = digits.slice(4);
        el.value = year + '-' + numeric.slice(0, 6); // correlativo: máximo 6 dígitos
    }
    // El cursor se fuerza siempre al final después de reformatear — si no,
    // en algunos navegadores el caret se queda en su posición anterior y el
    // valor se ve "atascado" o los dígitos se desordenan al seguir escribiendo.
    el.setSelectionRange(el.value.length, el.value.length);
    mfUpdateMnHighlight();

    // Debounce: validate after user stops typing (800ms)
    clearTimeout(window._mfMnTimer);
    const mn = el.value;
    if (/^\d{4}-\d{1,6}$/.test(mn)) {
        window._mfMnTimer = setTimeout(() => mfValidateManifestNumber(mn), 800);
    }
}

async function mfValidateManifestNumber(newMn) {
    const flt  = document.getElementById('mfFlight')?.value  || '';
    const date = document.getElementById('mfDate')?.value    || '';
    const el   = document.getElementById('mfManifestNumber');
    if (!flt || !date || !newMn || newMn.endsWith('-')) return;

    try {
        // Check if this manifest number exists for a DIFFERENT flt+date
        const res = await apiFetch('/shipments/shipment_manifest_api.php?check_manifest='
            + encodeURIComponent(newMn)
            + '&flt_number=' + encodeURIComponent(flt)
            + '&flt_date=' + encodeURIComponent(date));

        if (!res.success) return;

        if (res.conflict) {
            // Duplicate on different flight/date — show alert modal and clear field
            mfShowMnConflict(res.conflict, newMn);
            el.value = new Date().getFullYear() + '-';
            el.style.borderColor = '#C8102E';
            mfUpdateMnHighlight();
            return;
        }

        el.style.borderColor = 'var(--delta-sky)';
        mfUpdateMnHighlight();

        // If there was a previous manifest number, ask for confirmation
        const prevMn = window._mfPreviousMn;
        if (prevMn && prevMn !== newMn && /^\d{4}-\d+$/.test(prevMn)) {
            mfShowMnChangeConfirm(flt, date, prevMn, newMn);
        } else {
            // No previous — save directly
            window._mfPreviousMn = newMn;
        }
    } catch(e) {}
}

function mfShowMnConflict(conflict, newMn) {
    const existing = document.getElementById('mnConflictModal');
    if (existing) existing.remove();
    const modal = document.createElement('div');
    modal.id = 'mnConflictModal';
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9500;display:flex;align-items:center;justify-content:center;';
    modal.innerHTML = '<div style="background:#fff;border-radius:8px;width:420px;max-width:95vw;box-shadow:0 8px 40px rgba(0,0,0,.3);overflow:hidden;">'
        + '<div style="background:#C8102E;color:#fff;padding:14px 18px;font-size:14px;font-weight:800;">&#9888; Duplicate Manifest</div>'
        + '<div style="padding:20px;">'
        + '<p style="font-size:13px;margin-bottom:14px;">Manifest <strong style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-size:15px;">' + esc(newMn) + '</strong> is already assigned to:</p>'
        + '<div style="background:#FFF5F5;border:1px solid #FCA5A5;border-radius:6px;padding:12px 14px;margin-bottom:16px;font-size:13px;">'
        + '<div><span style="color:#9CA3AF;">Flight:</span> <strong>' + esc(conflict.flt_number||'—') + '</strong></div>'
        + '<div><span style="color:#9CA3AF;">Date:</span> <strong>' + fmtDate(conflict.flt_date||'') + '</strong></div>'
        + '<div><span style="color:#9CA3AF;">Station:</span> <strong>' + esc(conflict.station||'—') + '</strong></div>'
        + '</div>'
        + '<p style="font-size:12px;color:#6B7280;margin-bottom:16px;">The field has been cleared. Enter a different manifest number.</p>'
        + '<button onclick="document.getElementById(\'mnConflictModal\').remove()" '
        + 'style="width:100%;background:var(--delta-navy);color:#fff;border:none;border-radius:6px;padding:10px;font-size:14px;font-weight:700;cursor:pointer;">Entendido</button>'
        + '</div></div>';
    document.body.appendChild(modal);
    modal.addEventListener('click', function(e){ if(e.target===modal) modal.remove(); });
}

function mfShowMnChangeConfirm(flt, date, prevMn, newMn) {
    const existing = document.getElementById('mnConfirmModal');
    if (existing) existing.remove();
    const modal = document.createElement('div');
    modal.id = 'mnConfirmModal';
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9500;display:flex;align-items:center;justify-content:center;';
    modal.innerHTML = '<div style="background:#fff;border-radius:8px;width:420px;max-width:95vw;box-shadow:0 8px 40px rgba(0,0,0,.3);overflow:hidden;">'
        + '<div style="background:var(--delta-navy);color:#fff;padding:14px 18px;font-size:14px;font-weight:800;">&#128196; Confirm Manifest Change</div>'
        + '<div style="padding:20px;">'
        + '<div style="background:#F8FAFC;border:1px solid var(--border);border-radius:6px;padding:12px 14px;margin-bottom:14px;font-size:13px;">'
        + '<div><span style="color:#9CA3AF;">Flight:</span> <strong>' + esc(flt) + '</strong></div>'
        + '<div><span style="color:#9CA3AF;">Date:</span> <strong>' + fmtDate(date) + '</strong></div>'
        + '</div>'
        + '<div style="display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:10px;margin-bottom:16px;">'
        + '<div style="text-align:center;background:#FFF5F5;border:1px solid #FCA5A5;border-radius:6px;padding:10px;">'
        + '<div style="font-size:10px;color:#9CA3AF;margin-bottom:4px;">ANTERIOR</div>'
        + '<div style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-size:16px;font-weight:700;color:#C8102E;">' + esc(prevMn) + '</div>'
        + '</div>'
        + '<div style="font-size:20px;color:var(--delta-navy);">&#8594;</div>'
        + '<div style="text-align:center;background:#EFF6FF;border:1px solid var(--delta-sky);border-radius:6px;padding:10px;">'
        + '<div style="font-size:10px;color:#9CA3AF;margin-bottom:4px;">NUEVO</div>'
        + '<div style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-size:16px;font-weight:700;color:var(--delta-navy);">' + esc(newMn) + '</div>'
        + '</div>'
        + '</div>'
        + '<p style="font-size:12px;color:#6B7280;margin-bottom:16px;">Confirm the manifest number change?</p>'
        + '<div style="display:flex;gap:10px;">'
        + '<button onclick="mfCancelMnChange(\'' + esc(prevMn) + '\')" '
        + 'class="btn-secondary" style="flex:1;">Cancel</button>'
        + '<button onclick="mfConfirmMnChange(\'' + esc(flt) + '\',\'' + esc(date) + '\',\'' + esc(prevMn) + '\',\'' + esc(newMn) + '\')" '
        + 'class="btn-primary" style="flex:2;font-weight:700;">Confirm Change</button>'
        + '</div></div></div>';
    document.body.appendChild(modal);
}

function mfCancelMnChange(prevMn) {
    document.getElementById('mnConfirmModal')?.remove();
    const el = document.getElementById('mfManifestNumber');
    if (el) el.value = prevMn; // Revert to previous
    mfUpdateMnHighlight();
}

async function mfConfirmMnChange(flt, date, prevMn, newMn) {
    document.getElementById('mnConfirmModal')?.remove();
    window._mfPreviousMn = newMn;

    // Update the field visually
    const el = document.getElementById('mfManifestNumber');
    if (el) el.value = newMn;
    mfUpdateMnHighlight();

    const msg = document.getElementById('mfMsg');
    if (msg) { msg.style.color = '#854F0B'; msg.textContent = 'Saving manifest change...'; }

    // Save the entire manifest immediately (same as pressing "Guardar Pre-Manifiesto")
    await mfSave();

    bkShowToast('✓ Manifest updated: ' + newMn, '#0F6E56');
}
async function mfLookupManifestNumber(mn) {
    try {
        const res = await apiFetch('/shipments/shipment_manifest_api.php?lookup_manifest=' + encodeURIComponent(mn));
        if (res.success && res.found) {
            document.getElementById('mfWhExitTime').value = res.wh_exit_time ? res.wh_exit_time.substring(0,5) : '';
            // ETD ya no se sobreescribe aquí: es de solo lectura y siempre
            // refleja Flight Profile (flight_operations.dep_time) del vuelo/
            // fecha activo, no el valor histórico de otro manifiesto.
        }
    } catch(e) {}
}

// ── Datos operativos en Pre-Manifest ─────────────────────────────
// ── Guardado directo del Almacén (no depende de tener un Nº de Manifiesto
// válido — a diferencia de mfAutoSaveField/manifest_field_api.php, que es
// para campos propios del manifiesto). Mismo endpoint y misma UX que
// bkSaveWarehouse() en Booking, para mantener el comportamiento consistente
// en todo el sistema. ──
async function mfSaveWarehouse(shipmentId, warehouse) {
    if (mfManifestStatus === 'DEFINITIVE') {
        const sel = document.querySelector('.mf-warehouse[data-id="'+shipmentId+'"]');
        const row = mfRows.find(function(r){ return r.id == shipmentId; });
        if (sel && row) sel.value = row.warehouse || '';
        alert('The warehouse cannot be modified: the manifest is closed.');
        return;
    }

    const sel = document.querySelector('.mf-warehouse[data-id="'+shipmentId+'"]');
    if (sel) { sel.disabled = true; sel.style.opacity = '0.7'; }

    try {
        const res = await apiFetch('/shipments/shipments_patch_api.php', {
            method: 'POST',
            body: JSON.stringify({ id: shipmentId, field: 'warehouse', value: warehouse || null })
        });

        if (sel) { sel.disabled = !(mfCanEdit); sel.style.opacity = '1'; }

        if (res && res.success) {
            const row = mfRows.find(function(r){ return r.id == shipmentId; });
            if (row) row.warehouse = warehouse;
            bkShowToast('✓ Warehouse updated successfully', '#0F6E56');
            if (sel) {
                sel.style.outline = '2px solid #2ECC71';
                setTimeout(function(){ sel.style.outline = ''; }, 1500);
            }
        } else {
            bkShowToast('Error saving warehouse: ' + (res && res.message || 'Unknown error'), '#C8102E');
            const row = mfRows.find(function(r){ return r.id == shipmentId; });
            if (sel && row) sel.value = row.warehouse || '';
        }
    } catch(e) {
        if (sel) { sel.disabled = !(mfCanEdit); sel.style.opacity = '1'; }
        bkShowToast('Connection error while saving warehouse', '#C8102E');
        const row = mfRows.find(function(r){ return r.id == shipmentId; });
        if (sel && row) sel.value = row.warehouse || '';
    }
}

// ── Modal de confirmación antes de aplicar un cambio de prioridad (por
// Drag & Drop o edición manual). Solo tras Confirmar se ejecuta onConfirm(). ──
function mfShowPriorityConfirm(shipmentId, newPrio, onConfirm, onCancel) {
    const r = mfRows.find(function(x){ return x.id == shipmentId; });
    if (!r) { if (onCancel) onCancel(); return; }

    document.getElementById('mfPrioConfirmModal')?.remove();

    const dest = r.routing_dest || r.flt_dest || '—';
    const row = function(label, value) {
        return '<tr><td style="padding:4px 0;color:var(--text-light);font-size:12px;">' + label + '</td>'
             + '<td style="padding:4px 0;text-align:right;font-weight:700;font-size:13px;">' + value + '</td></tr>';
    };

    const overlay = document.createElement('div');
    overlay.id = 'mfPrioConfirmModal';
    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:9500;'
        + 'display:flex;align-items:center;justify-content:center;';
    overlay.innerHTML = '<div style="background:#fff;border-radius:8px;width:380px;max-width:92vw;'
        + 'box-shadow:0 8px 40px rgba(0,0,0,.3);overflow:hidden;">'
        + '<div style="background:var(--delta-navy);color:#fff;padding:14px 18px;">'
        + '<div style="font-size:14px;font-weight:800;">Change the priority of this AWB?</div>'
        + '</div>'
        + '<div style="padding:18px 20px;">'
        + '<table style="width:100%;border-collapse:collapse;">'
        + row('AWB', '<span style="font-family:\'Courier New\',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:var(--delta-navy);">' + esc(r.awb || r.shipment_number) + '</span>')
        + row('Freight Forwarder', esc(r.agent_name || '—'))
        + row('Destination', esc(dest))
        + row('Piezas', r.pieces || 0)
        + row('Gross Wgt', fmtWgt0(r.gross_weight) + ' Kg')
        + '<tr><td colspan="2" style="padding-top:8px;border-top:1px solid var(--border);"></td></tr>'
        + row('Current priority', '<span style="color:var(--text-mid);">' + (r.priority || '—') + '</span>')
        + row('New priority', '<span style="color:var(--delta-sky);font-size:15px;">' + newPrio + '</span>')
        + '</table>'
        + '</div>'
        + '<div style="display:flex;justify-content:flex-end;gap:10px;padding:14px 20px;background:#F8FAFC;border-top:1px solid var(--border);">'
        + '<button id="mfPrioCancelBtn" class="btn-secondary">Cancel</button>'
        + '<button id="mfPrioConfirmBtn" class="btn-primary">Confirm</button>'
        + '</div></div>';
    document.body.appendChild(overlay);

    const cleanup = function() { overlay.remove(); };
    document.getElementById('mfPrioCancelBtn').onclick = function() { cleanup(); if (onCancel) onCancel(); };
    document.getElementById('mfPrioConfirmBtn').onclick = function() { cleanup(); onConfirm(); };
    overlay.addEventListener('click', function(e){ if (e.target === overlay) { cleanup(); if (onCancel) onCancel(); } });
}

async function mfLoadOpsData(flt, date) {
    if (!flt || !date) return;
    try {
        const res = await apiFetch('/flights/flight_operations_api.php?flt_number='
            + encodeURIComponent(flt) + '&flt_date=' + date);
        const etdEl = document.getElementById('mfEtd');
        if (res.success && res.data) {
            const d = res.data;
            const fill = (id, v) => { const el = document.getElementById(id); if (el && v != null && v !== '') el.value = v; };
            fill('mfAcType',     d.aircraft_type);
            fill('mfTailNumber', d.ship_number);
            fill('mfPayloadKg',  d.payload_kg);
            fill('mfPaxOb',      d.pax_ob);
            fill('mfNrPax',      d.nr_pax_ob);
            fill('mfBagsInBulk', d.bags_in_bulk);
            fill('mfFuelLbs',    d.fuel_lbs);
            // ETD: solo lectura, se obtiene de Flight Profile (flight_operations.dep_time)
            // para este vuelo/fecha — ya no se ingresa manualmente en Pre-Manifest.
            if (etdEl) etdEl.value = d.dep_time ? String(d.dep_time).substring(0,5) : '';
            // Reutiliza este mismo fetch (ya trae payload_kg) para alimentar el
            // indicador de capacidad, sin llamar dos veces a flight_operations.
            mfPayloadKgCache = parseFloat(d.payload_kg) || 0;
        } else {
            if (etdEl) etdEl.value = '';
            mfPayloadKgCache = 0;
        }
    } catch(e) {
        mfPayloadKgCache = 0;
    }
    mfUpdateCapIndicator();
}

// ── Indicador de capacidad (mismo diseño/lógica que Booking): Payload
// (flight_operations, cacheado por mfLoadOpsData) vs Peso comprometido
// = SUM(GROSS WGT) de las guías activas del vuelo/fecha filtrados. ──
function mfUpdateCapIndicator() {
    const box = document.getElementById('mfCapIndicator');
    if (!box) return;
    if (!mfPayloadKgCache) { box.style.display = 'none'; return; }

    const grossActive = (mfRows || [])
        .filter(function(r){ return !['CANCELLED','OFFLOADED'].includes(r.operational_status); })
        .reduce(function(a,r){ return a + parseFloat(r.gross_weight||0); }, 0);

    box.style.display = 'block';
    document.getElementById('mfCapIndicatorInner').innerHTML = capIndicatorHTML(mfPayloadKgCache, grossActive);
    const pct = (grossActive / mfPayloadKgCache) * 100;
    document.getElementById('mfCapPct').textContent = pct.toFixed(1) + '%';
    document.getElementById('mfCapPct').style.color = capIndicatorColor(pct);
}

// NOTA: mfSaveOpsData() se eliminó — la barra "Operational flight data" de
// Pre-Manifest (ULD Builder) es de solo lectura (Corrección arquitectura
// Flight/Aircraft/Capacity, ver análisis). Antes escribía directo sobre
// flight_operations en paralelo a Flight Profile (fdSave), duplicando el
// punto de edición del mismo registro. Toda edición de A/C Type, Ship #,
// Payload, PAX O/B, NR PAX O/B, Bags in Bulk y Fuel se hace exclusivamente
// desde Flight Profile — mfLoadOpsData() abajo sigue leyendo esos mismos
// campos, sin cambios.

async function mfLoad() {
    const flt = document.getElementById('mfFlight').value;
    const date = document.getElementById('mfDate').value;
    const station = document.getElementById('mfStation')?.value || 'LIM';
    const tbody = document.getElementById('mfTableBody');
    const tfoot = document.getElementById('mfTableFoot');

    if (!flt || !date) {
        tbody.innerHTML = '<tr><td colspan="12" class="empty-state">Select a flight and date.</td></tr>';
        tfoot.style.display = 'none';
        mfRows = [];
        mfPayloadKgCache = 0;
        mfUpdateCapIndicator();
        return;
    }

    tbody.innerHTML = '<tr><td colspan="12" class="empty-state">Loading...</td></tr>';

    try {
        const params = new URLSearchParams({ flt_number: flt, flt_date: date, station });
        const res = await apiFetch('/shipments/shipment_manifest_api.php?' + params.toString());
        if (!res.success) {
            tbody.innerHTML = '<tr><td colspan="12" class="empty-state">' + esc(res.message||'Error') + '</td></tr>';
            return;
        }
        // Regla de negocio: el Pre-Manifest solo debe ofrecer guías CONFIRMED
        // de Booking como candidatas nuevas. Las que ya están vinculadas a
        // este manifiesto (in_manifest=1) se conservan igual aunque su estado
        // haya avanzado (PRE_MANIFEST/LOADED/OFFLOADED) — si no, desaparecerían
        // de la pantalla al guardar el pre-manifiesto y no se podría revisar/
        // embarcar lo ya incluido.
        mfRows = (res.data || []).filter(function(r) {
            if (r.in_manifest == 1) return true;
            return r.operational_status === 'CONFIRMED';
        });
        mfManifestStatus = res.manifest_status || null;

        const mnInput = document.getElementById('mfManifestNumber');
        if (res.manifest_number) {
            // This flt+date has a manifest — load it
            mnInput.value = res.manifest_number;
            window._mfPreviousMn = res.manifest_number;
            mnInput.style.borderColor = 'var(--delta-sky)';
        } else {
            // No manifest for this flt+date — always clear regardless of previous value
            mnInput.value = new Date().getFullYear() + '-';
            window._mfPreviousMn = null;
            mnInput.style.borderColor = 'var(--delta-sky)';
        }
        mfUpdateMnHighlight();

        const whExitInput = document.getElementById('mfWhExitTime');
        if (res.wh_exit_time) whExitInput.value = res.wh_exit_time.substring(0,5);
        // ETD ya no viene del manifiesto: se carga aparte desde Flight Profile
        // (flight_operations.dep_time) en mfLoadOpsData — ver más abajo.

        // Load operational flight data
        mfLoadOpsData(flt, date);

        if (!mfRows.length) {
            tbody.innerHTML = '<tr><td colspan="12" class="empty-state">No CONFIRMED AWBs for this flight/date.</td></tr>';
            tfoot.style.display = 'none';
            mfUpdateCapIndicator();
            return;
        }

        mfRenderRows();
        tfoot.style.display = '';
        mfUpdateCapIndicator();

        mfCalcTotals();
        mfUpdateDistroBar();
        mfUpdateActionBar();
        mfLoadSendHistory();
    } catch(e) {
        tbody.innerHTML = '<tr><td colspan="12" class="empty-state">Connection error.</td></tr>';
    }
}

// Columnas del Manifest: fijas (operativamente necesarias para
// el flujo de embarque) + opcionales
const MF_COLS = [
    { key:'check',    label:'Selection',     def:true, fixed:true },
    { key:'priority', label:'Priority',      def:true, fixed:true },
    { key:'awb',      label:'AWB',           def:true, fixed:true },
    { key:'ff',       label:'Agent',         def:true, fixed:true },
    { key:'goods',    label:'Commodity',     def:true,  fixed:false },
    { key:'dest',     label:'Destination',   def:true,  fixed:false },
    { key:'pieces',   label:'Pieces',        def:true,  fixed:false },
    { key:'gross_wgt',label:'Gross Weight',  def:true,  fixed:false },
    { key:'warehouse',label:'Warehouse',     def:true, fixed:true },
    { key:'uld',      label:'ULD Type',      def:true, fixed:true },
    { key:'skids',    label:'Skids',         def:true, fixed:true },
    { key:'remark',   label:'Remarks',       def:true, fixed:true },
    { key:'status',   label:'Status',        def:true, fixed:true },
];
function mfOpenColMgr() { colmgrOpen('manifest', MF_COLS, () => { mfRenderHeader(); mfRenderFoot(); mfRenderRows(); }); }

function mfRenderHeader() {
    const tr = document.getElementById('mfTableHeadRow');
    if (!tr) return;
    const settings = colmgrLoadSettings('manifest', MF_COLS).cols;
    // Encabezados en inglés, alineados con la terminología estándar de carga
    // aérea (misma que Pre-Manifest, Outbound Cargo Flight Report, Flight
    // Summary Report y Departure Notice). Mismo ancho/alineación de siempre.
    let html = '<th style="width:36px;text-align:center;"><input type="checkbox" id="mfCheckAll" onchange="mfToggleAll(this)"></th>';
    html += '<th style="width:50px;text-align:center;">Prio</th>';
    html += '<th>AWB No.</th><th>Agent</th><th style="width:50px;text-align:center;">Edit</th>';
    if (settings.goods)     html += '<th>Commodity</th>';
    if (settings.dest)      html += '<th style="text-align:center;">DEST</th>';
    if (settings.pieces)    html += '<th style="text-align:right;">Pieces</th>';
    if (settings.gross_wgt) html += '<th style="text-align:right;">GROSS WGT (K)</th>';
    html += '<th>Warehouse</th><th style="text-align:center;">Prod</th><th style="text-align:center;">ULD Type</th><th style="text-align:center;">Skids</th><th style="width:200px;">Remarks</th>';
    html += '<th style="text-align:center;">DAM No.</th><th style="text-align:center;">DAM Date</th>';
    html += '<th style="min-width:160px;">Status</th>';
    tr.innerHTML = html;
}

// ── Fila "Totals (checked)" del tfoot: antes tenía colspans fijos que no
// se ajustaban a las columnas activadas en el gestor de columnas, así que
// el fondo se quedaba corto y no llegaba al borde derecho de la tabla. Se
// recalcula igual que mfRenderHeader(), para que el colspan total siempre
// coincida con el número real de columnas visibles. ──
function mfRenderFoot() {
    const tr = document.querySelector('#mfTableFoot tr');
    if (!tr) return;
    const settings = colmgrLoadSettings('manifest', MF_COLS).cols;

    // Columnas fijas antes de Piezas/Gross Wgt: checkbox + Prior + AWB No + FF + Editar (+ Comodity/Dest si están activas)
    let leadCols = 5;
    if (settings.goods) leadCols++;
    if (settings.dest)  leadCols++;
    // Columnas fijas después de Piezas/Gross Wgt: Almacén, PROD, ULD, Skids, Remark, DAM, Date DAM, Status
    // (Split se movió al nuevo módulo Flight Departure — Incorporación Flight Departure #1)
    const trailCols = 8;

    let html = '<td colspan="' + leadCols + '" style="padding:10px;text-align:right;text-transform:uppercase;font-size:11px;">Totals (checked)</td>';
    if (settings.pieces)    html += '<td id="mfTotPieces" style="padding:10px;text-align:right;">0</td>';
    if (settings.gross_wgt) html += '<td id="mfTotGross" style="padding:10px;text-align:right;">0</td>';
    html += '<td colspan="' + trailCols + '"></td>';
    tr.innerHTML = html;
}

function mfRenderRows() {
    mfRenderHeader();
    mfRenderFoot();
    const settings = colmgrLoadSettings('manifest', MF_COLS).cols;
    const tbody = document.getElementById('mfTableBody');
    let html = '';
    mfRows.forEach(r => {
        const priColor = !r.priority ? '#9AA3AF' : r.priority===1 ? '#0066B2' : r.priority===2 ? '#1A7A4A' : r.priority===3 ? '#E07B00' : r.priority<=6 ? '#B8860B' : '#9AA3AF';
        const prioBg2  = !r.priority ? '#F4F6F9' : r.priority===1 ? '#DCE8F5' : r.priority===2 ? '#E1F5EE' : r.priority===3 ? '#FFF0DC' : r.priority<=6 ? '#FFF8DC' : '#F4F6F9';
        const checked = r.in_manifest == 1 ? 'checked' : '';
        const dest = r.routing_dest || r.flt_dest || '';
        const isLoaded = r.operational_status === 'LOADED';
        const isClosed = mfManifestStatus === 'DEFINITIVE';
        const rowBg = (isLoaded && isClosed) ? ' style="background:#E8F8F0;"' : '';
        // Reordenamiento de prioridad (Drag & Drop + edición manual): solo si el
        // manifiesto no está cerrado y la guía no está Embarcada/Loaded. El
        // arrastre solo se activa desde el badge de PRIOR (no toda la fila), pero
        // cualquier fila elegible puede recibir el "drop" de otra.
        const canDragPrio = !isClosed && !isLoaded;
        const dropAttrs = canDragPrio
            ? ' ondragover="mfRowDragOver(event)" ondragleave="mfRowDragLeave(event)" ondrop="mfRowDrop(event)"'
            : '';
        html += '<tr data-id="'+r.id+'"'+rowBg+dropAttrs+'>';
        const isRowLocked = isClosed && r.in_manifest == 1;
        html += `<td style="text-align:center;">`;
        if (isRowLocked) {
            html += `<span title="This AWB belongs to a closed manifest and cannot be modified."
                style="color:#9AA3AF;font-size:13px;cursor:not-allowed;">🔒</span>`;
        } else {
            html += `<input type="checkbox" class="mf-check" data-id="${r.id}" ${checked}
                ${mfCanEdit ? '' : 'disabled'}
                onchange="mfCalcTotals(); mfAutoSaveCheck(${r.id}, this.checked)">`;
        }
        html += `</td>`;
        if (canDragPrio && mfCanEdit) {
            html += `<td style="text-align:center;"><span class="mf-prio-badge" data-id="${r.id}" draggable="true"
                ondragstart="mfPrioDragStart(event,${r.id})" ondragend="mfDragEnd(event)"
                onclick="mfEditPriority(this)" title="Drag to reorder, or click to edit the number"
                style="cursor:move;display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;border-radius:4px;background:${prioBg2};color:${priColor};font-size:11px;font-weight:800;border:1px solid ${priColor}40;user-select:none;">${r.priority||'—'}</span></td>`;
        } else {
            html += `<td style="text-align:center;"><span title="${isClosed?'Manifest closed':isLoaded?'AWB loaded':'Not editable'}" style="display:inline-flex;align-items:center;justify-content:center;min-width:26px;height:26px;padding:0 6px;border-radius:4px;background:${prioBg2};color:${priColor};font-size:11px;font-weight:800;border:1px solid ${priColor}40;opacity:.75;">${r.priority||'—'}</span></td>`;
        }
        html += `<td><strong style="font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:var(--delta-navy);">${esc(r.awb||r.shipment_number)}</strong></td>`;
        html += `<td style="text-transform:uppercase;">${esc(r.agent_name||'—')}</td>`;
        html += `<td style="text-align:center;"><button class="action-btn-sm" onclick="awbEntryEdit(${r.id})" title="Edit AWB (full modal)">&#9998;</button></td>`;
        if (settings.goods)     html += `<td style="text-transform:uppercase;">${esc(r.description||'')||'<span style="color:var(--text-light);">—</span>'}</td>`;
        if (settings.dest)      html += `<td style="text-align:center;">${esc(dest)||'<span style="color:var(--text-light);">—</span>'}</td>`;
        if (settings.pieces)    html += `<td style="text-align:right;">${fmtWgt0(r.pieces)}</td>`;
        if (settings.gross_wgt) html += `<td style="text-align:right;">${fmtWgt0(r.gross_weight)}</td>`;
        html += '<td><select class="awb-input mf-warehouse" data-id="'+r.id+'" '+(mfCanEdit && !isClosed ?'':'disabled')+' onchange="mfUpdateDistroBar(); mfSaveWarehouse('+r.id+', this.value)" style="height:32px;font-size:12px;'+(isClosed?'opacity:.7;cursor:not-allowed;':'') +'">';
        html += '<option value="">—</option>';
        MF_WAREHOUSES.forEach(w => html += `<option value="${w}" ${r.warehouse===w?'selected':''}>${w.replace('_',' ')}</option>`);
        html += '</select></td>';
        // PROD badge
        const prod = (r.product_code||'').toUpperCase();
        const prodColor = (prod==='COL'||prod==='FRE') ? '#0F6E56' : prod ? '#5B6472' : '';
        html += '<td style="text-align:center;">'
              + (prod ? '<span style="display:inline-block;padding:1px 7px;border-radius:10px;font-size:10px;font-weight:700;color:#fff;background:'+prodColor+';">'+prod+'</span>'
                      : '<span style="color:var(--text-light);font-size:11px;">&#8212;</span>')
              + '</td>';
        html += `<td style="text-align:center;">${mfEditableCell(r.id,'uld_type',r.uld_type||'', esc(r.uld_type||'')||'<span style="color:var(--text-light);">—</span>')}</td>`;
        html += `<td style="text-align:center;">${mfEditableCell(r.id,'skids_qty',r.skids_qty||'', (r.skids_qty!=null&&r.skids_qty!==''?r.skids_qty:'<span style="color:var(--text-light);">—</span>'))}</td>`;
        html += `<td style="text-transform:uppercase;">${mfEditableCell(r.id,'remark',r.remark||'', esc(r.remark||'')||'<span style="color:var(--text-light);">—</span>')}</td>`;
        html += `<td style="text-align:center;">${mfEditableCell(r.id,'dam',r.dam||'', esc(r.dam||'')||'<span style="color:var(--text-light);">—</span>')}</td>`;
        html += `<td style="text-align:center;">${mfEditableCell(r.id,'date_dam',r.date_dam||'', r.date_dam ? fmtDate(r.date_dam) : '<span style="color:var(--text-light);">—</span>')}</td>`;
        html += `<td>${mfEmbarqueCell(r, true)}</td>`;
        html += '</tr>';
    });
    tbody.innerHTML = html;
}

// ══════════════════════════════════════════════════════════════
//  Reordenamiento de prioridad en Pre-Manifest: Drag & Drop (solo
//  desde el badge de PRIOR) + edición manual (clic en el badge).
//  Ambos flujos piden confirmación antes de aplicar el cambio, y
//  reusan bkReorderPriorities() (misma lógica que Booking).
// ══════════════════════════════════════════════════════════════

// ── Edición manual: clic en el badge → input numérico ──
function mfEditPriority(badge) {
    if (badge.querySelector('input')) return; // ya está editando
    const id  = badge.dataset.id;
    const cur = parseInt(badge.textContent.trim()) || '';

    const input = document.createElement('input');
    input.type  = 'number'; input.min = '1'; input.max = '99';
    input.value = cur;
    input.style.cssText = 'width:40px;text-align:center;font-size:11px;font-weight:800;'
                        + 'border:none;outline:none;background:transparent;padding:0;';

    badge.innerHTML = '';
    badge.appendChild(input);
    input.focus(); input.select();

    const cancel = function() { mfRenderRows(); };
    const commit = function() {
        const newPrio = parseInt(input.value);
        if (!newPrio || newPrio < 1 || newPrio === cur) { cancel(); return; }
        mfShowPriorityConfirm(parseInt(id), newPrio, async function() {
            const flt  = document.getElementById('mfFlight').value;
            const date = document.getElementById('mfDate').value;
            await bkReorderPriorities(flt, date, parseInt(id), newPrio);
            mfLoad();
        }, cancel);
    };
    input.addEventListener('blur', commit);
    input.addEventListener('keydown', function(e){
        if (e.key === 'Enter')  { e.preventDefault(); commit(); }
        if (e.key === 'Escape') { cancel(); }
    });
}

// ── Drag & Drop: el arrastre solo arranca desde el badge de PRIOR
// (draggable="true" está solo ahí, no en toda la fila) — el resto de la
// fila conserva su comportamiento normal de click. ──
function mfPrioDragStart(e, id) {
    mfDragId = id;
    e.dataTransfer.effectAllowed = 'move';
    try { e.dataTransfer.setData('text/plain', String(id)); } catch(err) {}
    const tr = e.currentTarget.closest('tr');
    if (tr) tr.classList.add('bk-dragging');
}

function mfRowDragOver(e) {
    if (!mfDragId) return;
    const tr = e.currentTarget;
    if (String(tr.dataset.id) === String(mfDragId)) return;
    e.preventDefault();
    e.dataTransfer.dropEffect = 'move';
    const rect = tr.getBoundingClientRect();
    const isTopHalf = (e.clientY - rect.top) < rect.height / 2;
    tr.classList.toggle('bk-drag-over-top', isTopHalf);
    tr.classList.toggle('bk-drag-over-bottom', !isTopHalf);
}

function mfRowDragLeave(e) {
    e.currentTarget.classList.remove('bk-drag-over-top', 'bk-drag-over-bottom');
}

async function mfRowDrop(e) {
    e.preventDefault();
    const tr = e.currentTarget;
    tr.classList.remove('bk-drag-over-top', 'bk-drag-over-bottom');
    document.querySelectorAll('#mfTableBody tr').forEach(function(r){ r.classList.remove('bk-dragging'); });

    const targetId  = tr.dataset.id;
    const draggedId = mfDragId;
    mfDragId = null;
    if (!draggedId || String(targetId) === String(draggedId)) return;

    const draggedItem = mfRows.find(function(r){ return String(r.id) === String(draggedId); });
    const targetItem  = mfRows.find(function(r){ return String(r.id) === String(targetId); });
    if (!draggedItem || !targetItem) return;

    const rect = tr.getBoundingClientRect();
    const isTopHalf = (e.clientY - rect.top) < rect.height / 2;

    // priority_api.php saca la guía arrastrada de la secuencia y la vuelve a
    // insertar en new_priority (1-indexed) DENTRO de esa secuencia ya sin
    // ella — por eso hay que ajustar la posición del objetivo si éste venía
    // después de la guía arrastrada (misma fórmula que en Booking).
    const pDrag   = parseInt(draggedItem.priority) || 0;
    const pTarget = parseInt(targetItem.priority)  || 0;
    const targetPos = pTarget > pDrag ? pTarget - 1 : pTarget;
    const newPrio = isTopHalf ? targetPos : targetPos + 1;

    mfShowPriorityConfirm(parseInt(draggedId), newPrio, async function() {
        const flt  = document.getElementById('mfFlight').value;
        const date = document.getElementById('mfDate').value;
        await bkReorderPriorities(flt, date, parseInt(draggedId), newPrio);
        mfLoad();
    }, function() { /* Cancelar: no se tocó nada, no hace falta revertir */ });
}

function mfDragEnd(e) {
    document.querySelectorAll('#mfTableBody tr').forEach(function(r){
        r.classList.remove('bk-dragging', 'bk-drag-over-top', 'bk-drag-over-bottom');
    });
    mfDragId = null;
}

// ── AWB Split / Partial Offload Modal ────────────────────────────
async function mfShowSplitModal(shipmentId) {
    const existing = document.getElementById('awbSplitModal');
    if (existing) existing.remove();

    const overlay = document.createElement('div');
    overlay.id = 'awbSplitModal';
    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:9000;'
        + 'display:flex;align-items:center;justify-content:center;';
    overlay.innerHTML = '<div style="background:#fff;border-radius:8px;width:600px;max-width:95vw;'
        + 'max-height:90vh;overflow-y:auto;box-shadow:0 8px 40px rgba(0,0,0,.3);">'
        + '<div style="background:var(--delta-navy);color:#fff;padding:14px 18px;'
        + 'display:flex;align-items:center;justify-content:space-between;border-radius:8px 8px 0 0;">'
        + '<div><div style="font-size:14px;font-weight:800;">&#9992; Partial / Offload AWB</div>'
        + '<div style="font-size:11px;opacity:.75;margin-top:2px;">AWB split due to partial load</div></div>'
        + '<button onclick="document.getElementById(\'awbSplitModal\').remove()" '
        + 'style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer;">&#x2715;</button>'
        + '</div>'
        + '<div id="awbSplitContent" style="padding:20px;">Loading...</div>'
        + '</div>';
    document.body.appendChild(overlay);
    overlay.addEventListener('click', function(e){ if(e.target===overlay) overlay.remove(); });

    try {
        const res = await apiFetch('/shipments/awb_split_api.php?shipment_id=' + shipmentId);
        if (!res.success) { document.getElementById('awbSplitContent').textContent = 'Error: ' + res.message; return; }
        const d = res.data;
        const origPcs    = parseInt(d.pieces || 0);
        const origGross  = parseFloat(d.gross_weight || 0);
        const origChg    = parseFloat(d.chg_weight || 0);

        document.getElementById('awbSplitContent').innerHTML = `
        <!-- Info original -->
        <div style="background:#F8FAFC;border:1px solid var(--border);border-radius:6px;padding:12px 16px;margin-bottom:16px;">
            <div style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);margin-bottom:8px;">Original AWB Information</div>
            <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;font-size:12px;">
                <div><span style="color:var(--text-mid);">AWB:</span> <strong style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:var(--delta-navy);">${esc(d.awb||d.shipment_number)}</strong></div>
                <div><span style="color:var(--text-mid);">Agente/FF:</span> <strong>${esc(d.agent_name||'—')}</strong></div>
                <div><span style="color:var(--text-mid);">Producto:</span> <strong>${esc(d.description||'—')}</strong></div>
                <div><span style="color:var(--text-mid);">PCS originales:</span> <strong style="font-size:14px;color:var(--delta-navy);">${origPcs}</strong></div>
                <div><span style="color:var(--text-mid);">Gross Weight:</span> <strong>${origGross.toFixed(2)} Kg</strong></div>
                <div><span style="color:var(--text-mid);">Chg Weight:</span> <strong>${origChg.toFixed(2)} Kg</strong></div>
                <div><span style="color:var(--text-mid);">Flight:</span> <strong>${esc(d.flt_number||'—')} / ${fmtDate(d.flt_date)}</strong></div>
                <div><span style="color:var(--text-mid);">Warehouse:</span> <strong>${esc((d.warehouse||'').replace('_',' ')||'—')}</strong></div>
                <div><span style="color:var(--text-mid);">Status:</span> <strong>${esc(d.operational_status||'—')}</strong></div>
            </div>
        </div>

        <!-- Split inputs -->
        <input type="hidden" id="splitShipmentId" value="${shipmentId}">
        <input type="hidden" id="splitOrigPcs" value="${origPcs}">
        <input type="hidden" id="splitOrigGross" value="${origGross}">
        <input type="hidden" id="splitOrigChg" value="${origChg}">

        <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px;">
            <!-- EMBARCADO -->
            <div style="background:#EFF6FF;border:2px solid var(--delta-sky);border-radius:6px;padding:14px;">
                <div style="font-size:12px;font-weight:800;color:var(--delta-navy);margin-bottom:10px;">
                    &#10003; CARGA EMBARCADA</div>
                <div class="awb-field" style="margin-bottom:8px;">
                    <label style="font-size:10px;font-weight:700;color:var(--text-mid);">PCS EMBARCADAS *</label>
                    <input type="number" id="splitLoadedPcs" min="1" max="${origPcs-1}"
                        value="${origPcs}" oninput="splitCalcOffload()"
                        style="width:100%;font-size:18px;font-weight:700;text-align:center;
                        border:2px solid var(--delta-sky);border-radius:5px;padding:6px;
                        color:var(--delta-navy);background:#fff;">
                </div>
                <div class="awb-field" style="margin-bottom:8px;">
                    <label style="font-size:10px;font-weight:700;color:var(--text-mid);">GROSS WEIGHT EMBARCADO (Kg)</label>
                    <input type="number" id="splitLoadedGross" step="0.01"
                        value="${origGross.toFixed(2)}"
                        style="width:100%;font-size:14px;font-weight:700;border:2px solid var(--delta-sky);
                        border-radius:5px;padding:5px;color:var(--delta-navy);background:#fff;">
                </div>
                <div class="awb-field">
                    <label style="font-size:10px;font-weight:700;color:var(--text-mid);">CHG WEIGHT EMBARCADO (Kg)</label>
                    <input type="number" id="splitLoadedChg" step="0.01"
                        value="${origChg.toFixed(2)}"
                        style="width:100%;font-size:14px;font-weight:700;border:2px solid var(--delta-sky);
                        border-radius:5px;padding:5px;color:var(--delta-navy);background:#fff;">
                </div>
            </div>

            <!-- OFFLOADED -->
            <div style="background:#FFF5F5;border:2px solid var(--delta-red);border-radius:6px;padding:14px;">
                <div style="font-size:12px;font-weight:800;color:var(--delta-red);margin-bottom:10px;">
                    &#9940; CARGA OFFLOADED</div>
                <div class="awb-field" style="margin-bottom:8px;">
                    <label style="font-size:10px;font-weight:700;color:var(--text-mid);">PCS OFFLOADED</label>
                    <input type="number" id="splitOffloadPcs" min="0" readonly
                        value="0"
                        style="width:100%;font-size:18px;font-weight:700;text-align:center;
                        border:2px solid var(--delta-red);border-radius:5px;padding:6px;
                        color:var(--delta-red);background:#fff;cursor:default;">
                </div>
                <div class="awb-field" style="margin-bottom:8px;">
                    <label style="font-size:10px;font-weight:700;color:var(--text-mid);">GROSS WEIGHT OFFLOADED (Kg) <span style="font-weight:400;color:var(--text-light);">auto</span></label>
                    <input type="number" id="splitOffloadGross" step="0.01" readonly
                        value="0"
                        style="width:100%;font-size:14px;font-weight:700;border:2px solid var(--delta-red);
                        border-radius:5px;padding:5px;color:var(--delta-red);background:#fff;cursor:default;">
                </div>
                <div class="awb-field">
                    <label style="font-size:10px;font-weight:700;color:var(--text-mid);">CHG WEIGHT OFFLOADED (Kg) <span style="font-weight:400;color:var(--text-light);">auto</span></label>
                    <input type="number" id="splitOffloadChg" step="0.01" readonly
                        value="0"
                        style="width:100%;font-size:14px;font-weight:700;border:2px solid var(--delta-red);
                        border-radius:5px;padding:5px;color:var(--delta-red);background:#fff;cursor:default;">
                </div>
            </div>
        </div>

        <!-- ULD y motivo -->
        <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:16px;">
            <div class="awb-field">
                <label style="font-size:11px;font-weight:700;color:var(--text-mid);">ULD afectado (opcional)</label>
                <input type="text" id="splitUldNumber" placeholder="ej: PMC12345DL"
                    class="awb-input" style="text-transform:uppercase;">
            </div>
            <div class="awb-field">
                <label style="font-size:11px;font-weight:700;color:var(--text-mid);">Motivo del Offload (opcional)</label>
                <input type="text" id="splitReason" placeholder="Weight, balance, restriction..." class="awb-input">
            </div>
        </div>

        <!-- Validation message -->
        <div id="splitMsg" style="min-height:18px;font-size:12px;font-weight:700;margin-bottom:12px;"></div>

        <!-- Confirm button -->
        <button onclick="mfConfirmSplit()"
            style="width:100%;background:var(--delta-red);color:#fff;border:none;border-radius:6px;
            padding:12px;font-size:14px;font-weight:700;cursor:pointer;">
            &#9992; Confirm Split (Split AWB)
        </button>`;

        // Initialize offload calculation
        splitCalcOffload();
    } catch(e) {
        document.getElementById('awbSplitContent').textContent = 'Connection error: ' + e.message;
    }
}

function splitCalcOffload() {
    const origPcs    = parseInt(document.getElementById('splitOrigPcs')?.value   || 0);
    const origGross  = parseFloat(document.getElementById('splitOrigGross')?.value || 0);
    const origChg    = parseFloat(document.getElementById('splitOrigChg')?.value  || 0);
    const loadedPcs  = parseInt(document.getElementById('splitLoadedPcs')?.value  || 0);
    const msg        = document.getElementById('splitMsg');

    const offloadPcs = origPcs - loadedPcs;
    if (offloadPcs < 0 || loadedPcs < 1) {
        if (msg) { msg.style.color = 'var(--delta-red)'; msg.textContent = 'Invalid loaded PCS.'; }
        return;
    }
    if (offloadPcs === 0) {
        if (msg) { msg.style.color = 'var(--delta-red)'; msg.textContent = 'Debe haber al menos 1 PCS offloaded para parcializar.'; }
    } else {
        if (msg) { msg.style.color = 'var(--delta-navy)'; msg.textContent = '✓ ' + loadedPcs + ' PCS embarcadas + ' + offloadPcs + ' PCS offloaded = ' + origPcs + ' PCS totales.'; }
    }

    // Proportional weights
    const ratio = origPcs > 0 ? loadedPcs / origPcs : 0;
    const offloadGross = parseFloat((origGross * (1 - ratio)).toFixed(2));
    const offloadChg   = parseFloat((origChg   * (1 - ratio)).toFixed(2));
    const loadedGross  = parseFloat((origGross - offloadGross).toFixed(2));
    const loadedChg    = parseFloat((origChg   - offloadChg).toFixed(2));

    const setVal = (id, v) => { const el = document.getElementById(id); if (el) el.value = v; };
    setVal('splitOffloadPcs',   offloadPcs);
    setVal('splitOffloadGross', offloadGross);
    setVal('splitOffloadChg',   offloadChg);
    setVal('splitLoadedGross',  loadedGross);
    setVal('splitLoadedChg',    loadedChg);
}

async function mfConfirmSplit() {
    const msg          = document.getElementById('splitMsg');
    const shipmentId   = parseInt(document.getElementById('splitShipmentId')?.value || 0);
    const origPcs      = parseInt(document.getElementById('splitOrigPcs')?.value     || 0);
    const loadedPcs    = parseInt(document.getElementById('splitLoadedPcs')?.value   || 0);
    const loadedGross  = parseFloat(document.getElementById('splitLoadedGross')?.value  || 0);
    const loadedChg    = parseFloat(document.getElementById('splitLoadedChg')?.value    || 0);
    const offloadPcs   = parseInt(document.getElementById('splitOffloadPcs')?.value  || 0);
    const offloadGross = parseFloat(document.getElementById('splitOffloadGross')?.value || 0);
    const offloadChg   = parseFloat(document.getElementById('splitOffloadChg')?.value   || 0);
    const uld          = document.getElementById('splitUldNumber')?.value.trim()  || '';
    const reason       = document.getElementById('splitReason')?.value.trim()     || '';

    if (loadedPcs + offloadPcs !== origPcs) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Error: PCS embarcadas + offloaded ≠ PCS originales.';
        return;
    }
    if (offloadPcs < 1) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Debe haber al menos 1 PCS offloaded.';
        return;
    }

    if (!confirm(`Confirm split?\n\nLoaded: ${loadedPcs} PCS — ${loadedGross} Kg\nOffloaded: ${offloadPcs} PCS — ${offloadGross} Kg\n\nA new AWB will be created for the ${offloadPcs} PCS not loaded.`)) return;

    msg.style.color = '#854F0B';
    msg.textContent = 'Processing...';

    try {
        const res = await apiFetch('/shipments/awb_split_api.php', {
            method: 'POST',
            body: JSON.stringify({
                shipment_id:    shipmentId,
                loaded_pieces:  loadedPcs,
                loaded_gross:   loadedGross,
                loaded_chg:     loadedChg,
                offload_pieces: offloadPcs,
                offload_gross:  offloadGross,
                offload_chg:    offloadChg,
                uld_number:     uld,
                reason:         reason,
                user:           currentUser?.email || 'agent',
            })
        });
        if (res.success) {
            msg.style.color = '#0F6E56';
            msg.textContent = '✓ ' + res.message;
            setTimeout(function(){
                document.getElementById('awbSplitModal')?.remove();
                if (currentView === 'flightdeparture' && typeof fdepLoad === 'function') fdepLoad();
                else mfLoad(); // Reload manifest
            }, 2000);
        } else {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = 'Error: ' + (res.message || 'No se pudo completar.');
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Connection error.';
    }
}

// ── Celda de Embarque: badge de estado + botones de acción ───
// Solo aplica a guías marcadas en este manifiesto (in_manifest=1).
// CONFIRMED -> puede confirmarse como Embarcada u OFFLOADED.
// LOADED/OFFLOADED -> ya resuelto, se puede revertir si fue un error.
function mfEmbarqueCell(r, isClosed, forceEdit) {
    const canEdit = forceEdit !== undefined ? forceEdit : mfCanEdit;
    isClosed = isClosed !== undefined ? isClosed : (mfManifestStatus === 'DEFINITIVE');
    if (!r.in_manifest || r.in_manifest == 0) {
        return '<span style="color:var(--text-light);font-size:11px;">—</span>';
    }
    const status = r.operational_status || 'CONFIRMED';

    // Config per status: { label, icon, bg, color, nextAction }
    const cfg = {
        'CONFIRMED':    { label:'CONFIRMADA',   icon:'●', bg:'#EFF6FF', color:'#1D4ED8', border:'#93C5FD' },
        'PRE_MANIFEST': { label:'PRE-MANIFEST', icon:'●', bg:'#F5F3FF', color:'#6D28D9', border:'#C4B5FD' },
        'LOADED':       { label:'EMBARCADA',    icon:'✓', bg:'#ECFDF5', color:'#065F46', border:'#6EE7B7' },
        'OFFLOADED':    { label:'NO EMBARCADA', icon:'✕', bg:'#FEF2F2', color:'#991B1B', border:'#FCA5A5' },
    };
    const c = cfg[status] || cfg['CONFIRMED'];

    if (!canEdit || isClosed) {
        return `<span style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;
            background:${c.bg};color:${c.color};border:1px solid ${c.border};
            border-radius:20px;font-size:10px;font-weight:700;white-space:nowrap;">
            ${c.icon} ${c.label}</span>`;
    }

    // Clickable badge that opens action menu
    const btnId = 'emb_' + r.id;
    let menuItems = '';
    if (status === 'CONFIRMED' || status === 'PRE_MANIFEST') {
        // CONFIRMADO → opciones: EMBARCADA, NO EMBARCADA
        menuItems =
            '<div onclick="mfChangeEmbarque(' + r.id + ',\'LOADED\');mfCloseEmbarqueMenu()" class="emb-menu-item" style="color:#065F46;">✓ &nbsp;Embarcada</div>'
            + '<div onclick="mfChangeEmbarque(' + r.id + ',\'OFFLOADED\');mfCloseEmbarqueMenu()" class="emb-menu-item" style="color:#991B1B;">✕ &nbsp;No Embarcada</div>';
    } else if (status === 'LOADED') {
        // EMBARCADO → opciones: NO EMBARCADA, CONFIRMADO
        menuItems =
            '<div onclick="mfChangeEmbarque(' + r.id + ',\'OFFLOADED\');mfCloseEmbarqueMenu()" class="emb-menu-item" style="color:#991B1B;">✕ &nbsp;No Embarcada</div>'
            + '<div onclick="mfChangeEmbarque(' + r.id + ',\'CONFIRMED\');mfCloseEmbarqueMenu()" class="emb-menu-item" style="color:#374151;">↩ &nbsp;Confirmado</div>';
    } else if (status === 'OFFLOADED') {
        // NO EMBARCADO → opciones: EMBARCADA, CONFIRMADO
        menuItems =
            '<div onclick="mfChangeEmbarque(' + r.id + ',\'LOADED\');mfCloseEmbarqueMenu()" class="emb-menu-item" style="color:#065F46;">✓ &nbsp;Embarcada</div>'
            + '<div onclick="mfChangeEmbarque(' + r.id + ',\'CONFIRMED\');mfCloseEmbarqueMenu()" class="emb-menu-item" style="color:#374151;">↩ &nbsp;Confirmado</div>';
    }

    return `<div style="position:relative;display:inline-block;">
        <button id="${btnId}" onclick="mfToggleEmbarqueMenu('${btnId}')"
            style="display:inline-flex;align-items:center;gap:4px;padding:3px 8px;
            background:${c.bg};color:${c.color};border:1px solid ${c.border};
            border-radius:20px;font-size:10px;font-weight:700;white-space:nowrap;
            cursor:pointer;outline:none;">
            ${c.icon} ${c.label} <span style="font-size:8px;opacity:.6;">▼</span>
        </button>
        <div id="${btnId}_menu" style="display:none;position:absolute;top:100%;left:0;
            background:#fff;border:1px solid var(--border);border-radius:6px;
            box-shadow:0 4px 16px rgba(0,0,0,.15);z-index:500;min-width:140px;
            overflow:hidden;margin-top:2px;">
            ${menuItems}
        </div>
    </div>`;
}

function mfToggleEmbarqueMenu(btnId) {
    // Close all other menus first
    document.querySelectorAll('[id$="_menu"]').forEach(function(m) {
        if (m.id !== btnId + '_menu') m.style.display = 'none';
    });
    const menu = document.getElementById(btnId + '_menu');
    if (menu) menu.style.display = menu.style.display === 'none' ? 'block' : 'none';
}

function mfCloseEmbarqueMenu() {
    document.querySelectorAll('[id$="_menu"]').forEach(function(m){ m.style.display = 'none'; });
}

// Close menu when clicking outside
document.addEventListener('click', function(e) {
    if (!e.target.closest('[id^="emb_"]') && !e.target.closest('.emb-menu-item')) {
        mfCloseEmbarqueMenu();
    }
});

async function mfChangeEmbarque(shipmentId, newStatus) {
    try {
        const res = await apiFetch('/shipments/shipment_status_api.php', {
            method: 'POST',
            body: JSON.stringify({
                shipment_id: shipmentId,
                new_status: newStatus,
                reason: 'Load confirmation from Flight Departure',
                user_id: currentUser?.id || null,
            })
        });
        if (res.success) {
            if (currentView === 'flightdeparture' && typeof fdepLoad === 'function') await fdepLoad();
            else await mfLoad();
        } else {
            alert(res.message || 'No se pudo cambiar el estado.');
        }
    } catch(e) {
        alert('Connection error.');
    }
}

function mfEditableCell(id, field, rawValue, displayHtml) {
    if (!mfCanEdit) return displayHtml;
    // Todas las celdas editables (ULD Type, Skids, Remarks, DAM, Date DAM) se
    // activan con un solo clic (Correcciones Pre-Manifest #3 / #7) — ya no
    // requieren doble clic, agilizando la carga de datos.
    return `<div class="mf-edit-cell" data-id="${id}" data-field="${field}" data-value="${esc(String(rawValue))}" onclick="mfStartEdit(this)" title="Clic para editar">${displayHtml}</div>`;
}

function mfStartEdit(cell) {
    if (!mfCanEdit || cell.querySelector('input,select')) return;
    const field = cell.dataset.field, id = cell.dataset.id, value = cell.dataset.value;
    const cfg = MF_EDITABLE_COLS[field];
    if (!cfg) return;

    let input;
    if (cfg.type === 'select') {
        input = document.createElement('select');
        if (cfg.allowEmpty) {
            const o = document.createElement('option'); o.value=''; o.textContent='—';
            if (!value) o.selected = true;
            input.appendChild(o);
        }
        cfg.options.forEach(opt => {
            const o = document.createElement('option');
            // Support both simple strings and [value, label] pairs
            const val   = Array.isArray(opt) ? opt[0] : opt;
            const label = Array.isArray(opt) ? opt[1] : opt;
            o.value = val; o.textContent = label;
            if (String(val) === String(value)) o.selected = true;
            input.appendChild(o);
        });
    } else {
        input = document.createElement('input');
        input.type = cfg.type === 'number' ? 'number' : cfg.type === 'date' ? 'date' : 'text';
        if (cfg.maxlength) input.maxLength = cfg.maxlength;
        input.value = value;
        if (cfg.integer) input.addEventListener('input', () => input.value = input.value.replace(/[^0-9]/g,''));
        if (field === 'remark') {
            input.style.textTransform = 'uppercase';
            input.addEventListener('input', () => { input.value = input.value.toUpperCase(); });
        }
    }
    input.style.cssText = 'width:100%;height:30px;border:1px solid var(--delta-sky);border-radius:3px;padding:2px 6px;font-size:12px;outline:none;box-sizing:border-box;';

    const finish = (commit) => {
        if (commit) {
            let newVal = input.value;
            if (field === 'remark') newVal = newVal.toUpperCase();
            cell.dataset.value = newVal;
            const r = mfRows.find(x => x.id == id);
            if (r) r[field] = newVal;
            cell.innerHTML = esc(newVal) || '<span style="color:var(--text-light);">—</span>';
            // AUTO-SAVE: guardar campo en el servidor inmediatamente
            mfAutoSaveField(parseInt(id), field, newVal);
        } else {
            cell.innerHTML = esc(cell.dataset.value) || '<span style="color:var(--text-light);">—</span>';
        }
    };
    input.addEventListener('keydown', e => { if (e.key==='Enter') finish(true); else if (e.key==='Escape') finish(false); });
    input.addEventListener('blur', () => finish(true));
    cell.innerHTML = ''; cell.appendChild(input); input.focus();
    // ULD Type: intenta abrir el desplegable nativo de inmediato (comportamiento
    // combobox). showPicker() no está soportado en todos los navegadores —
    // si no existe, el usuario igual puede abrirlo con un clic/flecha normal.
    if (field === 'uld_type' && typeof input.showPicker === 'function') {
        try { input.showPicker(); } catch(e) {}
    }
}

function mfToggleAll(checkbox) {
    // .mf-check:not([disabled]) excluye las guías de manifiesto cerrado
    document.querySelectorAll('.mf-check:not([disabled])').forEach(c => {
        if (c.checked !== checkbox.checked) {
            c.checked = checkbox.checked;
            mfAutoSaveCheck(parseInt(c.dataset.id), checkbox.checked);
        }
    });
    mfCalcTotals();
}

// ── Auto-Save: campo individual de shipment_manifest / shipments ──
let _mfSaveTimer = null;
async function mfAutoSaveField(shipmentId, field, value) {
    const mn = document.getElementById('mfManifestNumber')?.value || '';
    if (!mn || !/^\d{4}-\d+$/.test(mn)) return; // sin manifiesto válido no hay nada que guardar

    // Mostrar indicador discreto
    mfShowSaveIndicator('saving');

    clearTimeout(_mfSaveTimer);
    _mfSaveTimer = setTimeout(async () => {
        try {
            const res = await apiFetch('/shipments/manifest_field_api.php', {
                method: 'POST',
                body: JSON.stringify({
                    shipment_id:     shipmentId,
                    manifest_number: mn,
                    field:           field,
                    value:           value !== '' ? value : null,
                    user_id:         currentUser?.id || null,
                })
            });
            mfShowSaveIndicator(res.success ? 'ok' : 'error');
        } catch(e) {
            mfShowSaveIndicator('error');
        }
    }, 300);
}

// ── Auto-Save: campo global del manifiesto (wh_exit_time / etd) ──
async function mfAutoSaveGlobal(field, value) {
    const mn = document.getElementById('mfManifestNumber')?.value || '';
    if (!mn || !/^\d{4}-\d+$/.test(mn)) return;

    mfShowSaveIndicator('saving');
    try {
        const res = await apiFetch('/shipments/manifest_field_api.php', {
            method: 'POST',
            body: JSON.stringify({
                manifest_number: mn,
                field:           field,
                value:           value || null,
                user_id:         currentUser?.id || null,
            })
        });
        mfShowSaveIndicator(res.success ? 'ok' : 'error');
    } catch(e) {
        mfShowSaveIndicator('error');
    }
}

// ── Auto-Save: checkbox in_manifest ──────────────────────────────
async function mfAutoSaveCheck(shipmentId, checked) {
    const mn = document.getElementById('mfManifestNumber')?.value || '';
    if (!mn || !/^\d{4}-\d+$/.test(mn)) {
        // Sin manifiesto válido no podemos guardar — mostrar aviso
        mfShowSaveIndicator('error');
        const msg = document.getElementById('mfMsg');
        if (msg) { msg.style.color = 'var(--delta-red)'; msg.textContent = 'Enter a Manifest No. before marking AWBs.'; }
        // Revertir el checkbox
        const chk = document.querySelector('.mf-check[data-id="'+shipmentId+'"]');
        if (chk) chk.checked = !checked;
        return;
    }

    mfShowSaveIndicator('saving');

    const flt     = document.getElementById('mfFlight').value;
    const date    = document.getElementById('mfDate').value;
    const station = document.getElementById('mfStation')?.value || 'LIM';
    const whe     = document.getElementById('mfWhExitTime')?.value || null;
    const etd     = document.getElementById('mfEtd')?.value || null;

    // Leer warehouse actual del select de esa fila
    const whSel = document.querySelector('.mf-warehouse[data-id="'+shipmentId+'"]');
    const warehouse = whSel ? (whSel.value || null) : null;

    // Leer uld_type del cell
    const uldCell  = document.querySelector('.mf-edit-cell[data-id="'+shipmentId+'"][data-field="uld_type"]');
    const uldInput = uldCell ? uldCell.querySelector('select,input') : null;
    const uld_type = uldInput ? uldInput.value : (uldCell ? uldCell.dataset.value || '' : '');

    const skidsCell  = document.querySelector('.mf-edit-cell[data-id="'+shipmentId+'"][data-field="skids_qty"]');
    const skids_raw  = skidsCell ? (skidsCell.querySelector('input')?.value || skidsCell.dataset.value || '') : '';
    const skids_qty  = skids_raw !== '' ? parseInt(skids_raw) : null;

    const remCell = document.querySelector('.mf-edit-cell[data-id="'+shipmentId+'"][data-field="remark"]');
    const remark  = (remCell ? (remCell.querySelector('input')?.value || remCell.dataset.value || '') : '').toUpperCase();

    const damCell = document.querySelector('.mf-edit-cell[data-id="'+shipmentId+'"][data-field="dam"]');
    const dam     = damCell ? (damCell.querySelector('input')?.value || damCell.dataset.value || '') : '';

    const ddamCell = document.querySelector('.mf-edit-cell[data-id="'+shipmentId+'"][data-field="date_dam"]');
    const date_dam = ddamCell ? (ddamCell.querySelector('input')?.value || ddamCell.dataset.value || '') : '';

    try {
        const res = await apiFetch('/shipments/shipment_manifest_api.php', {
            method: 'POST',
            body: JSON.stringify({
                flt_number:      flt,
                flt_date:        date,
                station:         station,
                manifest_number: mn,
                wh_exit_time:    whe,
                etd:             etd,
                user_id:         currentUser?.id || null,
                items: [{
                    id:          shipmentId,
                    in_manifest: checked ? 1 : 0,
                    warehouse:   warehouse,
                    uld_type:    uld_type || null,
                    skids_qty:   skids_qty,
                    remark:      remark || null,
                    dam:         dam || null,
                    date_dam:    date_dam || null,
                }]
            })
        });

        if (res.success) {
            // Actualizar mfRows en memoria
            const r = mfRows.find(x => x.id == shipmentId);
            if (r) r.in_manifest = checked ? 1 : 0;
            mfShowSaveIndicator('ok');
            mfCalcTotals();
            mfUpdateDistroBar();
            // Actualizar solo la celda de embarque de esa fila (sin re-renderizar todo)
            const row = document.querySelector('tr[data-id="'+shipmentId+'"]');
            if (row && r) {
                const lastCell = row.querySelector('td:last-child');
                if (lastCell) lastCell.innerHTML = mfEmbarqueCell(r, true);
            }
        } else {
            mfShowSaveIndicator('error');
            // Revertir checkbox
            const chk = document.querySelector('.mf-check[data-id="'+shipmentId+'"]');
            if (chk) chk.checked = !checked;
        }
    } catch(e) {
        mfShowSaveIndicator('error');
        const chk = document.querySelector('.mf-check[data-id="'+shipmentId+'"]');
        if (chk) chk.checked = !checked;
    }
}
function mfShowSaveIndicator(state) {
    let ind = document.getElementById('mfSaveIndicator');
    if (!ind) {
        ind = document.createElement('span');
        ind.id = 'mfSaveIndicator';
        ind.style.cssText = 'font-size:11px;font-weight:600;margin-left:8px;transition:opacity .3s;';
        const msg = document.getElementById('mfMsg');
        if (msg) msg.parentNode.insertBefore(ind, msg.nextSibling);
    }
    if (state === 'saving') {
        ind.style.color = 'var(--text-mid)';
        ind.textContent = '⏳ Saving...';
        ind.style.opacity = '1';
    } else if (state === 'ok') {
        ind.style.color = '#2ECC71';
        ind.textContent = '✓ Guardado';
        ind.style.opacity = '1';
        setTimeout(() => { ind.style.opacity = '0'; }, 2000);
    } else {
        ind.style.color = 'var(--delta-red)';
        ind.textContent = '✗ Error saving';
        ind.style.opacity = '1';
    }
}

function mfCalcTotals() {
    let pieces = 0, gross = 0, awbs = 0;
    document.querySelectorAll('.mf-check').forEach(c => {
        if (!c.checked) return;
        const r = mfRows.find(x => x.id == c.dataset.id);
        if (!r) return;
        awbs++;
        pieces += parseInt(r.pieces)||0;
        gross  += parseFloat(r.gross_weight)||0;
    });
    // Actualizar badges superiores
    document.getElementById('mfBadgeAwbs').textContent    = awbs;
    document.getElementById('mfBadgePiezas').textContent  = fmtWgt0(pieces);
    document.getElementById('mfBadgeGross').textContent   = fmtWgt0(gross);
    // Actualizar totales del tfoot
    document.getElementById('mfTotPieces').textContent = fmtWgt0(pieces);
    document.getElementById('mfTotGross').textContent  = fmtWgt0(gross);
    mfUpdateDistroBar();
}

function mfUpdateDistroBar() {
    const bar  = document.getElementById('mfDistroBar');
    const cont = document.getElementById('mfDistroButtons');
    const isDefinitive = mfManifestStatus === 'DEFINITIVE';

    // 3 hojas fijas segun regla de negocio:
    // 1. TALMA + COL/FRE   2. TALMA + DRY   3. FRIO AEREO + COL/FRE
    const sheets = { TALMA_col_fre: [], TALMA_other: [], FRIO_col_fre: [] };

    mfRows.forEach(r => {
        const chk = document.querySelector('.mf-check[data-id="'+r.id+'"]');
        const isChecked = chk ? chk.checked : (r.in_manifest==1);
        const whSel = document.querySelector('.mf-warehouse[data-id="'+r.id+'"]');
        const wh = (whSel ? whSel.value : r.warehouse) || '';
        if (!isChecked && !isDefinitive) return;
        const prod = (r.product_code || '').toUpperCase();
        const isColFre = prod === 'COL' || prod === 'FRE';
        if (wh === 'TALMA') {
            if (isColFre) sheets.TALMA_col_fre.push(r);
            else          sheets.TALMA_other.push(r);
        } else if (wh === 'FRIO_AEREO') {
            if (isColFre) sheets.FRIO_col_fre.push(r);
        }
    });

    const hasAny = sheets.TALMA_col_fre.length || sheets.TALMA_other.length || sheets.FRIO_col_fre.length;
    if (!hasAny && !isDefinitive) { bar.style.display='none'; cont.innerHTML=''; return; }

    let html = '<div style="display:flex;gap:10px;flex-wrap:wrap;align-items:center;">';

    if (sheets.TALMA_col_fre.length)
        html += '<button class="action-btn-sm" style="border-color:#0066B2;color:#0066B2;"'
              + ' onclick="mfShowDistroMenu(\'TALMA\',\'col_fre\')" title="TALMA - COL/FRE">'
              + '&#128196; TALMA &middot; COL/FRE (' + sheets.TALMA_col_fre.length + ')</button>';

    if (sheets.TALMA_other.length)
        html += '<button class="action-btn-sm" style="border-color:#854F0B;color:#854F0B;"'
              + ' onclick="mfShowDistroMenu(\'TALMA\',\'other\')" title="TALMA - Carga DRY">'
              + '&#128196; TALMA &middot; DRY (' + sheets.TALMA_other.length + ')</button>';

    if (sheets.FRIO_col_fre.length)
        html += '<button class="action-btn-sm" style="border-color:#0F6E56;color:#0F6E56;"'
              + ' onclick="mfShowDistroMenu(\'FRIO_AEREO\',\'col_fre\')" title="FRIO AEREO - COL/FRE">'
              + '&#128196; FR&Iacute;O A&Eacute;REO &middot; COL/FRE (' + sheets.FRIO_col_fre.length + ')</button>';

    if (isDefinitive)
        html += '<button onclick="mfSendACS()" style="margin-left:auto;background:#002244;border:none;'
              + 'color:#fff;font-size:12px;font-weight:700;cursor:pointer;border-radius:5px;'
              + 'padding:0 14px;height:32px;display:flex;align-items:center;gap:6px;">'
              + '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">'
              + '<line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'
              + 'Send Weights &rarr; ACS</button>';

    html += '</div>';
    cont.innerHTML = html;
    bar.style.display = 'block';
}

// ══════════════════════════════════════════════════════════════
//  MODAL UNIFICADO DE REPORTES — envío / vista previa / impresión /
//  exportación PDF. Reemplaza los modales antes duplicados de Hoja
//  de Distribución, Outbound Report y Departure Notice. Reutilizable
//  para cualquier reporte futuro del sistema: basta con invocar
//  reportModalOpen(cfg) con la config propia de cada reporte.
//
//  cfg = {
//    reportType:     string  — clave usada para filtrar notification_config
//                              (ignorada si recipientMode = 'freight_forwarder')
//    title:          string  — título mostrado en la cabecera
//    icon:           string  — emoji/ícono junto al título
//    contextLine:    string  — subtítulo (vuelo, fecha, almacén, agente, etc.)
//    subject:        string  — asunto de correo por defecto (editable)
//    station:        string  — estación, usada al filtrar destinatarios
//    recipientMode:  'notification_config' | 'freight_forwarder'
//    agentId:        number  — (solo freight_forwarder) id del agente
//    agentName:      string  — (solo freight_forwarder) nombre del agente
//    buildHtml:      function → string | Promise<string> — documento completo
//                              (se usa para vista previa, impresión, PDF y cuerpo del email)
//    onSend:         async function({to, subject, html, pdfBase64}) → {success, message}
//    onSent:         function() — callback opcional tras un envío exitoso
//    filenamePrefix: string  — nombre de archivo para "Descargar PDF"
//  }
// ══════════════════════════════════════════════════════════════
let _reportModalCfg = null;

async function reportModalOpen(cfg) {
    document.getElementById('reportModal')?.remove();
    _reportModalCfg = cfg;

    const modal = document.createElement('div');
    modal.id = 'reportModal';
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.55);z-index:9500;display:flex;align-items:center;justify-content:center;';
    modal.innerHTML = `
        <div style="background:#fff;border-radius:8px;width:640px;max-width:96vw;max-height:92vh;overflow-y:auto;
             box-shadow:0 8px 40px rgba(0,0,0,.3);">
            <div style="background:var(--delta-navy);color:#fff;padding:14px 18px;
                 display:flex;align-items:center;justify-content:space-between;">
                <div>
                    <div style="font-size:14px;font-weight:800;">${cfg.icon || '📄'} ${esc(cfg.title || 'Reporte')}</div>
                    <div style="font-size:11px;opacity:.8;margin-top:2px;">${esc(cfg.contextLine || '')}</div>
                </div>
                <button onclick="reportModalClose()"
                    style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer;">&#x2715;</button>
            </div>
            <div style="padding:18px 20px;">
                <div style="margin-bottom:12px;">
                    <label style="font-size:10px;font-weight:700;text-transform:uppercase;color:var(--text-mid);display:block;margin-bottom:3px;">Asunto</label>
                    <input type="text" id="reportModalSubject" value="${esc(cfg.subject || '')}"
                        style="width:100%;border:1px solid var(--border);border-radius:5px;padding:6px 8px;font-size:12px;">
                </div>

                <div style="margin-bottom:12px;">
                    <label style="font-size:10px;font-weight:700;text-transform:uppercase;color:var(--text-mid);display:block;margin-bottom:6px;">Destinatarios</label>
                    <div id="reportModalRecipients" style="max-height:150px;overflow-y:auto;border:1px solid var(--border);
                         border-radius:6px;padding:8px 10px;background:#F8FAFC;">
                        <div style="font-size:12px;color:#9CA3AF;">Loading recipients...</div>
                    </div>
                </div>

                <div style="margin-bottom:14px;">
                    <label style="font-size:10px;font-weight:700;text-transform:uppercase;color:var(--text-mid);display:block;margin-bottom:3px;">Additional Recipients</label>
                    <input type="text" id="reportModalExtra" placeholder="correo1@empresa.com, correo2@empresa.com"
                        style="width:100%;border:1px solid var(--border);border-radius:5px;padding:6px 8px;font-size:12px;"
                        oninput="reportModalClearExtraError()">
                    <div id="reportModalExtraErr" style="font-size:11px;color:var(--delta-red);margin-top:3px;min-height:14px;"></div>
                </div>

                <div id="reportModalMsg" style="min-height:18px;font-size:12px;font-weight:600;margin-bottom:10px;"></div>
                <div style="display:flex;gap:8px;flex-wrap:wrap;">
                    <button onclick="reportModalSend()" class="btn-primary" style="flex:2;min-width:140px;">&#9993; Send Email</button>
                    <button onclick="reportModalPreviewWindow()" class="btn-secondary" style="flex:1;">&#128065; Preview</button>
                    <button onclick="reportModalPrint()" class="btn-secondary" style="flex:1;">&#128424; Print</button>
                    <button onclick="reportModalPdf()" class="btn-secondary" style="flex:1;">&#8595; PDF</button>
                    <button onclick="reportModalClose()" class="btn-secondary" style="flex:1;">Cancel</button>
                </div>
            </div>
        </div>`;
    document.body.appendChild(modal);
    modal.addEventListener('click', function(e) { if (e.target === modal) reportModalClose(); });

    reportModalLoadRecipients();
}

function reportModalClose() {
    document.getElementById('reportModal')?.remove();
    _reportModalCfg = null;
}

function reportModalClearExtraError() {
    const errEl = document.getElementById('reportModalExtraErr');
    if (errEl) errEl.textContent = '';
}

// ── Destinatarios automáticos según el origen del reporte (Caso 1:
// Notificaciones Email / Caso 2: Freight Forwarders — ver documento de
// requerimientos) ──
async function reportModalLoadRecipients() {
    const cfg  = _reportModalCfg;
    const cont = document.getElementById('reportModalRecipients');
    if (!cfg || !cont) return;

    let recipients = [];
    try {
        if (cfg.recipientMode === 'freight_forwarder') {
            const q = cfg.agentId
                ? '/catalog/catalog_api.php?table=freight_forwarder&id=' + cfg.agentId
                : '/catalog/catalog_api.php?table=freight_forwarder&q=' + encodeURIComponent(cfg.agentName || '') + '&station=' + (cfg.station || 'LIM');
            const res = await apiFetch(q);
            if (res.success && res.data) {
                const list = Array.isArray(res.data) ? res.data : [res.data];
                recipients = list
                    .filter(function(r) { return r.contact_email; })
                    .map(function(r) { return { name: r.legal_name || cfg.agentName || 'Freight Forwarder', email: r.contact_email }; });
            }
        } else {
            const res = await apiFetch('/catalog/catalog_api.php?table=notification_config&show_inactive=0');
            if (res.success) {
                recipients = (res.data || [])
                    .filter(function(r) { return r.report_type === cfg.reportType && r.station === (cfg.station || 'LIM'); })
                    .map(function(r) { return { name: r.recipient_name, email: r.recipient_email }; });
            }
        }
    } catch(e) {}

    // El modal pudo haberse cerrado (o cambiado de config) mientras esto cargaba
    if (_reportModalCfg !== cfg || !document.getElementById('reportModalRecipients')) return;

    if (!recipients.length) {
        cont.innerHTML = '<div style="font-size:12px;color:#9CA3AF;padding:4px 0;">'
            + (cfg.recipientMode === 'freight_forwarder'
                ? 'El Freight Forwarder no tiene email registrado.'
                : 'No hay destinatarios configurados para este reporte. '
                  + '<a href="#" onclick="switchView(\'catalog\');reportModalClose();return false;" style="color:var(--delta-sky);">Configurar en Catalog &rarr; Notificaciones Email</a>')
            + '</div>';
        return;
    }
    cont.innerHTML = recipients.map(function(r) {
        return '<label style="display:flex;align-items:center;gap:8px;padding:5px 0;cursor:pointer;">'
            + '<input type="checkbox" class="report-modal-recip" value="' + esc(r.email) + '" checked>'
            + '<span style="font-size:12px;"><strong>' + esc(r.name) + '</strong> '
            + '<span style="color:#6B7280;">&lt;' + esc(r.email) + '&gt;</span></span>'
            + '</label>';
    }).join('');
}

// Junta los checkboxes marcados + "Additional Recipients" (separados por
// coma, validados). Devuelve null si hay algún correo con formato inválido
// (y ya deja el mensaje de error visible).
function reportModalCollectRecipients() {
    const checked = Array.from(document.querySelectorAll('.report-modal-recip:checked')).map(function(c) { return c.value; });
    const extraRaw = (document.getElementById('reportModalExtra')?.value || '').trim();
    const errEl = document.getElementById('reportModalExtraErr');
    if (errEl) errEl.textContent = '';

    if (extraRaw) {
        const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        const parts = extraRaw.split(',').map(function(s) { return s.trim(); }).filter(Boolean);
        const invalid = parts.filter(function(p) { return !emailRe.test(p); });
        if (invalid.length) {
            if (errEl) errEl.textContent = 'Invalid email: ' + invalid.join(', ');
            return null;
        }
        parts.forEach(function(p) { if (!checked.includes(p)) checked.push(p); });
    }
    return checked;
}

async function reportModalSend() {
    const cfg = _reportModalCfg;
    const msg = document.getElementById('reportModalMsg');
    if (!cfg || !msg) return;

    const recipients = reportModalCollectRecipients();
    if (recipients === null) return; // formato de correo inválido — el error ya se muestra
    if (!recipients.length) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Select or enter at least one recipient.';
        return;
    }

    msg.style.color = '#854F0B';
    msg.textContent = 'Generando documento...';
    const html    = await Promise.resolve(cfg.buildHtml());
    const subject = document.getElementById('reportModalSubject')?.value || cfg.subject || '';

    let pdfBase64 = null;
    try {
        pdfBase64 = await mfGeneratePdfFromHtml(html, cfg.reportType || 'REPORT', cfg.filenamePrefix || '');
    } catch(e) { console.warn('PDF generation failed, sending HTML only:', e); }

    msg.textContent = 'Sending to ' + recipients.length + ' recipient(s)...';
    try {
        const res = await cfg.onSend({ to: recipients, subject, html, pdfBase64 });
        if (res && res.success) {
            msg.style.color = '#0F6E56';
            msg.textContent = '✓ ' + (res.message || 'Sent successfully.');
            if (typeof cfg.onSent === 'function') cfg.onSent();
            setTimeout(reportModalClose, 2200);
        } else {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = 'Error: ' + ((res && res.message) || 'Could not send.');
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Connection error.';
    }
}

async function reportModalPreviewWindow() {
    const cfg = _reportModalCfg;
    if (!cfg) return;
    const html = await Promise.resolve(cfg.buildHtml());
    const win = window.open('', '_blank');
    win.document.write(html);
    win.document.close();
}

async function reportModalPrint() {
    const cfg = _reportModalCfg;
    if (!cfg) return;
    const html = await Promise.resolve(cfg.buildHtml());
    const win = window.open('', '_blank');
    win.document.write(html.replace('</body>', '<script>window.onload=function(){window.print();};<\/script></body>'));
    win.document.close();
}

async function reportModalPdf() {
    const cfg = _reportModalCfg;
    const msg = document.getElementById('reportModalMsg');
    if (!cfg) return;
    if (msg) { msg.style.color = '#854F0B'; msg.textContent = 'Generando PDF...'; }
    try {
        const html = await Promise.resolve(cfg.buildHtml());
        const pdfBase64 = await mfGeneratePdfFromHtml(html, cfg.reportType || 'REPORT', cfg.filenamePrefix || '');
        if (pdfBase64) {
            const link = document.createElement('a');
            link.href = 'data:application/pdf;base64,' + pdfBase64;
            link.download = (cfg.filenamePrefix || 'Reporte') + '.pdf';
            link.click();
            if (msg) { msg.style.color = '#0F6E56'; msg.textContent = '✓ PDF generado.'; }
        }
    } catch(e) {
        if (msg) { msg.style.color = 'var(--delta-red)'; msg.textContent = 'Error generando PDF.'; }
    }
}

// ── Menú de opciones para Hoja de Distribución ───────────────────
async function mfShowDistroMenu(warehouse, group) {
    const flt        = document.getElementById('mfFlight').value || '';
    const date       = document.getElementById('mfDate').value   || '';
    const mn         = document.getElementById('mfManifestNumber').value || '';
    const station    = document.getElementById('mfStation')?.value || 'LIM';
    const whLabel    = warehouse === 'TALMA' ? 'TALMA' : 'FRÍO AÉREO';
    const grpLabel   = group === 'col_fre' ? 'COL/FRE' : 'DRY';
    const reportType = warehouse === 'TALMA' ? 'DISTRO_TALMA' : 'DISTRO_FRIO_AEREO';

    reportModalOpen({
        reportType:  reportType,
        title:       'Distribution Sheet',
        icon:        '📄',
        contextLine: whLabel + ' — ' + grpLabel + ' • ' + flt + ' • ' + fmtDate(date),
        subject:     'Distribution Sheet | ' + whLabel + ' | ' + flt + ' | ' + fmtDate(date),
        station:     station,
        recipientMode: 'notification_config',
        filenamePrefix: 'Hoja_Distribucion_' + warehouse + '_' + grpLabel.replace(/[^A-Z0-9]/gi, ''),
        buildHtml:   function() { return mfBuildDistroHtml(warehouse, group); },
        onSend: function(payload) {
            return apiFetch('/shipments/distro_send_api.php', {
                method: 'POST',
                body: JSON.stringify({
                    to:              payload.to,
                    flt_number:      flt,
                    flt_date:        date,
                    manifest_number: mn,
                    warehouse:       warehouse,
                    group:           group,
                    station:         station,
                    distro_html:     payload.html,
                    pdf_base64:      payload.pdfBase64,
                    subject:         payload.subject,
                })
            });
        },
    });
}

// ── Genera PDF base64 desde HTML usando html2canvas + jsPDF ───────
async function mfGeneratePdfFromHtml(html, warehouse, group) {

    // Inject html into a hidden div (same document context, better rendering)
    const container = document.createElement('div');
    container.style.cssText = 'position:fixed;left:-9999px;top:0;width:1122px;background:#fff;z-index:-1;';
    document.body.appendChild(container);
    container.innerHTML = html;

    // Wait for rendering
    await new Promise(r => setTimeout(r, 1000));

    // Find the actual table element to get its real width
    const table = container.querySelector('table.data, table.dws, table');
    if (table) {
        // Force table to fill container
        table.style.width = '100%';
    }

    // Load html2canvas
    if (typeof html2canvas === 'undefined') {
        await new Promise((resolve, reject) => {
            const s = document.createElement('script');
            s.src = 'https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js';
            s.onload = resolve; s.onerror = reject;
            document.head.appendChild(s);
        });
    }

    // Load jsPDF
    if (typeof window.jspdf === 'undefined') {
        await new Promise((resolve, reject) => {
            const s = document.createElement('script');
            s.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js';
            s.onload = resolve; s.onerror = reject;
            document.head.appendChild(s);
        });
    }

    // Capture the full container
    const canvas = await html2canvas(container, {
        scale: 2,
        useCORS: true,
        allowTaint: true,
        backgroundColor: '#ffffff',
        width: 1122,
        windowWidth: 1122,
        scrollX: 0,
        scrollY: 0,
        logging: false,
    });

    document.body.removeChild(container);

    const { jsPDF } = window.jspdf;
    // A4 landscape: 297mm x 210mm
    const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });

    const pageW = 297;
    const pageH = 210;
    const margin = 5;
    const usableW = pageW - margin * 2;
    const usableH = pageH - margin * 2;

    // Scale canvas to fit page width
    const canvasW = canvas.width;
    const canvasH = canvas.height;
    const ratio   = usableW / canvasW;
    const imgH    = canvasH * ratio;

    // Multi-page support
    let yOffset = 0;
    let page = 0;
    while (yOffset < canvasH) {
        if (page > 0) pdf.addPage();
        // Crop canvas slice for this page
        const sliceH = Math.min(usableH / ratio, canvasH - yOffset);
        const sliceCanvas = document.createElement('canvas');
        sliceCanvas.width  = canvasW;
        sliceCanvas.height = sliceH;
        const ctx = sliceCanvas.getContext('2d');
        ctx.drawImage(canvas, 0, yOffset, canvasW, sliceH, 0, 0, canvasW, sliceH);
        const imgData = sliceCanvas.toDataURL('image/jpeg', 0.95);
        pdf.addImage(imgData, 'JPEG', margin, margin, usableW, sliceH * ratio);
        yOffset += sliceH;
        page++;
    }

    return pdf.output('datauristring').split(',')[1];
}

// ── Genera HTML de la Hoja de Distribución sin abrir ventana ─────
// Usada por mfDistroSendEmail para adjuntar y mostrar en el email
function mfBuildDistroHtml(warehouse, group) {
    const flt      = document.getElementById('mfFlight').value;
    const date     = document.getElementById('mfDate').value;
    const mn       = document.getElementById('mfManifestNumber').value;
    const etd      = document.getElementById('mfEtd')?.value || '--:--';
    const whe      = document.getElementById('mfWhExitTime')?.value || '--:--';
    const isColFre = group === 'col_fre';
    const whLabel  = warehouse === 'TALMA' ? 'TALMA' : 'FR\u00cdO A\u00c9REO';
    const whColor  = warehouse === 'TALMA' ? '#0066B2' : '#0F6E56';

    const rows = mfRows.filter(r => {
        const chk = document.querySelector('.mf-check[data-id="'+r.id+'"]');
        const isChecked = chk ? chk.checked : (r.in_manifest==1);
        const whSel = document.querySelector('.mf-warehouse[data-id="'+r.id+'"]');
        const wh = whSel ? whSel.value : r.warehouse;
        if (!isChecked || wh !== warehouse) return false;
        const prod = (r.product_code || '').toUpperCase();
        return isColFre ? (prod === 'COL' || prod === 'FRE') : (prod !== 'COL' && prod !== 'FRE');
    });

    if (!rows.length) return null;

    // Reuse mfExportDistro logic but capture HTML instead of opening window
    // Temporarily intercept window.open
    const origOpen = window.open;
    let capturedHtml = null;
    window.open = function() {
        return {
            document: {
                write: function(h) { capturedHtml = h; },
                close: function() {}
            }
        };
    };
    mfExportDistro(warehouse, group);
    window.open = origOpen;
    return capturedHtml;
}

function mfExportDistro(warehouse, group) {
    const flt      = document.getElementById('mfFlight').value;
    const date     = document.getElementById('mfDate').value;
    const mn       = document.getElementById('mfManifestNumber').value;
    const etd      = document.getElementById('mfEtd')?.value || '';
    const whe      = document.getElementById('mfWhExitTime')?.value || '';
    const isColFre = group === 'col_fre';
    const whLabel  = warehouse === 'TALMA' ? 'TALMA' : 'FR\u00cdO A\u00c9REO';
    const grpLabel = isColFre ? 'COL/FRE' : 'DRY';
    const logoUrl  = window.location.pathname.split('/').slice(0,2).join('/') + '/assets/img/delta-Air-Lines-Logo.png';
    // Nombre del usuario autenticado que genera/imprime el documento
    // (Correcciones Hoja de Distribución #5) — distintos backends pueden
    // exponer el nombre completo bajo llaves distintas; se prueban las más
    // probables y se cae a email como último recurso.
    const dlAgentName = (currentUser && (currentUser.name || currentUser.full_name || currentUser.display_name)) || (currentUser?.email || '');

    // ── "dd MMM - HH:mm" (ej. 26 Jul - 23:30) — Correcciones Hoja de
    // Distribución (últimos ajustes) #2 ──
    const MONTHS = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
    function fmtCardDateTime(dateStr, timeStr) {
        if (!dateStr) return '\u2014';
        const d = new Date(dateStr + 'T12:00:00');
        const label = String(d.getDate()).padStart(2,'0') + ' ' + MONTHS[d.getMonth()];
        return timeStr ? label + ' - ' + timeStr : label;
    }

    // ── Warehouse Departure: si la hora de salida del almacén es MAYOR
    // que el ETD, la salida ocurrió el día calendario anterior (vuelos de
    // madrugada, carga que sale la noche previa) — Correcciones Hoja de
    // Distribución (últimos ajustes) #1. Se resta un día a la fecha del
    // vuelo antes de formatear. ──
    function whDepartureDateFor(flightDateStr, etdTime, whTime) {
        if (!flightDateStr || !whTime || !etdTime) return flightDateStr;
        if (whTime > etdTime) {
            const d = new Date(flightDateStr + 'T12:00:00');
            d.setDate(d.getDate() - 1);
            return d.getFullYear() + '-' + String(d.getMonth()+1).padStart(2,'0') + '-' + String(d.getDate()).padStart(2,'0');
        }
        return flightDateStr;
    }

    const flightDateEtdLabel = fmtCardDateTime(date, etd);
    const whDepartureLabel   = fmtCardDateTime(whDepartureDateFor(date, etd, whe), whe);

    const rows = mfRows.filter(r => {
        const chk = document.querySelector('.mf-check[data-id="'+r.id+'"]');
        const isChecked = chk ? chk.checked : (r.in_manifest==1);
        const whSel = document.querySelector('.mf-warehouse[data-id="'+r.id+'"]');
        const wh = whSel ? whSel.value : r.warehouse;
        if (!isChecked || wh !== warehouse) return false;
        const prod = (r.product_code || '').toUpperCase();
        return isColFre ? (prod === 'COL' || prod === 'FRE') : (prod !== 'COL' && prod !== 'FRE');
    });
    if (!rows.length) { alert('No hay gu\u00edas para este grupo/almac\u00e9n.'); return; }

    const totalGross = rows.reduce(function(s,r){ return s + (parseFloat(r.gross_weight)||0); }, 0);
    const totalSkids = rows.reduce(function(s,r){ return s + (parseInt(r.skids_qty)||0); }, 0);

    const CHK = '<span style="display:inline-block;width:16px;height:16px;border:1.5px solid #333;vertical-align:middle;"></span>';

    // ── Build table rows ────────────────────────────────────────
    let rowsHtml = '';
    rows.forEach(function(r, i) {
        const bg    = i%2===0 ? '#fff' : '#F5F8FC';
        const awb   = esc(r.awb || r.shipment_number);
        const agent = esc(r.agent_name || '\u2014');
        const desc  = esc((r.description || '\u2014').toUpperCase());
        const dest  = esc(r.routing_dest || r.flt_dest || '');
        const rem   = esc((r.remark || '').toUpperCase());
        const gross = r.gross_weight ? parseFloat(r.gross_weight).toLocaleString('en-US',{minimumFractionDigits:0}) : '';
        const skids = r.skids_qty || '';

        if (isColFre) {
            rowsHtml += `<tr style="background:${bg};height:44px;">
                <td class="c" style="font-weight:700;">${esc(r.uld_type||'')}</td>
                <td class="c uldnum"></td>
                <td class="c">${CHK}</td>
                <td class="c">${CHK}</td>
                <td class="awb">${awb}</td>
                <td>${agent}</td>
                <td class="c">${skids}</td>
                <td class="r">${gross}</td>
                <td>${desc}</td>
                <td class="c">${dest}</td>
                <td class="c">${CHK}</td>
                <td class="c">${CHK}</td>
                <td class="c">${CHK}</td>
                <td>${rem}</td>
                <td class="c">${CHK}</td>
                <td class="c">${CHK}</td>
            </tr>`;
        } else {
            rowsHtml += `<tr style="background:${bg};height:44px;">
                <td class="c" style="font-weight:700;">${esc(r.uld_type||'')}</td>
                <td class="c"></td>
                <td class="awb">${awb}</td>
                <td>${agent}</td>
                <td class="c">${skids}</td>
                <td class="r">${gross}</td>
                <td>${desc}</td>
                <td class="c">${dest}</td>
                <td>${rem}</td>
                <td class="c">${r.dam||''}</td>
                <td class="c">${r.date_dam||''}</td>
            </tr>`;
        }
    });

    // ── Firmas — reubicadas al pie de página, alineadas con END TIME
    // (Correcciones Hoja de Distribución #4); DL AGENT muestra el nombre
    // del usuario autenticado debajo (#5) ──
    const signZone = `
    <div class="sign-zone">
        <div class="sign-times">
            <div class="sign-time-lbl">START TIME:</div>
            <div class="sign-time-box"></div>
            <div class="sign-time-lbl" style="margin-top:16px;">END TIME:</div>
            <div class="sign-time-box"></div>
        </div>
        <div class="sign-names" style="justify-content:${isColFre ? 'space-around' : 'center'};">
            ${isColFre ? `
            <div class="sign-block">
                <div class="sign-line"></div>
                <div class="sign-role">AGENT G4S</div>
            </div>` : ''}
            <div class="sign-block">
                <div class="sign-line"></div>
                <div class="sign-role">DL AGENT</div>
                <div class="sign-user">${esc(dlAgentName || '\u2014')}</div>
            </div>
        </div>
    </div>`;

    // ── Cabecera con tarjetas/badges (Correcciones Hoja de Distribución #1) ──
    const headerCards = `
    <div class="hcards">
      <div class="hcard">
        <span class="hcard-lbl">Flight</span>
        <span class="hcard-val">${esc(flt.replace(/^DL/,''))}</span>
      </div>
      <div class="hcard">
        <span class="hcard-lbl">Flight Date / ETD</span>
        <span class="hcard-val">${flightDateEtdLabel}</span>
      </div>
      <div class="hcard">
        <span class="hcard-lbl">Manifest N&deg;</span>
        <span class="hcard-val">${esc(mn || '\u2014')}</span>
      </div>
      <div class="hcard hcard-warn">
        <span class="hcard-lbl">Warehouse Departure</span>
        <span class="hcard-val">${whDepartureLabel}</span>
      </div>
      <div class="hcard hcard-total">
        <span class="hcard-lbl">Total Kg</span>
        <span class="hcard-val">${Math.round(totalGross).toLocaleString('en-US')}</span>
      </div>
    </div>`;

    // ── Build thead — anchos redistribuidos para usar casi el 100% del
    // ancho imprimible y columna ULD# ampliada en COL/FRE (Correcciones
    // Hoja de Distribución #3 y #6) ──
    const thead = isColFre ? `<thead>
        <tr>
            <th rowspan="2" style="width:4%">ULD<br>TYPE</th>
            <th rowspan="2" style="width:9%">ULD #</th>
            <th colspan="2" style="background:#1A3A6B;">ULD COND.</th>
            <th rowspan="2" style="width:9%">AWB</th>
            <th rowspan="2" style="width:15%">FWDR</th>
            <th rowspan="2" style="width:4%">SKIDS</th>
            <th rowspan="2" style="width:6%">GROSS<br>WT (Kg)</th>
            <th rowspan="2" style="width:14%">DESCRIPTION</th>
            <th rowspan="2" style="width:4%">DST</th>
            <th rowspan="2" style="width:4%">STRAPS</th>
            <th colspan="2" style="background:#1A3A6B;">NETS COND.</th>
            <th rowspan="2" style="width:10%">REMARKS</th>
            <th colspan="2" style="background:#C8102E;">CARGA INS.</th>
        </tr>
        <tr>
            <th style="width:3%;background:#1A3A6B;">OK</th>
            <th style="width:3%;background:#1A3A6B;">DMG</th>
            <th style="width:3%;background:#1A3A6B;">OK</th>
            <th style="width:3%;background:#1A3A6B;">DMG</th>
            <th style="width:4.5%;background:#C8102E;">SKID</th>
            <th style="width:4.5%;background:#C8102E;">ULD</th>
        </tr>
    </thead>` : `<thead>
        <tr>
            <th style="width:5%">ULD<br>TYPE</th>
            <th style="width:6%">ULD #</th>
            <th style="width:10%">AWB</th>
            <th style="width:17%">FWDR</th>
            <th style="width:5%">SKIDS</th>
            <th style="width:8%">GROSS WEIGHT<br>(Kg)</th>
            <th style="width:18%">DESCRIPTION</th>
            <th style="width:4%">DST</th>
            <th style="width:14%">REMARKS</th>
            <th style="width:6%">DAM</th>
            <th style="width:7%">DATE DAM</th>
        </tr>
    </thead>`;

    const tfoot = isColFre ? `<tfoot>
        <tr>
            <td colspan="6" style="text-align:right;padding:8px 10px;border:1px solid #bbb;">TOTAL</td>
            <td style="text-align:center;padding:8px;border:1px solid #bbb;">${totalSkids||''}</td>
            <td style="text-align:right;padding:8px;border:1px solid #bbb;">${Math.round(totalGross).toLocaleString('en-US')}</td>
            <td colspan="8" style="border:1px solid #bbb;"></td>
        </tr>
    </tfoot>` : `<tfoot>
        <tr>
            <td colspan="4" style="text-align:right;padding:8px 10px;border:1px solid #bbb;">TOTAL</td>
            <td style="text-align:center;padding:8px;border:1px solid #bbb;">${totalSkids||''}</td>
            <td style="text-align:right;padding:8px;border:1px solid #bbb;">${Math.round(totalGross).toLocaleString('en-US')}</td>
            <td colspan="5" style="border:1px solid #bbb;"></td>
        </tr>
    </tfoot>`;

    // ── Full HTML ───────────────────────────────────────────────
    const html = `<!DOCTYPE html>
<html><head><meta charset="UTF-8">
<title>Hoja Distribuci\u00f3n ${whLabel} \u2014 ${esc(flt)}</title>
<style>
  * { box-sizing:border-box; margin:0; padding:0; }
  html, body { width:297mm; }
  body { font-family:Arial,sans-serif; font-size:12px; color:#000;
         margin:0 auto; padding:9mm; background:#fff; }
  -webkit-print-color-adjust: exact;
  print-color-adjust: exact;
  .page { display:flex; flex-direction:column; min-height:189mm; }
  h1 { font-size:19px; font-weight:900; text-transform:uppercase;
       color:#002244; letter-spacing:.02em; margin:12px 0 14px; }
  .hcards { display:flex; gap:12px; flex-wrap:wrap; margin-bottom:16px; }
  .hcard { background:#F8FAFC; border:1px solid #E2E8F0; border-radius:8px;
           padding:8px 16px; display:flex; flex-direction:column; gap:3px;
           justify-content:center; }
  .hcard-lbl { font-size:9.5px; font-weight:700; text-transform:uppercase;
               letter-spacing:.05em; color:#6B7280; }
  .hcard-val { font-size:16px; font-weight:800; color:#002244; }
  .hcard-warn { background:#FFF6DA; border:2px solid #F0B429; }
  .hcard-warn .hcard-lbl { color:#7A4B00; }
  .hcard-warn .hcard-val { color:#7A4B00; font-size:17px; }
  .hcard-total { margin-left:auto; align-items:center; background:#002244;
                 border:2px solid #002244; padding:8px 22px; }
  .hcard-total .hcard-lbl { color:#AFC3DE; }
  .hcard-total .hcard-val { font-size:24px; font-weight:900; color:#fff; letter-spacing:.02em; }
  table { width:100%; border-collapse:collapse; table-layout:fixed; font-size:11px; }
  thead th { padding:7px 4px; text-align:center; border:1px solid #001422;
             font-size:9.5px; text-transform:uppercase; font-weight:700;
             color:#fff; background:#002244;
             -webkit-print-color-adjust:exact; print-color-adjust:exact; }
  tbody td { padding:6px 4px; border:1px solid #ccc; vertical-align:middle;
             word-break:break-word; }
  tfoot td { border:1px solid #bbb; font-size:11px; font-weight:700;
             background:#E8EEF6; -webkit-print-color-adjust:exact; print-color-adjust:exact; }
  .c { text-align:center; }
  .r { text-align:right; }
  .awb { font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;
         letter-spacing:.02em; font-size:10.5px; font-weight:700; color:#002244; }
  .uldnum { font-family:'Courier New',monospace; font-size:10px; }
  .sign-zone { margin-top:auto; padding-top:26px; display:flex;
               justify-content:space-between; align-items:flex-end; }
  .sign-time-lbl { font-size:10.5px; font-weight:700; text-transform:uppercase; margin-bottom:5px; }
  .sign-time-box { width:190px; height:34px; border:1px solid #333; }
  .sign-names { flex:1; display:flex; align-items:flex-end; }
  .sign-block { text-align:center; min-width:260px; }
  .sign-line { width:260px; height:1px; background:#333; margin:0 auto 6px; }
  .sign-role { font-size:12px; font-weight:700; letter-spacing:.05em; }
  .sign-user { font-size:11.5px; font-weight:600; color:#333; margin-top:3px; }
  @media print {
    @page { size:A4 landscape; margin:8mm; }
    body { padding:0; width:281mm; }
    .page { min-height:181mm; }
    thead th, tfoot td, .hcard-warn, .hcard-total { -webkit-print-color-adjust:exact !important; print-color-adjust:exact !important; }
  }
</style>
</head><body>
<div class="page">

<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:6px;">
  <img src="${logoUrl}" alt="Delta Air Lines" style="height:64px;width:auto;">
  <div style="font-size:9px;color:#9AA3AF;text-align:right;">
    Impreso: ${new Date().toLocaleString('es-PE')}
  </div>
</div>

<h1>HOJA DE DISTRIBUCI\u00d3N &ndash; ${whLabel} &middot; ${grpLabel}</h1>

${headerCards}

<table>
${thead}
<tbody>${rowsHtml}</tbody>
${tfoot}
</table>

${signZone}

</div>
</body></html>`;

    const win = window.open('', '_blank');
    win.document.write(html);
    win.document.close();
}


async function mfSave() {
    const msg = document.getElementById('mfMsg');
    const btn = document.getElementById('mfBtnSave');
    const mn = document.getElementById('mfManifestNumber').value.trim();

    const anyChecked = Array.from(document.querySelectorAll('.mf-check')).some(c => c.checked);
    if (anyChecked && (!mn || !/^\d{4}-\d+$/.test(mn))) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Enter a valid Manifest No. (e.g. 2026-125).';
        return;
    }

    const flt     = document.getElementById('mfFlight').value;
    const date    = document.getElementById('mfDate').value;
    const station = document.getElementById('mfStation')?.value || 'LIM';

    const items = [];
    document.querySelectorAll('.mf-check').forEach(c => {
        const id = c.dataset.id;

        const whSel = document.querySelector('.mf-warehouse[data-id="'+id+'"]');
        const warehouse = whSel ? (whSel.value || null) : null;

        const uldCell  = document.querySelector('.mf-edit-cell[data-id="'+id+'"][data-field="uld_type"]');
        const uldInput = uldCell ? uldCell.querySelector('select,input') : null;
        const uld_type = uldInput ? uldInput.value : (uldCell ? (uldCell.dataset.value || '') : '');

        const skidsCell  = document.querySelector('.mf-edit-cell[data-id="'+id+'"][data-field="skids_qty"]');
        const skidsInput = skidsCell ? skidsCell.querySelector('input') : null;
        const skids_raw  = skidsInput ? skidsInput.value : (skidsCell ? (skidsCell.dataset.value || '') : '');
        const skids_qty  = skids_raw !== '' ? parseInt(skids_raw) : null;

        const remCell  = document.querySelector('.mf-edit-cell[data-id="'+id+'"][data-field="remark"]');
        const remInput = remCell ? remCell.querySelector('input') : null;
        const remark   = (remInput ? remInput.value : (remCell ? (remCell.dataset.value || '') : '')).toUpperCase();

        const damCell  = document.querySelector('.mf-edit-cell[data-id="'+id+'"][data-field="dam"]');
        const damInput = damCell ? damCell.querySelector('input') : null;
        const dam      = damInput ? damInput.value : (damCell ? (damCell.dataset.value || '') : '');

        const dateDamCell  = document.querySelector('.mf-edit-cell[data-id="'+id+'"][data-field="date_dam"]');
        const dateDamInput = dateDamCell ? dateDamCell.querySelector('input') : null;
        const date_dam     = dateDamInput ? dateDamInput.value : (dateDamCell ? (dateDamCell.dataset.value || '') : '');

        items.push({ id: parseInt(id), in_manifest: c.checked ? 1 : 0, warehouse, uld_type, skids_qty, remark, dam: dam || null, date_dam: date_dam || null });
    });

    if (btn) btn.disabled = true;
    msg.style.color = 'var(--text-mid)'; msg.textContent = 'Saving...';

    try {
        const res = await apiFetch('/shipments/shipment_manifest_api.php', {
            method: 'POST',
            body: JSON.stringify({
                flt_number:      flt,
                flt_date:        date,
                station:         station,
                manifest_number: mn || null,
                wh_exit_time:    document.getElementById('mfWhExitTime').value || null,
                etd:             document.getElementById('mfEtd').value || null,
                user_id:         currentUser?.id || null,
                items,
            })
        });
        if (res.success) {
            msg.style.color = '#2ECC71'; msg.textContent = '✅ ' + res.message;
            await mfLoad();
            mfUpdateActionBar();
        } else {
            msg.style.color = 'var(--delta-red)'; msg.textContent = res.message || 'Error saving.';
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)'; msg.textContent = 'Connection error.';
    }
    if (btn) btn.disabled = false;
}

// ── Tarea #2 + #3: Barra de acciones del Manifest ────────────────

function mfUpdateActionBar() {
    const isDefinitive = mfManifestStatus === 'DEFINITIVE';

    // Banner de estado DEFINITIVO
    let banner = document.getElementById('mfDefinitiveBanner');
    if (isDefinitive) {
        if (!banner) {
            banner = document.createElement('div');
            banner.id = 'mfDefinitiveBanner';
            banner.style.cssText = 'margin-bottom:12px;padding:10px 16px;background:#E1F5EE;border:1px solid #9FE1CB;border-radius:6px;display:flex;align-items:center;gap:10px;font-size:13px;font-weight:600;color:#085041;';
            banner.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg> Manifiesto CERRADO — edici\u00f3n de datos operativos bloqueada. Permitido: Other Charges, Shipper, Consignee, Remarks, DAM, Date DAM.';
            const distroBar = document.getElementById('mfDistroBar');
            if (distroBar) distroBar.parentNode.insertBefore(banner, distroBar);
        }
        // Bloquear checkboxes, warehouse y celdas operativas
        document.querySelectorAll('.mf-check, .mf-warehouse').forEach(el => el.disabled = true);
        document.querySelectorAll('.mf-edit-cell').forEach(el => {
            const field = el.dataset.field;
            // Permitir solo campos secundarios
            if (!['remark','dam','date_dam'].includes(field)) {
                el.style.pointerEvents = 'none';
                el.style.opacity = '.7';
            }
        });
    } else {
        if (banner) banner.remove();
    }
}

async function mfCloseManifest() {
    const mn      = document.getElementById('mfManifestNumber').value.trim();
    const flt     = document.getElementById('mfFlight').value;
    const date    = document.getElementById('mfDate').value;
    const msg     = document.getElementById('mfMsg');
    const btnClose = document.getElementById('mfBtnClose'); // ya no existe en el DOM (botón eliminado); se guarda con chequeo null por si esta función huérfana se vuelve a invocar

    if (!mn || !/^\d{4}-\d+$/.test(mn)) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'You need a valid Manifest No. to close.';
        return;
    }

    if (!confirm(
        'Close manifest ' + mn + ' as DEFINITIVE.\n\n' +
        'Requirements:\n' +
        '  ✓ All AWBs loaded (LOADED)\n' +
        '  ✓ Warehouse assigned on every AWB\n' +
        '  ✓ DAM and Date DAM completed\n' +
        '  ✓ Freight Forwarder, Shipper y Consignee\n\n' +
        'Confirm closing?'
    )) return;

    if (btnClose) btnClose.disabled = true;
    msg.style.color = 'var(--text-mid)';
    msg.textContent = 'Validating and closing...';

    try {
        const res = await apiFetch('/shipments/manifest_close_api.php', {
            method: 'POST',
            body: JSON.stringify({
                manifest_number: mn,
                flt_number:      flt,
                flt_date:        date,
                user_id:         currentUser ? currentUser.id : null,
            })
        });

        if (res.success) {
            msg.style.color = '#2ECC71';
            msg.textContent = '🔒 ' + res.message;
            mfManifestStatus = 'DEFINITIVE';
            mfUpdateActionBar();
            mfUpdateDistroBar();
            mfRenderRows();
        } else {
            // Mostrar errores de validación
            if (res.errors && res.errors.length) {
                // Agrupar por regla
                const byRule = {};
                res.errors.forEach(e => {
                    if (!byRule[e.rule]) byRule[e.rule] = [];
                    byRule[e.rule].push(e.msg);
                });
                const ruleLabels = {
                    1: 'Missing DAM / Date DAM',
                    2: 'AWBs not loaded',
                    3: 'Warehouse not assigned',
                    4: 'Missing FF / Shipper / Consignee',
                };
                let errHtml = '<div style="color:var(--delta-red);font-size:12px;">';
                errHtml += '<strong>' + esc(res.message) + '</strong>';
                Object.keys(byRule).forEach(rule => {
                    errHtml += '<div style="margin-top:6px;font-weight:700;color:#854F0B;">'
                             + '⚠ ' + (ruleLabels[rule] || 'Error') + ':</div>';
                    byRule[rule].forEach(m => {
                        errHtml += '<div style="margin-left:12px;color:var(--delta-red);">• ' + esc(m) + '</div>';
                    });
                });
                errHtml += '</div>';
                msg.innerHTML = errHtml;
            } else {
                msg.style.color = 'var(--delta-red)';
                msg.textContent = res.message || 'Error al cerrar.';
            }
            if (btnClose) btnClose.disabled = false;
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Connection error.';
        if (btnClose) btnClose.disabled = false;
    }
}

async function mfSendPreManifest() {
    const mn      = document.getElementById('mfManifestNumber').value.trim();
    const flt     = document.getElementById('mfFlight').value;
    const date    = document.getElementById('mfDate').value;
    const station = document.getElementById('mfStation')?.value || 'LIM';
    const msg     = document.getElementById('mfMsg');
    const btn     = document.getElementById('mfBtnSendPre');

    if (!mn || !/^\d{4}-\d+$/.test(mn)) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Save the manifest first (a valid Manifest No. is required).';
        return;
    }
    if (!confirm('Send Pre-Manifest ' + mn + ' by email to the configured recipients?')) return;

    btn.disabled = true;
    msg.style.color = 'var(--text-mid)';
    msg.textContent = 'Sending...';

    try {
        const res = await apiFetch('/shipments/manifest_send_api.php', {
            method: 'POST',
            body: JSON.stringify({
                flt_number:      flt,
                flt_date:        date,
                manifest_number: mn,
                station:         station,
                user_id:         currentUser ? currentUser.id : null,
            })
        });
        if (res.success) {
            msg.style.color = '#2ECC71';
            msg.textContent = '✅ ' + res.message;
            await mfLoad();
        } else if (res.registered) {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = '⚠ Registered but not sent: ' + res.message;
            await mfLoadSendHistory();
        } else {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = res.message || 'Error sending.';
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Connection error.';
    }
    btn.disabled = false;
    mfUpdateActionBar();
}

// Tarea #4 — Enviar Pesos → ACS
async function mfSendACS() {
    const mn      = document.getElementById('mfManifestNumber').value.trim();
    const flt     = document.getElementById('mfFlight').value;
    const date    = document.getElementById('mfDate').value;
    const station = document.getElementById('mfStation')?.value || 'LIM';
    const msg     = document.getElementById('mfMsg');

    if (mfManifestStatus !== 'DEFINITIVE') {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'The manifest must be DEFINITIVE to send to ACS.';
        return;
    }

    // Modal para ingresar datos del agente y del avión
    // Se pre-llena con el nombre del usuario autenticado (Nombre y
    // Apellido en User Management); editable por si el agente físico
    // de vuelo es distinto al usuario que genera el reporte.
    const defaultAgentName = (currentUser && (currentUser.full_name || currentUser.name)) || '';
    const agentName = prompt('Flight agent name (shown on the report):', defaultAgentName);
    if (agentName === null) return; // canceló

    const acType    = prompt('Aircraft type:', 'A330-300');
    if (acType === null) return;

    const positions = prompt('Number of positions (ULDs):', '');
    if (positions === null) return;

    if (!confirm(
        'Generate and send the Outbound Cargo Flight Report to ACS.\n\n' +
        'Flight: ' + flt + '  |  Date: ' + date + '\n' +
        'Manifest: ' + mn + '\n\n' +
        'Confirm sending?'
    )) return;

    msg.style.color = 'var(--text-mid)';
    msg.textContent = 'Generating report and sending to ACS...';

    try {
        const res = await apiFetch('/shipments/outbound_cargo_api.php', {
            method: 'POST',
            body: JSON.stringify({
                flt_number:      flt,
                flt_date:        date,
                manifest_number: mn,
                agent_name:      agentName,
                ac_type:         acType || 'A330-300',
                positions:       parseInt(positions) || 0,
                station:         station,
                user_id:         currentUser ? currentUser.id : null,
            })
        });

        // Abrir el reporte para impresión en todos los casos (si viene print_html)
        if (res.print_html) {
            const win = window.open('', '_blank');
            win.document.write(res.print_html);
            win.document.close();
            setTimeout(() => win.print(), 600);
        }

        if (res.success) {
            msg.style.color = '#2ECC71';
            msg.textContent = '✅ ' + res.message +
                ' | Total: ' + res.total_kg + ' KG / ' + res.total_lb + ' LB';
            mfLoadSendHistory();
        } else if (res.registered) {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = '⚠ Report generated but email failed: ' + res.message;
            mfLoadSendHistory();
        } else {
            msg.style.color = 'var(--delta-red)';
            msg.textContent = res.message || 'Error al generar el reporte.';
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Connection error.';
    }
}

// ── Print Pre-Manifest ───────────────────────────────────────
function mfPrint() {
    const flt     = document.getElementById('mfFlight')?.value    || '';
    const date    = document.getElementById('mfDate')?.value      || '';
    const mn      = document.getElementById('mfManifestNumber')?.value || '';
    const etd     = document.getElementById('mfEtd')?.value       || '';
    const whe     = document.getElementById('mfWhExitTime')?.value || '';
    const station = document.getElementById('mfStation')?.value   || 'LIM';

    if (!mfRows.length) { alert('No AWBs in the manifest.'); return; }

    const isClosed  = mfManifestStatus === 'DEFINITIVE';
    const marked    = mfRows.filter(r => r.in_manifest == 1);
    const totalPzas = marked.reduce((s,r) => s + (parseInt(r.pieces)||0), 0);
    const totalKg   = marked.reduce((s,r) => s + (parseFloat(r.gross_weight)||0), 0);

    // Vuelo: extraer número limpio (ej. "DL150" → orig "LIM" dest "ATL")
    const fltNum  = flt.replace(/^DL/,'');
    const fltOrig = marked.length ? (marked[0].routing_orig || marked[0].flt_orig || station) : station;
    const fltDest = marked.length ? (marked[0].routing_dest || marked[0].flt_dest || '') : '';
    const dateLabel = date ? (()=>{
        const d = new Date(date+'T12:00:00');
        return d.toLocaleDateString('en-US',{day:'2-digit',month:'short',year:'2-digit'});
    })() : '';

    // Logo Delta embebido en base64
    const DELTA_LOGO = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAE4gAAAw1BAMAAACYdHZ0AAAAElBMVEVHcEwUMWIvLVwAM2aZGTPAGTOn0c67AAAAA3RSTlMAuFnE3Qk+AAAgAElEQVR42uzBAQEAAACAkP6v7ggKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgNmDAwEAAAAAIP/XRlBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYQ8OBAAAAACA/F8bQVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVWFPTgQAAAAAADyf20EVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVRX24EAAAAAAAMj/tRFUVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVdi5d9zGgSCKohCBzpkwV9L7aBvUAsZA738rHkozsMSf0ir4nCW86IKFJgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC/xTDaAAAgndpsAACQTv+wAQBANkOfr1YAAEim9LlZAQAgmd5n91QAgGSGvxHnngoAkExZIq7ZAQAglWmJuE87AABkMvQl4txTAQBSKY+Ia5YAAEhkekSceyoAQCLLNXWJOPdUAIBEyr+Iu422AABIY/r/Jc49FQAgjfs19R5x7qkAAGmUn4gbrQEAkET9ibgvawAA5PC4pj4ibjYHAEAO5SnivE8FAEiiPn+J8z4VACCH/hxx7qkAACmU14gbLQIAkEB9jTjvUwEAMuivEeeeCgCQwGUdcaNNAADCq+uI8z4VACC+vo4491QAgPAu24i7WgUAILi6jTj3VACA6Po24j6sAgAQ22Un4m5XuwAAhFZ3Im5udgEACK3vRdwfuwAARHbZjTjvUwEAQqv7EdcsAwAQWN+POO9TAQACGw4izvtUAIDA6kHEzc02AABh9aOI8z4VACCs4TDivE8FAAirHkdcsw4AQFD9OOK8TwUACGo4iTjvUwEAgionETc3+wAAhDSdRZx7KgBASKtr6iriZgMBAERUziOuWQgAIKDpPOI+LQQAEM/6mrqOOP/7BQAIqLyLuNFGAADhTO8i7stGAADRbK6pm4jzPhUAIJzyNuJuo5UAAIKZvtm7mxy3jS0MoKIDzqUWNKda4LzbvQHZUC/ABrj/rTw7cRDk2YFFsapYl3W+IGMDxfo5vMWr/n0lTn+qiIiISGX5+Tb1Z8S5TxURERGpLP09iNsbJxEREZGqMt6DOP2pIiIiIlXlF7epv0Cc+1QRERGRqtLfhTj9qSIiIiJVZbyvEqc/VURERKSmTPchzn2qiIiISEXp70Xc3liJiIiIVJPxXsTpTxURERGpJ9O9iHOfKiIiIlJNuvsRtzdaIiIiIpVkvB9xV6MlIiIiUkmm+xHnPlVERESkknRzEDcYLxERkUpO8MPrwv/2BjF2xjmI83u/IiIileT4tjQfDWLsTHMQ98l4rZwPzwUzfIshfzzPW8rQ7FMY7CUbTvinuxxxr7bq0OlmIe7dmb72il2+ZO9c2D/y9nY4HM5Pzzg3/5As9rBKZN/sknna6Pz8Y0vT8+G8RN9l3iCu8YyzEHe7GrHor12zV/j3/49/gu7p6dkTKLq9QlwFS2art01/EFzkef13GcYYtJ5pHuK+GLHWEPd/5bm3w+FZUQ7iIA7iVOIqyAXiGk83E3H6U5tG3F85Ho8gB3EQB3Hm9SZOBJ0NkdPPRdzVmDWPuB81ucMTyEEcxEGcSlzwE8FHcZEzzUWc/lSI+6cix3EQB3EQZ17H3mRebeOBZ8BsxOlPhbh/r3+OgziIgziVuFXSkWzj6Wcj7nY1ahDHce0hbmh2yUCcSly1uRiExjPNR5z7VIj7OYcnj0YlDuIgTiUu4oGgsyHuKfMA4vSnQpxyHMRBHMSZ11s5EHQ2hE3/COKuxg3ifrkRvPopYIiDOIhTiSuWLtHe7Q08ak6PIO6zcYO4/4pbVYiDOIgzr2MhzkdxYQ+Z6RHEuU+FuP/OEeMgDuIgTiWuSC4Q13b6xxB3NXIQpxrXGuKGZpcMxKnEbf080NkQNKfHEOc+FeJ+U41TrFWJgziIU4kLs8XYrmNOgOkxxLlPhbjffSerGgdxEAdx5nXedMl2bId6yPQPIu59b+wg7jc56FSFOIiDOJW4EIjzUVzMnB6txLlPhbjfv9p5t4M4iIM48zrEcQBxIY+Y6VHEuU+FuLvuVBufJxAHcRCnEhfiOPBzvxHTP444aoe4u+5U2/40TncqxEGcl5MQOwzERczpccR9NXoQd1eaLsapxEEcxKnE5UuXcBxa3qnDnjDT44i7GT6Iuy8t//gvxEEcxJnX+XI2Dk2nX4A4/akQ58s4iIM4iFOJ28hp4EyPl3FJJU5/KsTd/2Vcq782AnEQB3HmdYzTwEdx8TItQZz7VIibwziIc9hBHMSpxNW7wUBcuPTLEKf2CnFzFDdAXPQMzS4ZiFOJqzKdBd50xmWI058KcfO+jIM4hx3EQZxKXLqcLfCmMy1DnPtUiKM4iIM4iDOvt3EYQFy0QuxCxOlPhThXqhAHcRCnEreNw8BHccEyLq3E6U+FOIqDOIiDOPN6E/sLxAXLtBRx7lMhbvYu0Vj5FuIgDuJU4nKlSzwSzV2VxE63HHGeOMT5MK4hxA3NLhmIU4mrMWcj0XLG5Yhznwpx8xX3saVnpRIHcRCnEhflLGj4LyRGzLQccZ+MIsT58ALiIA7izOs1tpejvbnhdAkQpz8V4rQ3QBzEQZxK3Da2F8droIwJEHe7GkeIoziIgziIM6+38Pjaeb/eQKYUiPtiHCHukRxb2SsgDuIgTiUuUy6GouF0SRCnPxXivPG1g7ih2SUDcSpxTRwFOhviZEyDuKuRhDiKU4mDOIhTiSu+u6Q/CnQ2xMmUBnH6UyHu0d2iCcVBHMRBnHmdJ90bxLWbD4kQ9z4YS4hTi4M4iIM4lbj4iPNzv2EyJkLc7WosIY7iIA7iIM68LpyLsWg4UyrE6U+FODeqEAdxEKcSt4WToKk/qBP6aEmGOP2pEEdxrSBuaHbJQJxKXBubi4/igmRMh7ir0YQ4ilOJgziIU4krmi7LYDhgY+Q9HeL0p0LcghwgDuIgDuLM61oe3n4nEU6WKR3i9KdCnNMR4iCOA1TitnAQQFyIjAkRd7saT4hbkG3/RDjEQRzEmdeBDgKdDSFySok496kQR3EQB3EQpxK3gb1FZ0OIhz+lRJz+VIhbtmlsuX6vOxXiIM7LSY50mUbDCRsgfVrEXY0oxLVpA5U4iIM4lbh1crHK280pLeI+G1GIW1aL267iIA7iIM68jnQOQFyAc2VKizj3qRC3MNv9oRGIgziIU4mLdA7obKg/fWrEkTvELcxmmxsgDuIgzryOtLXobKg/p9SI+2pMIW7hvrHVFwGIgziIU4nLkC7bcAzO2NqPlSk14m4GFeJ8FtcA4oZmlwzEqcRVlovhaDZ9csS9e+gQp4avEgdxEKcSF/8YcJ7XnlP6Spz+VIjzWRzEQRzEmdfxjwGdDbWfKlN6xLlPhTiKgziIgziVuELJ90mczoba0+dAnPorxPksDuIgDuLM6/CI09lQecYciNOfCnFeACEO4iBOJa5MLtZ5q5lxmzoDce5TIc6F6vYRNzS7ZCBOJa6dUwDiqk6fBXH6UyEuRSlue9NIJQ7iIE4lLtbG4qO4qjPmqcTpT4U4n8VBHMRBnHldIh3ENZspD+Lcp0KcC1WIgziIU4kLjzidDTWnz4W4vbGFOF9jQBzEQZx5nT8XA9JqxlyI058Kcd4BIQ7iIE4lLvwh8LSTajPlQpz7VIhLkgPEETbEQZyXkxX3FR/F1ZsuH+IUYCFOJV8lDuIgTiUu/0kOca1mzIe4q9GFuCT7x7ClZwVxEAdx5nUwxOlsqDdTPsS5T4U432NAHMRBnEpc+DNgv5NK+Z4TcewOcTYQiIM4iDOvo58BH3dSZ8aciPN7vxDngwyIgziIU4mLvq0cdlJnppyI+2R8Ic6F6qYRNzS7ZCBOJa6i5P4kTmdDtU8+K+LeByMMcWl2kO1MJZU4iIM4lbjEOXtfazRjVsTdrkYY4nyRAXEQB3HmdfAjYL+TGjPlRdwXIwxxiUpxm9lCIA7iIE4lLtwR4G82VJkuM+L0p0KcTzIgDuIgzryOvqv4KK7KjLkRdzXGENfquzHEQRzE2W3K1GMgrtFMuRGnPxXiUuUIcRVmaHbJQJxKXD05W+tt5kN2xOlPhbhk2cipqRIHcRCnEhfvBHjZSXXpsyPudjXKEJeqnL+NNwKIgziIM6/jnQA6GyrMlB9x+lMhziYCcRAHcSpxwTcVH8VV+OALIE5/KsT5KAPiIA7izOvgDw3i6ktfAnFX4wxxvoqDOIjjAZW4XDlb7G3mVAJxn40zxCV7FdzCLqI7FeIgzstJwAMA4qo7TKYSiHOfCnHpcoA4hx3EQZxK3L/3lDIHwEcnbWXpyyCO3iGOGSAO4iDOvM6Ursyg+CiutpzKIE5/KsTZRiAO4iBOJS444gZHbV1nyVQGce5TIQ4aIA7iIM68zpSLUWkyfSHEvXvwEOerOIiDOIhTiYu9/zvL68qpVCVOfyrE2Ue2irih2SUDcbaZWraUUvu/zoa6nvtUCnHuUyFOKU4lDuIgTiUuS7pSo6Kzoar05RCnBgtxCfeR6NMJ4iAO4szrhDkX23wVZGrKqRzivhptiFPShziIgziVuAy5WO4t5rHb1McQdzPcEOczLIiDOIgzrzPEsDSZviDi9KdCnFIcxEEcxKnExd5RdDZUlFPJSpz+VIhTitsm4oZmlwzEqcTVka7csOhsqChTScS5T4W4lHmCOIcdxEGcStyfORccl8FhW0v6sojbG3GIs5NAHMRBnHkdefd3lFeTsSzi9KdWv4yPd8ZrcgWIO1aTA8RtDnH5Z83yW73seWlq94e4gJnKIs59au3L+Ol5Tp4Oh6fXFUEX+suM5Yib97Dypt0ls1XEfcg+Zy4BVoDSvs6GutMXRpz+1NpPpAce0PD8fD6sJLnI82n5njuY8BAXOJ0VUNdwzonxriRj6Uqc/tTtIe7v9/bz4bU44iIfoBAHcdRhBSTLpeTO62821JKpNOLcp1Z+Ir0s+uefz6UrcoG3EoiDOIizAuqZyT6Ki7iIyiPOqttoJe4fxx38ygjEQZxAHMRJ7ozlEec+dcuVuB86OZTbTgJX9SEO4iDOCqhnNNtpKttQpvKI+2TUt12J+8sn5cpxL2GfFcRBHHZYARAnC576CojTn7r9StyPcpwuKYiDOIG4MrmURZyxryLjCoi7XY379itxBRn3GvatAOIgDuKsgGomso/iAmZaA3FfjHsTlbhijAt7hkIcxEGcFZBqOymNuBdjXsESWgVx+lNbqcQVYlzY1gaIgziIswKqGUwfxcXLuA7irka+lUrc95xfvRFCHMQJxEGcJM60DuL0pzZUifueg80E4iBOIC5nSvc1GPwazpCVEPfu2bdUifu+Vx9tJhAHcQJxFc9jnQ3xMq6EuNvV2DdVicv+ZVzQv9oAcRAHcVZALbuJiR8v01qI05/aWCXuW8453xODtjZAHMRBnBVQy1j6KC7eEbIa4vSntlaJ22VugN/HXIGOMIgDDysA4uSxjOsh7mr0W6vE5b1SjbmbQBzEgYcVUMk0NvrxMq2HOP2p7VXivuXsPhXiIE4gbiOI2xv2dU+QFRGnP7XBStwu54dxLyGXoCMM4iDOCqhjMzHzw2VcEXG3q/FvsBKX8bdGXtvcdx1hEAdxslvlkzgfxa2d05qIc5/aZCUuY3tDxM0c4iCOPKyAJDlDXHNZeJu6EHH6U9usxO12z5kUF/Gn4iAO4iDOCqhjFhv+cOnXRdzVE2iyEpetFhfxnRDiIA7irIDAiHv5H3t3k9tIjoRhWLKgfbYN7V0Qcm9rLqAq2AfoAvL+V5nxTKOnp7pr2hIjSUby+VDbMmzGD9/8yMi08A11agtx30RgTCduNYpL2M1BHIgDcSqgj14yzAHIdmK+tIU456mjOnFrUdzjiI1XFYE4EEeN5hpcimuqY2uIm8RgUCdupXtxCdsJiANx0EMFROgM4obTqTXEfReDUZ24lby4fO0cxIE4EKcCukhir/tNt30srSHuTRCGdeLWmYfPd54K4kAciFMBII7u0LE5xL2L/rhO3CoUl8/ZB3EgDsSpgC4WUu5n06m9E2c+dWAnbrf7xXkqiANx2EMFpIY4l+Ka7R5Le4hznjqyE7fGtbiXdGVoCwNx2EMFlOv+uYYnEJdTxx4gbhKHgZ24FSguXT8BcSAOxKmAljn8ek6yX9APmnuAOPOpIztxa5wAZGvoIA7EgTgV0BTi9iAupQJOUwMgznnq0E7cCsMN2foJiANxIE4FtFzHx73kT6ljFxBnPnVsJy5+LD5bPwFxIA58qICW6ziVpr9LcW009+HEmU8d24kLvxaXrZ+AOBAHPlRAuc4F61cKcSLQREsfEOc8dXAnLvxAdcoVKxAH4kCcCmiZwuUf7JosfwMde4E44R/biQs/UE22m4I4EAfiVEB5I7l/rmG3O4C4hJp7gTjzqYM7ceEHqiCOQByIs4yfnWsoj4Hsb6GlF4hznjq6Exd9oJqrpYM4EIc+VEDDZXwpz3+TDS0i3g3EmU8d3YmLPlB9BHEE4kCcDP788j0JQTrN/Thx5lNHd+J2+1CKy/VUCOJAHIhTAe0y+N/90mRDPi39QJzz1OGduOADVRBHIA7EDaWHsode32zIVzc9QZwSHN2JC55tmMZovrYwEAfiVEDpKj5GBEH6V9fcE8Q5Tx3eiYv9hmqqhgLiQBz8UAGl+lLW7Isfo0WgtpaeIO6reIzuxMXONqS6FAfiQByIUwHtEvg5ogB8s6F62XQFce/iP7wTF2vFZUooEAfiQJwKaJfA//n/JhuSae4K4t6uIjK8ExdqxWVqKCAOxIE4FdCsjfx2bnEAcbm09AVxv4rI8E5cAMzk3FBBHIgDcSqg2SJeYqLgdb+VA94ZxJlP5cSFvmYkU0MBcSAOf6iAZt3zt15fOtkA4upq7g3irmIyvBMX+sbfRF0dxIE4EKcCmuXvc1AFCEJVLb1BnPlUTlyoFTfliRWIA3EgTgW0yt/fHTSTDZn00B3EmU/lxIVacYl2VBAH4kCcCmjVRV6jwgDiamruDuLerqLCiSt+Fkx5QQPEgTgQpwLKdCjPW5MNmbT0B3HmUzlxoQOqedo6iANxIE4FtHr+/b3Vm2xIpIcOIc58KiduF/muuClPOdrCQByIUwGN0ve/aycKeXTsEeKu4sKJC/xsQ54tFcSBOBCnAsqayP3pG+HmuRRXW6ceIe6buHDiAq24PN4+iANxIE4FNFrCPzTKQ2EUHqVytU1j6RHinKdy4iKtuDzfYwZxIA6BqIBGS3iJi4NLcdV07DqdPz4AACAASURBVBPiriLDiSu+XJvQ2wdxIA6BqIAinSP6pMmGNDr1CXHmUzlxkVZcGm8fxIE4EKcCGmXvHx92hSHLnrH0CXFvQsOJC3zLSJrHQhAH4kCcCijqIU8hbdJkQxIde4W4q9hw4uJe+JvmUhyIA3EgTgW0WcHXyEAogUo69Qpx5lM5cbvA89Qsj4UgDsRBEBXQZgUvkYFwKa7SlrH0CnHmUzlxIb87iCMQB+IG0jmm0T+AuBQ69gtxk+hw4na7L4NdigNxIA7EqYASRTV6cUihU78Q9110OHFxow0gjkAciBtA97eQH24On/NuGyOFe+kX4syncuJCfvlcnR3EgTgQpwKaLOAPT7oHNZBAx44h7n0SH05c3GhDknQCcSAOg6iAAn2JSlqTDRl06tmJM5/KiftQEMQl2VVBHIgDcSqgSe5Osb0IxNXYMJaeIc55KifuQ0GvikvSUUAciANxKqAHiCuugkk2r65j3xAnBThxu7DzVBBHIA7Ebd+aiZprAHEZNPcNceZTOXEhv36m1g7iQByIUwEt1u81OhSKYP39Yukb4pyncuI+FHSemuOxEMSBOBCnAlq0y0t0KFyKW13HziHOfConLqarJ3prEYgDcSBOBbRI3ZfwZiSb19bcuxNnPpUTF/L7J3osBHEgDsSpgBap++c2P8h7nRJr6R3inKdy4j50BnG2MBAH4lTAmg3k9Tm8DEDc2sXSP8TZjzhxu7Dz1Odt92BbGIgDccNXwCHy8POgCvrW3D/EOU/lxH2ATcx5aorHQhAH4kCcCrhb58iUNdnQuZb+Ie6rKHHidlHnqSCOQByIk7mf7o8mGzqvlQQQZz6VExfT19PsqyAOxIE4FXB3/wiFOJfi+tacAOLeruLEiYs6T03h7YM4EAfiVED91Xt9BnHZtGSAuF/FiRO3i3rJCIgjEAfirN6n2+NZGfQc7BQQZz6VE/ehL8OMp4I4EAfiVMC9OsdmrMmGnjXngLirSHHioi7FTQliBeJAHIhTAfUT92WVdqQbraglB8SZT+XEhfwJII5AHIjbuB6iu+MQrwTIGuwkEPduS+LERUHcZdtd2BYG4kDc2BUQPNdgsqFnzUkg7u0qVpy4oPPUDBc0QByIA3EqoP7i/fUPNNnQr5YsEGc+lRMXwjZJxlNBHIgDcSrgTp2jE3Y/QM9Nqoc0EGc+lRP3oVHGU0EciANxKqB63j6uE41X7WgtzXkg7ipanLigL29N/ccKxIE4EKcCqnePaaVKmGT0SlryQJz5VE7cv3SIgLiXTbdhEAfiQNzQFbCPd8xAXK87RSKIM5/KidsFXYpLsLOCOBAH4lTAfSp4KfrPfmTpEYjX/a6kORHEvV3FixMXcykuQUMBcSAOxKmA2mn703w9gLg+dcoEcc5TOXG7mDfFgTgCcSBO2n52rsE3G7rdKJZMEGc+lRO3C3pTXP+pBOJAHIhTAbWbx89b/ADTZBl1zAVxVxHjxMVA3LTlPgziQByIG7kC9musWWkpPErpNXTKBXHfRIwTt3t4AnG2MBCHRFTAz3T/EMLrGj/UpbgV94klF8Q5T+XE7WIuxfW/tYI4EAfiVEDlrH1dLx4gbg0ds0HcJGacuJDX/YI4AnEgTtZ+/szTZEOPOmWDuO9ixokLuRTX/1MhiANxIE4FVO4d/+cxvfgeCw9mhVAv2SDuTdA4cSGv+wVxBOJA3DZ1WGfJnpRCdzqmg7h3LM+Ji7kU131/B3EgDsSpgHtUcOFkpR/rUtxKOuVz4syncuJiIK77xwEQB+JAnAqom7SvKxl8IG6lXWLJB3HOUzlxMZMNII5AHIjb5M6+ylxDQEB0pGgdM0LcJG6cuD2I0zJBHIhTAdGtY1oJDk02rKM5I8SZT+XEhUw2XDbciUEciANx41bAfq0VKy0G32yI3iSWjBDnPJUTV/4Vvwz3M0AciANxKuAOndfqiiYbOtMxJcSZT+XEhUw2gDgCcSBOzt7QFX2zoTPNOZ0486mcuJDJBhBHIA7EbU8FV9cuK0dES4qN9JIT4pyncuJCJht67ycgDsSBOBVQdd2m1fCwq/1jKzpmhbhJ7IZ34kYYTwVxIA7EqYCq6/a8cjWYbAjVnBXizKdy4h5AnC0MxIERFfAXKrhssuKPdikufotYskKc81ROXMR46kvvFWoLA3EgTgVUbI5/y1gHENdTgeSFuEn0RnfiIsZTe3f2QRyIA3Eq4PbG8bReru634gJsQnNeiLuK3vBOXMB4au+bK4gDcSBOBdRctr9t777Z0JOWvBDnPJUTFzDZ0LuzD+JAHIhTATfry5rrpRo6qo/MEGdzGt6JA3G2MBAH4lTAn3VesyWabOhHc2aI877f4Z24iPFUEEcgDsTJ2BsIq/ibDZpSmJbMEPdV/EZ34iIgrvN2AuJAHIhTARX7xqVCTCZ5HVUeqSHu3e40uhMXMZ46bbYZgzgQB+IGrYD9qh3RZEM3mlND3NtVBAd34kCcLQzEwREV8Cd9WXe5lEMvWnJD3K8iOLoTdwZxtjAQB+JUQFzC1mi8JhuiqiM5xJlPHd6JO5RDXOe7K4gDcSBOBdRL2E/x1UFM+tCcHeKuYji4E7cHcdoliANxKiCsbVyqBGWS2CFaskOc+dTRnbiA8dTXzXZjEAfiQNyYFXBYubk/gLg+tof0EGc+dXQnDsTZwkAciFMBP+i89mo9qYceNKeHuLerKI7txO1A3N//gb/84+m1zb9flIxNC8SlytdP9kOTDV1oyQ9x5lMHd+Ii3jGydYhrqWclA+JA3AYhbi8oPewOG4A486mjO3HlENf5F2BAHIgDcbKw2ppdKkVlktnlOm4B4q7iOLYTd946aIA4EAfiZGG1NXup1JdAXICWLUDcN3Ec24kLeFFc390ExIE4ECcLqz3bfrYdKogONodNQJzz1MGduD2I61ivz0rGngXiEqXra62KMNlQruM2IO4qkkM7cSCOEwfiQByI+5+mUQHiTDa012kbEOc8dWwnLoBxXvruxyAOxIE4WVhpyS7VwjJJ7dK9YdkGxDlPHduJC2CcRxAH4kAciLNkt7R2kw3NddwIxL1LhaGduIB3jPS9vboTB+JAHIi7SecaaOVSXGudtuLEOU8d2omLeFEciOPEgTgQp8Hf1AxBXOutYdkKxDlP5cSBOBAH4kAciCvuGTc0w4OwtNVxOxA3iebITtwZxIE4EAfi0ELEil0qxsXOXabTdiDuu2iO7MSVQ1zfH091Jw7EgTgQV2nFXip2phe5XbQzLNuBuDfhHNmJO2ycNDhxIA7EycJKyXpLZ9/2EUj3Om4I4synDu3E7TdOGiAOxIE4WVgnWW96oDXZ0FSnLTlx5lNHduJAHIgDcSAOxEW0jJu46iwuLbVsCeKcp3LitnvB1p04EAfiwEKdBbtUDcwkue/XcVsQJxcGduIeQBwnDsRhEhAX4JBNVVuTmijQvC2IM586sBO3A3EgDsSBOBAXkKtT1aJwKa5Ay7YgznnqwE4ciANxIA7EgbiAXL3x+gOIa6fjxiDOfOrITlzxH9PVX7MtiHMnDsSBuDwd40aqMtnQTvPWnDjzqQM7ceUQx4njxIE4EGe9LpUjw325W8vWIM55Kiduq/sriANxIE4WVvHHXir3JkVxr47bgzi27LhO3BnEgTgQB+JAXHGq3trXi69byO47NW8P4pynjuvEgTh34kAciANxxal6c7mabGilZXsQ91VUOXHbbCWcOBAH4mRhleWq3nufpfd9Qd4gxJlPHdeJ24M4EAfiUAlUKF2uS/XQvEjvuzRvEOLeruI6qhN3AHEgDsShEhBX6o5N1ZvTo/S+S/9k7/6Sm8aWOABjp/TuG+P3ZDx6d8gGDGUWAFXa/1buwOXOMFQS4khHanV/vYGAzh99+p3TyZARcV+MqyQO4tyJgziIq424/Zzf5i7FLbMkUiJOf2rZJG78Hv8YeawkcRAHcWbhq/eL/ZyfXBC3SPU5EXc2spI4SRzEQRzEVUbcZtaP2dGdDTvz+w015ESc/tSqSdwEyoE4iIM4iCv+tN4wRW8gbokxToq4z3fGtmYSN145oW9uuRMHcRAHcTNkY7cL7E6WxRuqT4q4y9nYSuISbvKSOIiDOLPwtTXvrr4duy5cintDDVkRpz+1ahK3hziIgziIg7hxu+HdAuvi4c4Ev3qM0yJOf2rRJO7dI8RBHMRBHMSNelhvCsWOxmb26vIi7mx0SyZx4/83EOdOHMRBHMQtMDgnE/zaGvIiTn+qJA7iJHEQxyV1ETdint4uMjj+ZsPVb4TEiNOfKolL2OcOcRAHcWZh+3m6W2Rh6Gy4tvrEiLucjW/JJA7iIA7iIA7i5u9r0Nkwfx0yI05/qiQO4tyJgziIq4q4ef9ew7c6Zt58Q74QhsyI058qiYM4SRzEgUlVxN3Pfq55A3HzVpcbcWcjLImDOIiDOIiribi5+xomGB0L47o65EbcJyNcMYk7QhzEQRzEQdz8fQ3+ZsPc74MhN+Kcp5ZM4sYj7hR50boTB3EQB3GtN4u7pVaGt/Y11WVH3NkYS+IkcZI4iIO4ikwYcUHtYbHtd2eKX1GH7IjTnyqJk8RBHMRBXEnE3S+AuA3Ezfk6GLIj7mKQJXGSOIiDOIiriLj5+xp0NsxbXXrEfYZ6SZwkzp04iIM4iJvnQ3Z0Z8OjKf76OuRP4vSnFkzi7h9T/XckcRCnIG7mJ3W32NLwNxuueBsM+RHnPLVgEjcecZI4iIM4iKv8pN7+U3U2zFddBcSZD/WSuJvRm/xt4LGCOIiDOLOwMaceFtx/vbRfXYcKiPtqnCVxqV6w7sRBHMRBXONJervg+Ph1v69+GQwVEOc8VRIniZPEQRzElUPcmBaD0zI/FuKuqq4E4vSnSuIkcRAHcRBXDnGbhR6R8Zmr+hpJnP5USZwkDuIgjk0gbp4wTGfDXO+CoQbinKeWS+LGb/KSOHfiIA7iVl7HhRC3SfU6CVxdFcRRvSQO4iRxEAdxxRC3X2h26myYqfoqiNOfKolznApxEAdxtRA3psFgt9QPhrgraqiCOOepkjhJHMRBHMTVQtxmsSdkgOYZ4DqI2xltSRzEuRMHcXACca+rcT9ZZ8Ms1ddB3NloS+IgThIHcXBSCXEjpujI88x7i2OOGuogznmqJA7iIA7iIA7iZpmcOhtmWQeVEHdnvCVxEAdxEAdxdRC3XW4/30LcDNVXQpzf9yuJ053qThzEQVwhxG0WfEB7I9S+hkqI+2i8JXGSOEkcxEFcHSKM2AYfFl8dO9P8t8ugFOI+Y70kThIHcRAHcXUQt18QcRuro3n1pRB3ORvxSh9Oo3ujJHEQB3EQV3aGflh8iFyK+20NtRD3xYhXSuLGH6dK4tyJgziIW3Ntl9zOdTa0XwXFEKc/VRIniZPEQRzElUHcZtHnY4haV18NcWdjLomTxEEcxEFcESEcRzwfb5T4NVRDnP5USZwkDuIgDuKqIG7JvgadDe0XQTnE6U+t9N00HnG7wGPlThzEQRzENZygHwKMkUtxL1dfDnGXs1GXxOXI8iVxEAdxZmHDh7QLsEtB3Ms11EOc/lRJHMRBHMRBHMTN8UmeKxeI9xYoiDj9qYVW3PEx8wYCcRAHcWZhu01wkjsPENe0uoqIOxv3MknceMRJ4tyJgziIqzk/JznJvLE+WtZQEXH6UyVxkjhJHMRBHMTNMDN1NjR9CZREnP5USZwkDuIgDuIqIG6z9O43fpsy0Z+vriTiLmcjL4mTxEEcxEEcxLXf/VJvwkvXoSbiPhn5KkncHuLciYM4QCmMuOPia1RnQ8N3wFATcfpTyyw4iJPEQRzEVUbc0n0NE5yHWCDPVlcVcWdjL4mDOIiDOIhLH9UsPzF1NrSrQ1XEOU+VxEEcxEEcxN15RM03v61RavYKGKoiznmqJA7i3ImDOIiDuBk2P5fiWlVXF3HmhCQuwSYviYM4iDMLX6xjgO8siGtVh7qI+2r0JXEQB3EQB3E29GcrACStkBffAENdxF0Mf42PpkeIgziIg7iyiNtGmJc3OhvaVFcYcZ/FsyWSuK0kzp04iIO4uojbRNjKdTY0qkPlJE5/aokkbpubGpI4iIM4s7BVCDbdVp66v2zBF8BQGXHOU2skcY5TIQ7iIK4u4vYhPmB1NjSprjbiTApJnCQO4iAO4uznjfsadDY0qr424vSnVkjixu/xj5HHyp04iIM4iGu0RXyINE7m+hM11Eac89QKSdz4veMh6w4tiYM4iEuPuE2MnXzjk69BdcURpz9VEieJgziIg7jMdQzyOe5SXIPqqydx+lMlcZI4iIM4iLOdNw+/IK5BDdUR5zxVEgdx7sRBHMTZzpsfQhxTb8TLVAdxd2ZB+k+me4iTxEEcxFVF3CbKrLyBuMmrhzjnqfmTuNHff7HfrxAHcRBnFrZ5PicDFbsGiHOemj+JgziIgzhIKWuDY5iN3KW4yec+xOlPLZDEjf7PPN7mRtx/FixLBuIgLuzUnPjO6j71TrxE9RB3uZzNg+xfTHtJnDMKiIO4oitgG6WvQWfD9DVA3OXyxTyQxK06xIc4iIM4K6DJ4/kQbKQg7pcHCnH6UyVxayMpxEGcgrh5Hs/OSIWuHuKcpxZI4raSOBsjxFFK1RUQp69hgr14Z7r/XAPEfauPZkLuJG6C7k2IUxAHcfVm5l20VaKz4V8zH+L+15/qFZY8iYM4iIM4iCu6AgL1NehsmLh6iHOeWiGJG7/Fx97jIQ7iIM4KaPF0JifTDcRNWQPE6U+tkMRBHMRBHMRBXIBvcZvVpE8T4vSnSuKW+I2X9kVLBuIgbqYac4L54Y+Ja/zfsd6Z739XD3HOU0skcTfjk7jYn2NeYRAHcVZAg4n58BiudDb8UwPE6U8tkcQdxydxEKcgDuLWWBP8hqVQ5VLcP0MLcfpTayRxe4iDOIiDuJorYPMIcUmrgzjnqTWSOIiDOIiDuKIr4D4Z4uxWf9cB4pynSuIyvF4hDuIgzgpotv0Fq5MJ/2PjHyBOf2qNJC77XVqIgziIswKqIE5nw4/qIM55ao0kboI/2CCJUxAHcauMa7IZzqW4/9cB4n6uT2ZE2iRugou9u+TbNMRBHMR5NhC3Kp4PEOc8tUYSN8Eudoq9mL3CIA7irIAnK11fg1/3+6M6iPt3mReSOEmcgjiIMy0hbg11gDh/P7VIEneTvakd4iAO4qyAMoizUL5v+wPE/VImRdYk7jj+EgbEKYiDuDW+6vMZzqW479VBnP7UKkncBJ+iEKcgDuJKPpqAZca/C3SaGgdx+lOzJnH77F9+EAdxEGcFPFnHjIjbmfKBTlPjIM55atYkLv2XH8RBHMRZAY0+YSEuZHUQpz+1ShI3waUQSZyCOIhbY16TEnFWSqTT1ECI+2pepEziNhAHcRAHcSVXQMorcTobQp2mBkKc89ScSdwE21jwP9YHcRAHcVZAHcTZsCKdpgZC3OedzTBjEnczfss4xR4riIM4iLMCnqpjTsR5Vx8gTn9qmSRun37LgDiIgzgroNHuB3ERa4A456llkjiIgziIg7iSK2CbFHHll0oHcfpTJXF5dniIgziIswKaPBidDSGrhzj9qb+r0XcpoiRxU/yGEYhTEAdxEKezIciWP0Cc89QySdwU21j0FW1LhDiIswIafIu7FBdzwkOc89T2b6RTmv09fHYPcRAHcVZAizkJcSGrh7jn6mw7TJfEHSEO4iAO4iqugG1Ww1VfKwPEOU+tk8Tt828YEAdxEGcFtHguOhtCjivEPV9eZumSuPy/YQTiIA7irIBSiKu9ZfUQ5/f91knipjhQOAUfK4iDOIizApp8wboUF7AGiHu+PtoPkyVxmwL7BcRBHMRZARBXZbpD3Et/P9XbLFkSB3EQB3EQV3IF5O1rqH0proc4/amFkrgpmlPv0u/VEAdxEOexQNwqaoC4l+qLDTFXEjfFgUL+D26IgziIS1fHxIgrvGdtIE5/aqUkbl/gmw/iIA7irIAmX7AuxYWrHuKcpxZK4qa4FRL+3QpxEAdxVkAtxN2Wne0DxOlPLZTEbSAO4iAO4iqugMx9DYUvxW0gTn9qpSTufoLd4pR/szbpIQ7iPBWIW0H1EOc8tVISN8XV3l30sYI4iIM4K6DF5qezIVwNEKc/tVISt6+wWUAcxEGcFdBi89PZEG63D2e4r+8v+lMlcXF5s4bYHuIgDuKsgGKIK7pcunCGe/fuT+epkri4m/tj/F8TB3EQB3FWQJPNz6W4aBXNcN+7hP/UnyqJC7yPSeIUxEEcxEFchM0+Xg4XUHH6U/MkcVMcKMR/tUIcxEGcFfBLJe9rKLprdQFzuG917zxVEhcWcbvwYwVxEAdxVkCDzU9nQ7Q6xDRcNMU5T53iMy5CErd9hDiIgziIK7gCttkNV3K9bKMaLpribIpJkrgbiIM4iIO4iitgkx5xFS/FdWENF0xxZ7tijjtxk9wKqfDNDXEQB3GeCcRFr0inqX/8+o+LpLhPdsUcSVyRnQLiIA7irIAGX7A6G4Jt9YEM98QbPpLivNNS3InbQpzdEOIgruQK2OdH3KncRA90mvrkigmkuJ1tMUMStymyUUAcxEGcFTD9F2zwui030Q+xDffXSgyDuK+2xQx34u6L9LFDHMRBnBUw/ResS3HBdvrohoukONtihiRuD3EQB3EQB3EQl6K68IaLo7jPu/LbYoIkbjsJ4lawu0McxEGcFTD9F2z0qvaePsQ33F+vI/2pkrhQ36Jr+NiDOIiDOCsA4rLXGgwXR3HeSOtP4u4hDuIgDuIqroAKfQ3lVkyM09Qvv10rW/2pkrhpqsw2AXEQB3FWwLRPxKW4cNWvIYeLo7jy/anrT+Km+RY9rWCsIA7iIM4K+LnuayCu1Ma1XUcO9/2f+t55qiRufN2UuXUBcRAHcVbApJPRpbhw1a3GcDEUV74/df1J3HGSXWIVX2heYRAHcVYAxKWufj2Gi6G46v2p60/i6ly6gDiIgzgrYNItQWdDuApguGum4HvnqZK4pfd1iFMQB3E1H4jOhmi1/GnqdZ0CARRX/L22+iTuvs6XHsRBHMRZAZPvfjobIlW/phzue713niqJG1OF7lxAHMRBnBUw5Vx0KS5crSuHi6E4b6T/sndHW2k0SxiGYVycg+C5I3Iu4QbMv/B8myX3fytb3dnRP0YDTNd0f/W9vXIeobuqn6nuYpSjq9ClEIncDuJAHIgjAt5lBBCXb43rGa664sz7U9UrcYXOEzRSNlsYiANxREDpR1g6GxoaG0HDVVfcPTuS8CNSmUfRrUnKBnEgDsTlGRc2iPPpbJA03GRyy3kqlbiqj6Ia2yqIA3Egjgh4G2sbxNmkrrqnqf85/w+/pT+VSlzNR9G5xFyBOBAH4oiAgkuRS3GtjY1kHa664u7ZkWQrcUunxzwQB+JAHBHwlhBAXLpR03CXw/70mor7zo6kGlyFTlNFLlyAOBAH4oiAgl8HnQ2tTamu4SaTvmJ/qvPWpl2J60EciANxIA7EpR8eK3wjbLiqirtnRxKtxC2tnvJAHIgDcUTAr2HU1+DyzgZpw9VU3KNxWpSuxJV6Er3TmCsQB+JAHBFQ+hmWS3HtLPBqhrsp8wF6+lOpxFV5EhWZfxAH4kAcEfArH4C4bGNWy3DFvt2e81QqcRWeRFUq9SAOxIE4IqDctyF1nuqwwGsZrmBY9PSnUokbP4mppAcQB+JAHBEA4rKOTt9w1RRn3J+qXIkrdZpwqRLibGEgDsQRAYXzn8gwSF6bBIZ7jlLOU6nEHWuaUklsLjJXIA7EgTgiwBRx8/zr+yqD4Wopzvc8dXBvQL1K3IXbIx6IA3Egjggolg60xl365d3lMNzzB6E/lUrcqA+iW5+sDeJAHIjjy+BSXJNjlsRwlRR3z46k9nxULIfJ7KkgDsSBOCLg5+hBXLJR4zQ1aq+qgLh/2JHUKnHFboTMVeYKxIE4EEcElE6AdDY0kt+z1OFqKc51e5OtxJX7pUuZqQdxIA7EEQGuiJsnX93jn6Y+BoZDtxodcXPTtChbiSt2mKBTpQdxIA7EEQGlsoHayB43V5kMV0Nxru9PVa3ElSvEgTgGiANxft8Fl+LaSu+5DFdDcexIUpW4crd6L3WinC0MxIE4IuB1rEFcrjH2aWp83WpsxT3MPdOiaiXO8aYFiANxII4IKLQM6Wxoa4x8mvpjjB1rRX8qlbjPxkW5xzudzADiQByIIwJsEZe60NJlq8O9jhXnqVTiwjOYUI0exIE4EEcEFPoq6GxoaszS1eEqKG5umRY1K3EFf+dS50ociANxII4I8EVc6ktxVykNN7LiflimRc1KnGeJHsSBOBBHBLyONYhLNbqkhhtZcexIKgjqPfMCiANxII4IKLMKuRTX1JhlNdxkckt/KpW4yEIciGOAOBAnV7gBcbnGJq3hRlWcZX+qYiWuN00LIA7EgTgioMw3QWdDU7k9seFGVRw7kkQlruhDKIhjgDgQB+K4FFdzjHeaWqWT75b+VCpxUYU4qawA4kAciCMCXsYaxKUam8R1uFEV59ifqleJK1qIk9pPQRyIA3FEQJFFyKW4plJ76jrca+mF81Qqcb/GwjYpgDgQB+KIgJcxrKC16C8r/FuAuE8XdnbDjae4uV9alKvElb0MIpXTQRyIA3FEwOAv4k519tKGzkinqVVXf09/KpW4Qn+w7h0LEAfiQBwRMPiLmKvmr7SX4sYxXOUiVc95KpW414Vg3LIO4kAciCMCBq/Ba9XI2e2Sruv8dbjxFOe3yYlV4gr/xuVcaq5AHIgDcUTA0DVYrZy1dsvYx46NheFGUpzfeapYJa6s4bZaKR3EgTgQRwQMzQRb3elLijgTwz2vgBEQ950dqemoKnuYqnbDAsSBOBBHBAz9Hi51py9n7ExdDDeK4h7sdjmpSlzpFwbeac0ViANxII4IGPowO9dNYDk7GzY2hnteA/GKu2dHajgjLMoaThti1QAAIABJREFUTi2jgzgQB+KIANW+hoG/bpc3hRkZbgzFPbIjtVvNKnyYKvdUB+JAHIgjAoYuQeHQ2W3nCVd1uOHa2sboT/WtxE0LH6buLsXmCsSBOBBHBKj2NdDZ8OexMarDjaK4e7O0uJapxJW+EKeXD0AciANxRIBqXwOdDX8ewYR7bG7Ndyv6Uz0rcaUvxOndkQVxIA7EEQED75XcKWewhJ0NUzfDhSvOrT9V5k5c6QtxgnspiANxII4IkO1rKPI7n+ly2MbOcOGKu2dHarESV/xCnODtChAH4kAcESD6voZCiJtnW9Sxhmt1L1vRn+pWiQswnN5r+EAciANxRMCwb6Eq4noQ92E2Iw33o93NbEV/qlclrgswnN7lChAH4kAcETDVXX10NnwYM8M63OtYcZ5qVYkLMJzgEx2IA3EgjghYt5+w41KY4AnK18OyDhetOK/+VIlK3CLAcFu9dA7iQByIIwKWwt+AZeL+MqnbGi5ScV79qQqVuAjDKe6kIA7EgTj7CBh2t6Sygehs+G1sfA0Xqbh7dqSmQqqPMJxiKgBxIA7E2UeAcF9DgR+X13vRztfjythwk8kt56kWlbgYwynerABxIA7EgTjlxUdnw2853dpwgYpz2uqar8QFGU4xE4A4EAfi7CNg3XS+Ds9hud7ZMPM2XJzi7tmRmqnEBRlO8mIFiANxIM4+ApbSia/ApbhMWSzoNFXoyDlIcf+wI7WSFBZBhpN8mgNxIA7EuUeAdF8DnQ2/TaZ5HS5QcUZ7XdOVuC7KcJr7KIgDcSDOPQKm2k+vdDa8HzP3Olyc4uY+abHlSlyc4TQf5kAciANxIE567V1wKe7duMJwz6OPQNwPn7TYcCXuZhc3JOcKxIE4EOceActG0/VoSSwR4kJOU2/0vocQxbEj1a9p9cs4w2kW5EEciANxIE77CKJA+k6TxiJOU+eKX0SA4h7mNmmx1Upctww0nGgWAHEgDsSZR8CwJNDAO6vobHgbAaepogu8pz81XSXuJpJwqvV4EAfiQJx5BAz8Cup/ADob3oZWHa6P/N57zlPrhVREJa7rQw2n+iQH4kAciDOPgF596U1tH8I/jJlUHa4/7CMVN6U/NVMlLrArVToHgDgQB+LMI0D9ShyIexsbJcM9/7H7p8gjyin9qbV2pOKVuK7fBQ/VTRTEgTgQB+LEzyAKnLLkyGOdkOG6l+t7Ly+Wj/w/OE9NUokLPklVvhcL4kAciPOOgE7/49PZ8HPMhAz3+h+8wOhRR3E2/amNVeJulvGG25omcBAH4kCc9zfQwkegs+HnKHyaGl8zfKXRk47iXPpTm6rE3Sx2IwxZn4M4EAfivCOg1195F1yK+9+QqcP1h3eI2z8EErrjPFW7Enez2I5hON08DuJAHIjzjoBlI8m66iTmQFzR09TIU85fFcP9CIpblUScyX7XTCWuH6UKJ72FgjgQB+JAnPghREdnw79t1Ljh3n6R+P82evouojiT89Q2KnHdYoS7cPKXYkEciANx1hEwzfDpC6T6uwSLWcNw3bu3Sry1DAQ2qRZVHDvSWCbqF7vRCKdciQdxIA7EgTj13Ednw+tUFjRc4J7z/v951/gZeFJZUnEeG171Sly/HK8IJ96dDuJAHIizjoB1BsRxKe5llDtNDfxR2/7wZ8SF+qig4jzOU6tW4m4Wi93IQzn6QRyIA3HWEbDMUL8CcS9DoQ73GzT//StsgaupmOIeLdJirUpcd7NY7iqMO+G5AnEgDsQ5R8DAnoB5ik+hfqDycykL1OF+Lxb+9lu6gWWuFeepLVfiupsK9be3JzjlSQVxIA7EOUfANMeH34G4YqepcYZ739LwJ8SF1rlWnKeOuCNtF0ePXf0hvX8OR9yisfGNkBl9KDMGxA0arXyMtXkifxntG+7jf/aBSIFNqoUU931iMEY901xWR5x0Du922caWkBl9KNcw3BG3zhFtF1yKmwoa7iPiHvZx2eS2zPtTryf5x3LnNLRDH8QRMiDOGXEp+hqKdDbsJtpj07jh+sMxiNvvn+IWVRnF3YO4ZGMuPVcgjpABcc6l+GFL7y7J58hwKa5xw/3ZmH9C0lPcrbNb+lNBXLYaPIgjZECcMeKmWT67/fN4kdPUsCLYx5aGzxEX2TtwS38qiEv29AbiCBnva6EgLkewrYcvY+3Ohk3LdbjPDHf4rNgVFlS3nKeCuFz3KEAcIUMlzhhxyyzB1rufqjRdh/v0v9wrKs6gP9UJceov3ANxhAyIA3H6tasSnQ3KM9k1bLj+cDLi9k9hSaWnPxXEpUngII6QAXHeW/+wj36XK5Mpr+NNu4b76k/7gkpxpuQ8FcTlKcSBOEIGxHEjMMNHN1/Hgw0X9eG7L3n5hZTimlR7+lNBXJpCHIgjZECccRIYeJMsWQwJdzYMPk2NWsWftjT8FXH7/T9Rf1RPfyqIy1KIA3GEjPmzjDfilnlizbuzYdZqHe4v/+9fKl6tKu4exFGIA3EgjkociNNeeN9yTaTyVF61WYf764/X/aWDIKzkNR2GuH8myYcN4vQLcSCOkAFxvojrEs27dWdD16bh+sMwxD0zLmqT7ThPBXEpCnEgjpABcb5pYJpp3pfGC3nWpOGO6JjdiypOOeWBuFSFOBBHyIA4X8StM4Wac2fDVYuGO+aPOuJH2aJ+WneQ4n5Mcg8TxG0zVFRBHCED4mwRN3DdZRJpi5/o+DQ+xHBRf9NRsDzqBQlBETZIcSAuw0hRUAVxhIz3vQIQl6VuZdzZMGuvDnekK496QULUDbQBintIsf27I26bYq5AHCFDJc4VcXne11Aql4mu5PNPU6N+xKM/lENcXB9BR3+qNeJySBzEETIgzhVxF7mm3XYld80Z7uiXgB1b+Aq6gN5xnmqMuByFOBBHyIA4W8QN7Gu4zhdFmml9Jmu4w9HHl0GVr25Ff6ot4q5zzBWII2RAnGsqWOaKtLUr4s49TQ16BWh3wt9zwvtKY+LsbMXl7k91QNy3JHMF4ggZEOeKuGQ50LWz4dzT1B8N/Dl7XcWBOHEpJCnEgThCxrwsbYy4aTK6u3Y2zJqqw00PQYh7iOoIXdGf6oi4LIU4EEfIUIkDcUlmvUAYKf6C+6alOlx/iELc8wianRX9qX6IS1OIA3GEDIhzRdw62+f27GzoWjLcqaA8zU1PQXBacZ5qh7g8hVQQR8iAOFPELbMFmmdnw6whw53cYtFI+WtFf6oZ4hZ55grEETIgzhNxXbpA8+xs2DRjuO70NtmT5RTU3nBLf6oX4q7zzBWII2RAnGc6SHclzrOzoWvHcGf8JXtlxYE42XGZaK5AHCHj/VQD4hJpp8BalutZm7ZiuP4wCuIegt7ecEt/qg/itpnmCsQRMlTiPBG3zvexHTsbTj9NjUHQWYY7nPXjHq0oLnF/anLEpeI3iCNkQJwn4hLGmWNnQyN1uDPfGnHeT7TF6OmW81QTxF2mmisQR8iAOEvEdQnjzLCzYdpEHa4703CHM9+W8D1klk5W3PUk60iNuG2ueQNxhAyIs0RcwitxRRAntpg3TRjucBgXcUHtDT3nqQ6Im+eaKxBHyIA4S8T1Gefcr7OhBcNND6Mj7iGmCnai4r6DOA5TQRyISxAyytVpW8QtM35qu86GE/0UQu/+MD7iotobTlPcg3Lmc0XcNtukgThChkociDt9tPmh7DobTjtNva7/JxRDXBOKuwdxckCYZ5srEEfIgDhHxHUpw+zCra5cvQ53dkvDYMTtnx6rK+5xknTkRdy3dHMF4ggZEOeIuIGf+zLrbO52d0qzWLsON9BwgxAX1KQ6pT81MeK2+eYKxBEyIM4RcSn7GorkM6V7z5vahjscaiIupr2h4zy1xL0EDlNBHIgDcSCu1SV3nTeSlDJIZcP1/2XvjJYax4EoijXwjhPn3c7Y75PZHwBK+QCm0v//K8uAHQIZEodIttR9TtXW7jAQ1lhtHVq6kcwrcS+s5+3Fac2nau3ELRTeKySOkvnPqMYgcQqrzFaywc3rcJ3ML3FR4g3OfD5VqcSVGu8VEkfJ0IkzKHFOaZXZervf8RY173ePKHFxzuBy1tdTdUqcuncXQeIoGSTOqsT9ULoqYUvi5uzDXRtpCCVxcU5vcMbzqSolbnOv8l4hcZQMEmdQ4q7MNfzS/EDL5k0IxmZTo1iOSCoSN6/Ffed7u/U68aeGSonTuCEOiaNkkDibErfUes2WHiF38zlcIQlJ3HYb4QnkYq2nutL73W7nF4qfD2yIQ+KQOCQOiYtZ91eOuHSvLESyIZc7Op/DNZKSxL0QwYhcNeo7X7olr/G7/r2KU36TOYUSp3NDHBJHyQQh5+qwKXGF2iL7YeeXEjebw3WSmsTtIsQbRlrcZT/fDwdCpDvQ9EncJudWAxKHxNGJQ+JCXvVC8/3MZjyPWk39E2HSWElyEnd5RyyYxT1cNDy3H9Qz2WeHPolT63BIHCWDxBmUuCsXHX/pfqJlkmwY41IRwpNOJEWJixJvqALnU4v3Nyhukj62S53EKQ01IHGUDBJnU+KWei95aeUZ4mbqw0miEhelrzXG4i54uQ/b96qET3zQJnFaQw1IHCWDxJmUuCtzDUlvEQ4hcVnc0rtZHK6RZCUuyukNVcj11PbjPriXP27v1VYRDofEIXFIHBKX5EVvdN/QTAb0ag6H6yRhiZvJ4kbvxnOfY7RtsguquiRObTAViaNkApFziSBxyvaMWZE4p8HhAkuc30XYA9gGy6dWR1276pI+HjMSDofEIXF04pC4G825BjvJhrvpHS5kLDWOxHn/GL4cz1rcyOefO94C5y7bUseM9C0lyHl+QuKQOCQOiTtG9f028hBZTe9wIulL3C5CSPWcxf0Z/TJHlXOb6JvFKZK4jeJgKhJHySBxJiVOc64hTDGlf0/PGVX4mauQHCTObyPsMmuD5FMPGnH1vz7IjBRFCH7fKAeJo2SQOGsSV6guscbEiL6bug/XSB4SFyXecNrixkVM+6Zb8VyuyvK5XNT9R7cpPkD0SJz2PhwSR8kgcfYk7krN+a39juYwolcT9+E6yUbipre4p5Ev8fqvrXjZbWW3ex1kzvsHZiQcDolD4malzrgCLErcUrXhOAtPETetw0WINESUuBgh1ebq9dTe1m4/vTlxm+R66hKHQ+KQODpxSBwSl2k1Jf8UudPhcJEkLkZItbkyn1r0b0XSfmrgFUnmU5c4HBKHxCFxSJzOoq8NVFPq19hN6XBrkdwkLkK8obkun3rbd9zaN3dbV0MDL8l86k8cDolD4pA4JE7nJad+fQaSDadWU8Pbi+QncZ/PRohtcee/uup3zrX+4ab+G0+t+tZcNf7IB2YkHA6JQ+KQOPMSpzvXYCLZcGI1Nfj/eSdZStx2O6HFjcinDg23vxL3Nkz7r7pNcVPcEodD4pA4JA6JUznafll4qCX+GOkm68PF2w4XuxPnd8E7XMX386luOJ1rL3FD0iHJTXFLHA6JQ+IMlUydcQUgceqU3S3VP0a0OFxUifM++OkNxbfXU4uh33a7l7hB57yvmZGCe4AZh0PiKBk6ccYkzqm/XvXJhrvJHE4kZ4mb0OLOfaP9omn7D4m7Z0bC4ZA4JA6JQ+KmuOIMCkx9sqGbyOEKyVzittvQ99F9cz21HT7juBNX7T/CjBTKAnJeHELikDgkDok7xU/1BfZD+46aL4wouAxL7hIXIaT6hcWdCyfsTe24E9cmGE9d4nBIHBKHxCFxGgfbvY2nWsrPkWKaPlwnGiQueLzBfSuful8zfZe4wetukTgcDolD4pA4JA6JG55qypMN/7Sr0Nu/YkcappI476exuKdzEld/kjg3eF2K7zGS84xU2nI4JI6S0b4JHIkLe8G1kXpK+DoncTgRLRIXPN7wT4t7vlTi2iHSWiBxRBqQOCSOThwSN80F53CNupMNhR6Hm0bi+qPmA/50qovzqYcS9/Jfbv3evEPiQs7/OU9GSBwSh8QhcWf5aaC+QiQb0j2YoovvcI2okjgf+vSGf1nc05nm3c2+AffqlS//PAwS98yMxFIqEofEIXFI3ARjbWHjpqb8IDlWoT+Bv0MnyiRuCot7vEjiDr7AJXhkQ6YzUnljECSOkkHiLEmcM3GvNScbjldTQzdyVqJO4vwu9A/pyOK29YUSV+//juVUtsMhcUgcEofETXG9tZmCSvVKu8h9ODehw00ncd4/Br6hRxb3cOqzDyWuWlWHq6/siWMpFYlD4uYl5+JB4jSW10/Fv5rEdjgRnRK3DX1EaXVJPnXfeLt9lb1D5UPiaMMhcUgcnTgkbhK92Vi5q8kmG4q4DteIVonzPrbF1WMkrn3Vt5ZOHG04JA6JQ+KQuIvRaTYxJC7RJ0mnyeEmlrjQ8YZq/Hrq/sSGt05ccaB8nNhAGw6JQ+KQOCRukoLP5FYrPrMhqsN1olrigltcOzqf+vnYrQPlQ+JowyFxSBwSh8RNcrm1oYpKcli7iA43aaRhFokLHlJtx+ZTq0Ha3jpxL1/4+P4aSBxtOCQOiUPikLgJLjeX61SbbPjQKws7e83gcJNLXPCQajtyPXVvan0n7mA9tfL3zEjfn/Btt+GQOEomCDlXkTGJWxqprh9akw0R+3AiFiQudEi1Hbeeuj/lvu/EHayneiTuipXU9Y11kDhKhk4cEqcs16A32eCi9eEasSFxL4T9wbWj8qnF8EZxfSfufT3VBU/N2pmRSuMrqUgcJYPEGZM4Z+VOO6WPkrtYDteJGYnbBo43NGPWU/eqNkhcMWyhu03w1K08ZqTNIuc1ICQOiUPikLjJr7Y2VVIJjutVJIdbiR2J834XNknwbnEnXtfvO3APnz/wxIz0DZYoHBJHySBx1iSuMVNcOpMNLo7DubkcbiaJ8/45aNU2I9ZTq97VbgeJG9ZTT/XvmJHowiFxSBwSh8SFGmgbS/c1xR2A+9XUoNu5nYg1iYtlcV8/CodV030nrs+nJrklLvUZiS4cEkfJBCXngkLiNOYatCYbho5Z0F+bCjEocVsf1IMHi/s6MOx6w9t34voWXJJb4tKekUoUDomjZOjEmZS4a8v9l60n2ya1Wzu0zMJ2kcSixIU+vaHoX/Xrz+h3zO07cf16qk9xS1zCM9KGNxVB4pA4JM6qxP0wdKM1JhvuIjhcJ0YlLnC84c3itl8PmfYtjvreiXtdSC38/s8p8TPRqaZcoHBIHBKHxJmVuCsfzZvaWE2lNrBXwR1uvkjD/BLn/VPIH2VxJp/a65r7+NEqydXUNGckmnBIHBKHxNmWuKWh2lKYbHDhHU7EssSFjTe4M+up1VGEof77NU/MSCMNrr4BJA6JQ+KQOAu5BpXJhrvQDleIcYkLewaXO5tPfTovdsxIGBwSh8RNTc71ZUniCkuy7vSN7FVgh2vEusQFjje4k/nUm+Mtc7cnz1tlRmIVFYlD4ujEIXE2JU7fpjgnEnT5rxMkLoLFff237Yuy1Z8//Z4Z6QRLWnBIHBKHxCFxPZZyDQol7i6sw60EiXsNqYbshbmTVvZpF57zie6IS2JG2mzKtUPRkDgkDolD4kINM2t3NrFdgKvnkE/+JBwuBYnz28eAJexOaVnxweLc6RyE4RlpWZbrGoFD4pA4JA6J+1DslnINCpMNQR1OBImLEm9wp7ysPVi/rf5+6wdmpE/2Vi7ovyFxSBwSh8RFuNRfBp9tCd3ckFNbI0jcB0Le51OvVb0642NZer9LdzH1ZYBspuKvtpXNYrFe16d/dPDlL+fKKH/nOA7KTc4scy694uohVxu51DI3Vw8xtnXOKsk4XDISF/YMrhMT7mv/zfcX/ngDAAAAMJ5OkLgji5uoKeYOD4xgKAIAAMAFGrESJC726Q0naIZvyLufAQAAwCUOJ4LEzWpxrqy8LxcMRQAAAMjW4ZKSuG2aJ2ABAAAApBRpSE/iWOEEAACAVOkEiUshpAoAAABwCStB4s6cwUViFAAAAFLDpedwyUncdPEGAAAAgJEOJ4LEYXEAAACQGY0gceP2xbExDgAAAHC4/CSOeAMAAACkQydIHPEGAAAAyIwUIw0pS5z3jzWjBgAAAGZ3OBEk7sJeHPEGAAAAmJtGkLhvhFTvGTkAAACAw2UncYRUAQAAYFY6QeIIqQIAAEBurASJ+7bFPTJ+AAAAYBZc4g6XtsQRUgUAAICZHE4EibuqF+exOAAAAJicRpC4q1kzjgAAAGBaOkHiiDcAAAAADmdS4jiDCwAAAKYk+UhDNhLnPac3AAAAwGQOJ4LEYXEAAACQGYUgcYRUAQAAIDcaQeICQ7wBAAAAotMKEke8AQAAADIjk0hDZhLnPRYHAAAAOFyGEke8AQAAAGI6nAgSh8UBAABAZjSCxMULqW7vGWEAAACAw+UmcYRUAQAAIA6dIHGRQ6rPjDIAAAAITF6RhjwlzvvHmpEGAAAA1h0uQ4kj3gAAAABBaUSQOM7gAgAAABwOiftK44g3AAAAQCA6QeKwOAAAAMiMLLfDZSxxfvfIoAMAAADDDperxPktIVUAAAC42uFEkDjiDQAAAJAZjSBxs7Bm7AEAAMD36QSJm4knRh8AAPzP3t3bNgxDURiFMoEb91KhRQRmgAC5+6+SIl2QH9lN9B7PGcFg8QHUNeFZ94g4FQcAFPNSvOGKR5zXGwCA5xouEXEqDgAoZomI+/eR6s05BAAes0XEXeB/f73eAAA8ZI+Iu0TFmTcAAOeVnzS0iTgjVQBguobrEXHmDQDA2YZLRNyVrlRVHABwwhYRd7GVqnkDADBRw7WJOBUHAPxpj4i74I3qm5MJAPyiy6ShW8SNcaxOJwDwY8MlIs5IFQAoZomIu/AbXCoOAPjWFhFn3gAAVLNHxKk4AKCYXpOGnhE3Xo/VSQUAujdcv4gzbwAAvjRcIuLMGwCAYraIuDJ8GAcA9G64phFn3gAAfLpHxJV6g+twZgGAnpOG1hFn3gAAdJ00NI84FQcA01si4kqOVG/OLgDMbIuIK/phnHkDAExsj4gzbwAAimk8aZgg4sZQcQCg4USceQMAUKThEhFX/UpVxQHAdLaIOK83AADV7BM03PuYgIoDgLmsfgIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPhgDw4EAAAAAID8XxtBVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVYU9OBAAAAAAAPJ/bQRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUkc9vRAAAGEUlEQVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVFfbgQAAAAAAAyP+1EVRVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV2IMDAQAAAAAg/9dGUFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVpDw4JAAAAAAT9f+0NAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACMBvGf1L6br2nwAAAAASUVORK5CYII=';

    // Filas del manifiesto
    let rows = '';
    marked.forEach((r, i) => {
        const bg      = i % 2 === 0 ? '#fff' : '#F7F9FC';
        const awb     = esc(r.awb || r.shipment_number);
        const dam     = r.dam     ? esc(r.dam)   : '';
        const dateDam = r.date_dam ? (()=>{
            const d = new Date(r.date_dam+'T12:00:00');
            return d.toLocaleDateString('en-GB',{day:'2-digit',month:'2-digit',year:'2-digit'});
        })() : '';
        const shipperName = esc((r.shipper_name   || '').toUpperCase());
        const consgName   = esc((r.consignee_name || '').toUpperCase());
        const hawbs       = r.hawb_count || '';
        const desc        = esc((r.description || '').toUpperCase());
        const pcs         = r.pieces || '';
        const kg          = r.gross_weight ? parseFloat(r.gross_weight).toFixed(2)+' Kg.' : '';
        const orig        = esc((r.routing_orig || r.flt_orig || station || 'LIMA, PE').toUpperCase());
        const dest        = esc((r.routing_dest || r.flt_dest || fltDest || '').toUpperCase());

        // Celda AWB + DAM debajo
        const awbCell = `<div class="awb">${awb}</div>`
            + (dam ? `<div class="dam-line">DAM ${dam}</div>` : '')
            + (dateDam ? `<div class="dam-line">${dateDam}</div>` : '');

        rows += `<tr style="background:${bg};">
            <td class="tc seq">${i+1}</td>
            <td class="awb-cell">${awbCell}</td>
            <td class="tc pcs">${pcs}</td>
            <td class="tc wgt">${kg}</td>
            <td class="tc hawb-no">${hawbs}</td>
            <td class="shipper-td">${shipperName}</td>
            <td class="tc" style="font-size:7.5px;">${orig}</td>
            <td class="consignee-td">${consgName}</td>
            <td class="tc" style="font-size:7.5px;">${dest}</td>
            <td class="goods-td">${desc}</td>
        </tr>`;
    });

    const html = `<!DOCTYPE html><html><head><meta charset="UTF-8">
    <title>${isClosed ? 'AIR CARGO MANIFEST' : 'AIR CARGO PRE-MANIFEST'} ${mn} ${flt} ${date}</title>
    <style>
        * { box-sizing:border-box; margin:0; padding:0; }
        body { font-family:Arial,sans-serif; font-size:9px; color:#000;
               margin:0; padding:4px; background:#fff; box-sizing:border-box; }

        /* ── Top header row ── */
        .top-hdr { display:grid; grid-template-columns:160px 1fr 200px;
                   border:1.5px solid #000; margin-bottom:0; }
        .top-hdr .logo-cell { padding:6px 10px; border-right:1px solid #000;
                              display:flex; flex-direction:column; justify-content:center; }
        .top-hdr .logo-cell img { width:130px; }
        .top-hdr .logo-cell .carrier { font-size:7px; color:#555; margin-top:2px;
                                       letter-spacing:.04em; }
        .top-hdr .title-cell { display:flex; align-items:center; justify-content:center;
                                border-right:1px solid #000; }
        .manifest-title { font-size:16px; font-weight:900; letter-spacing:.08em;
                          text-align:center; border:2px solid #000; padding:6px 20px; }
        .top-hdr .meta-right { padding:5px 8px; font-size:8.5px; }
        .meta-right .mfto { font-size:11px; font-weight:900; color:#002244; margin-bottom:4px; }
        .meta-right .meta-line { margin-bottom:3px; }
        .meta-right .meta-line strong { color:#002244; }

        /* ── Sub header rows ── */
        .sub-hdr { display:grid; border:1.5px solid #000; border-top:none;
                   margin-bottom:0; font-size:8px; }
        .sub-hdr.row1 { grid-template-columns:1fr 1fr; }
        .sub-hdr.row2 { grid-template-columns:200px 1fr 200px; }
        .sub-cell { padding:3px 8px; border-right:1px solid #000; }
        .sub-cell:last-child { border-right:none; }
        .sub-label { font-size:7px; color:#555; text-transform:uppercase; }
        .sub-val   { font-size:9px; font-weight:700; }
        .sub-cell.span2 { grid-column:span 2; }

        /* ── Consolidation header ── */
        .consol-hdr { border:1.5px solid #000; border-top:none; padding:3px 8px;
                      display:grid; grid-template-columns:140px 1fr; font-size:8px; }
        .consol-lbl { font-weight:700; color:#444; border-right:1px solid #ccc;
                      padding-right:8px; font-size:7.5px; text-transform:uppercase; }

        /* ── Table ── */
        table { width:100%; border-collapse:collapse; font-size:8.5px;
                border:1.5px solid #000; border-top:none; }
        thead th { background:#002244; color:#fff; padding:4px 3px;
                   text-align:center; border:1px solid #001422;
                   font-size:7.5px; text-transform:uppercase;
                   white-space:nowrap; vertical-align:bottom; }
        thead .th-top { background:#1A3A6B; }
        td { padding:4px 3px; border:1px solid #C8CDD5; vertical-align:top; }
        .tc   { text-align:center; }
        .seq  { font-weight:700; color:#002244; width:22px; }
        .pcs  { font-weight:700; }
        .wgt  { text-align:right; font-weight:700; }
        .awb  { font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em; font-weight:700;
                font-size:9px; color:#002244; }
        .dam-line { font-size:7.5px; color:#444; margin-top:1px; }
        .awb-cell { width:90px; max-width:90px; word-break:break-all; }
        .shipper-td, .consignee-td { text-transform:uppercase; font-size:8px;
                                     min-width:170px; }
        .goods-td { text-transform:uppercase; font-size:8px; min-width:110px; }
        .hawb-no { width:40px; }
        tr:nth-child(even) td { background:#F4F7FB; }

        /* Totals row */
        .tot-row td { background:#DCE8F5; font-weight:700; border:1px solid #9BBAD8; }

        /* Footer */
        .footer { margin-top:8px; display:flex; justify-content:space-between;
                  font-size:7.5px; color:#888; border-top:1px solid #ccc;
                  padding-top:5px; }
        .footer .stamp { font-size:8px; color:#002244; font-weight:700; }

        @media print {
            @page { size:A4 landscape; margin:5mm; }
            body  { margin:0; }
        }
    </style></head><body>

    <!-- ═══ TOP HEADER ══════════════════════════════════════════ -->
    <div class="top-hdr">
        <div class="logo-cell">
            <img src="${DELTA_LOGO}" alt="Delta Air Lines">
            <div class="carrier">DELTA AIR LINES, INC</div>
        </div>
        <div class="title-cell">
            <div>
                <div style="font-size:7px;text-align:center;color:#555;margin-bottom:4px;">
                    DEPARTMENT OF THE TREASURY &nbsp; UNITED STATES CUSTOMS SERVICE<br>
                    19 CFR 122.48, 122.52, 122.54, 122.73, 122.113, 122.118
                </div>
                <div class="manifest-title">${isClosed ? 'AIR CARGO MANIFEST' : 'AIR CARGO PRE-MANIFEST'}</div>
            </div>
        </div>
        <div class="meta-right">
            <div class="mfto">MFTO: ${esc(mn)}</div>
            <div class="meta-line"><strong>${esc(flt)}</strong></div>
            <div class="meta-line"><strong>${esc(dateLabel)}</strong></div>
            <div class="meta-line" style="margin-top:4px;font-size:7.5px;">
                ETD: <strong>${esc(etd)}</strong> &nbsp; Sal.Alm.: <strong>${esc(whe)}</strong>
            </div>
        </div>
    </div>

    <!-- ═══ SUB HEADERS ══════════════════════════════════════════ -->
    <div class="sub-hdr row1">
        <div class="sub-cell">
            <div class="sub-label">2. Owner / Operator</div>
            <div class="sub-val">DELTA AIR LINES, INC</div>
        </div>
        <div class="sub-cell" style="border-right:none;">
            <div class="sub-label">3. Marks of Nationality and Registration</div>
        </div>
    </div>
    <div class="sub-hdr row2">
        <div class="sub-cell">
            <div class="sub-label">4. Port of Lading</div>
            <div class="sub-val">${esc(station)}, PE</div>
        </div>
        <div class="sub-cell" style="text-align:center;align-content:center;">
            <span style="font-size:9px;font-weight:700;"></span>
        </div>
        <div class="sub-cell" style="border-right:none;">
            <div class="sub-label">5. Port of Unlading</div>
            <div class="sub-val">${esc(fltDest)}</div>
        </div>
    </div>
    <div class="consol-hdr">
        <div class="consol-lbl">Items 8 &amp; 9 for<br>Consolidations only</div>
        <div style="padding-left:8px;">
            <div class="sub-label">8. Consolidator</div>
        </div>
    </div>

    <!-- ═══ MANIFEST TABLE ═══════════════════════════════════════ -->
    <table>
        <thead>
            <tr>
                <th rowspan="2" style="width:22px;">№</th>
                <th rowspan="2" style="min-width:95px;">
                    10. Air Waybill No.<br>
                    <span style="font-size:6.5px;font-weight:400;">(+DAM / Date DAM)</span>
                </th>
                <th rowspan="2" style="width:30px;">12<br>No.of<br>Pieces</th>
                <th rowspan="2" style="width:55px;">13<br>Weight<br>(LB/KG)</th>
                <th rowspan="2" style="width:55px;">14<br>No. of<br>HAWBs</th>
                <th colspan="2" class="th-top">15. Shipper Name and Address</th>
                <th colspan="2" class="th-top">16. Consignee Name and Address</th>
                <th rowspan="2" style="min-width:90px;">17<br>Nature of Goods</th>
            </tr>
            <tr>
                <th style="width:95px;background:#1A3A6B;">Name</th>
                <th style="width:55px;background:#1A3A6B;">City, Country</th>
                <th style="width:95px;background:#1A3A6B;">Name</th>
                <th style="width:55px;background:#1A3A6B;">City, Country</th>
            </tr>
        </thead>
        <tbody>${rows}</tbody>
        <tfoot>
            <tr class="tot-row">
                <td class="tc" colspan="2" style="text-align:right;padding-right:8px;font-size:8px;">TOTALS</td>
                <td class="tc pcs">${totalPzas}</td>
                <td class="tc wgt">${totalKg.toFixed(2)} Kg.</td>
                <td colspan="6"></td>
            </tr>
        </tfoot>
    </table>

    <!-- ═══ FOOTER ════════════════════════════════════════════════ -->
    <div class="footer">
        <div>
            <span class="stamp">${isClosed ? '\uD83D\uDD12 MANIFIESTO CERRADO' : 'PRE-MANIFIESTO (BORRADOR)'}</span>
            &nbsp;&nbsp; SIGHA · Sistema de Gesti&oacute;n GHA
        </div>
        <div>Impreso: ${new Date().toLocaleString('es-PE')}</div>
    </div>

    <script>window.onload = () => window.print();<\/script>
    </body></html>`;

    const win = window.open('', '_blank');
    win.document.write(html);
    win.document.close();
}


// ════════════════════════════════════════════════════════════════
//  ULD BUILDER MODULE  (uld*)
//  Tab "ULD Builder / Load Planning" dentro de Pre-Manifest
// ════════════════════════════════════════════════════════════════

let uldData = { ulds: [], awbs: [] };
let uldDragShipmentId = null;

function mfSwitchTab(tab) {
    document.getElementById('mfTabContentPre').style.display = tab==='pre' ? '' : 'none';
    document.getElementById('mfTabContentUld').style.display = tab==='uld' ? '' : 'none';
    const btnPre = document.getElementById('mfTabPre');
    const btnUld = document.getElementById('mfTabUld');
    if (btnPre) btnPre.classList.toggle('mf-step-tab-active', tab==='pre');
    if (btnUld) btnUld.classList.toggle('mf-step-tab-active', tab==='uld');
    if (tab === 'uld') {
        // Populate flight info bar from Pre-Manifest fields
        const setInfo = function(id, val) {
            const el = document.getElementById(id);
            if (el) el.textContent = val || '—';
        };
        setInfo('uldInfoStation', document.getElementById('mfStation')?.value);
        setInfo('uldInfoFlt',     document.getElementById('mfFlight')?.value);
        const dateVal = document.getElementById('mfDate')?.value;
        setInfo('uldInfoDate', dateVal ? (function(){
            const d = new Date(dateVal+'T12:00:00');
            return d.toLocaleDateString('es-PE',{day:'2-digit',month:'2-digit',year:'numeric'});
        })() : '');
        setInfo('uldInfoMn',  document.getElementById('mfManifestNumber')?.value);
        setInfo('uldInfoWhe', document.getElementById('mfWhExitTime')?.value);
        setInfo('uldInfoEtd', document.getElementById('mfEtd')?.value);
        // AWB totals from mfRows
        const marked = (mfRows||[]).filter(function(r){ return r.in_manifest==1; });
        setInfo('uldInfoAwbs', marked.length || '0');
        setInfo('uldInfoPzas', marked.reduce(function(s,r){ return s+(parseInt(r.pieces)||0); }, 0));
        const kg = marked.reduce(function(s,r){ return s+(parseFloat(r.gross_weight)||0); }, 0);
        setInfo('uldInfoKg', kg.toLocaleString('es-PE',{minimumFractionDigits:2,maximumFractionDigits:2}));
        uldLoad();
    }
}

async function uldLoad() {
    const flt     = document.getElementById('mfFlight')?.value || '';
    const date    = document.getElementById('mfDate')?.value   || '';
    const station = document.getElementById('mfStation')?.value || 'LIM';
    const awbWrap = document.getElementById('uldAwbList');
    const uldWrap = document.getElementById('uldList');
    if (!flt || !date) {
        if (awbWrap) awbWrap.innerHTML = '<div class="empty-state">Select flight and date first.</div>';
        if (uldWrap) uldWrap.innerHTML = '';
        return;
    }
    try {
        const params = new URLSearchParams({flt_number:flt, flt_date:date, station});
        const res = await apiFetch('/shipments/flight_ulds_api.php?' + params);
        if (!res.success) return;
        uldData = res;
        // Ordenar ULDs por la prioridad de las guías que contienen (Correcciones
        // Pre-Manifest / ULD Builder #4): la prioridad efectiva de un ULD es la
        // MENOR prioridad (mayor prioridad operacional) entre sus AWBs asignados.
        // Los ULDs vacíos van al final de su grupo de condición. Este auto-orden
        // se recalcula cada vez que se recarga uldData (p. ej. tras asignar o
        // quitar una guía) — el reordenamiento manual (Drag & Drop de ULDs) no
        // pasa por aquí, así que sigue disponible sin que este auto-orden lo pise
        // de inmediato.
        if (uldData.ulds) {
            const effectivePriority = function(u) {
                const awbs = u.awbs || [];
                if (!awbs.length) return Infinity;
                let best = Infinity;
                awbs.forEach(function(a) {
                    const master = (uldData.awbs || []).find(function(x){ return x.id === a.shipment_id; });
                    const p = parseInt(a.priority) || parseInt(master && master.priority) || 0;
                    if (p > 0 && p < best) best = p;
                });
                return best;
            };
            uldData.ulds.forEach(function(u) { u._effectivePriority = effectivePriority(u); });
            uldData.ulds.sort(function(a, b) {
                const condOrder = function(u) {
                    if (u.uld_condition === 'OK' || (!u.uld_condition && !u.is_bulk)) return 0;
                    if (u.uld_condition === 'DAMAGED') return 1;
                    if (u.uld_condition === 'EMPTY') return 2;
                    if (u.is_bulk) return 3;
                    return 2;
                };
                const ca = condOrder(a), cb = condOrder(b);
                if (ca !== cb) return ca - cb;
                return a._effectivePriority - b._effectivePriority; // ties keep creation order (stable sort)
            });
            // Assign sequential priority based on sorted position
            uldData.ulds.forEach(function(u, i) { u._displayPriority = i + 1; });
        }
        uldRenderAwbs();
        uldRenderUlds();
        uldRenderSummary();
    } catch(e) { console.error('ULD load error', e); }
}

// ULD type images (SVG inline representations)
// ULD image system: tries /scargo/assets/uld/{TYPE}.png, falls back to inline SVG
const ULD_IMG_BASE = (function() {
    const parts = window.location.pathname.split('/');
    const base  = parts.length > 1 ? '/' + parts[1] : '';
    return base + '/assets/uld/';
})();
const ULD_SVG = {
    // Wide pallets (PMC/PAG/PLA/PLB) - flat rectangular
    PMC: `<svg viewBox="0 0 80 52" width="80" height="52">
        <rect x="2" y="12" width="76" height="32" rx="3" fill="#C8D8EE" stroke="#002244" stroke-width="2"/>
        <rect x="6" y="6" width="68" height="10" rx="2" fill="#002244" opacity=".75"/>
        <rect x="4" y="40" width="72" height="6" rx="1" fill="#001433"/>
        <line x1="2" y1="22" x2="78" y2="22" stroke="#002244" stroke-width="1" opacity=".4"/>
        <line x1="2" y1="32" x2="78" y2="32" stroke="#002244" stroke-width="1" opacity=".4"/>
        <rect x="12" y="8" width="56" height="6" rx="1" fill="#FFC72C" opacity=".5"/>
    </svg>`,
    PAG: `<svg viewBox="0 0 80 52" width="80" height="52">
        <rect x="2" y="12" width="76" height="32" rx="3" fill="#C8D8EE" stroke="#002244" stroke-width="2"/>
        <rect x="6" y="6" width="68" height="10" rx="2" fill="#002244" opacity=".75"/>
        <rect x="4" y="40" width="72" height="6" rx="1" fill="#001433"/>
        <line x1="2" y1="26" x2="78" y2="26" stroke="#002244" stroke-width="1" opacity=".4"/>
    </svg>`,
    PLA: `<svg viewBox="0 0 80 52" width="80" height="52">
        <rect x="2" y="8" width="76" height="36" rx="2" fill="#FFE8C0" stroke="#E07B00" stroke-width="2"/>
        <rect x="6" y="4" width="68" height="8" rx="2" fill="#E07B00" opacity=".8"/>
        <rect x="4" y="40" width="72" height="6" rx="1" fill="#A05500"/>
        <line x1="2" y1="24" x2="78" y2="24" stroke="#E07B00" stroke-width="1" opacity=".4"/>
    </svg>`,
    PLB: `<svg viewBox="0 0 80 52" width="80" height="52">
        <rect x="2" y="8" width="76" height="36" rx="2" fill="#FFE8C0" stroke="#E07B00" stroke-width="2"/>
        <rect x="6" y="4" width="68" height="8" rx="2" fill="#E07B00" opacity=".8"/>
        <rect x="4" y="40" width="72" height="6" rx="1" fill="#A05500"/>
    </svg>`,
    // Containers (AKE/AKH/LD3/LD2) - upright rectangular
    AKE: `<svg viewBox="0 0 60 72" width="60" height="72">
        <rect x="4" y="2" width="52" height="64" rx="4" fill="#C8E8DC" stroke="#0F6E56" stroke-width="2"/>
        <rect x="8" y="6" width="44" height="10" rx="2" fill="#0F6E56" opacity=".7"/>
        <rect x="4" y="58" width="52" height="8" rx="2" fill="#085041"/>
        <line x1="4" y1="28" x2="56" y2="28" stroke="#0F6E56" stroke-width="1" opacity=".4"/>
        <line x1="4" y1="46" x2="56" y2="46" stroke="#0F6E56" stroke-width="1" opacity=".4"/>
        <rect x="22" y="30" width="16" height="12" rx="2" fill="#0F6E56" opacity=".3"/>
    </svg>`,
    AKH: `<svg viewBox="0 0 60 72" width="60" height="72">
        <rect x="4" y="2" width="52" height="64" rx="4" fill="#C8E8DC" stroke="#0F6E56" stroke-width="2"/>
        <rect x="8" y="6" width="44" height="10" rx="2" fill="#0F6E56" opacity=".7"/>
        <rect x="4" y="58" width="52" height="8" rx="2" fill="#085041"/>
    </svg>`,
    LD3: `<svg viewBox="0 0 56 72" width="56" height="72">
        <rect x="4" y="4" width="48" height="60" rx="5" fill="#E8ECF2" stroke="#5B6472" stroke-width="2"/>
        <rect x="8" y="8" width="40" height="10" rx="2" fill="#5B6472" opacity=".6"/>
        <rect x="4" y="56" width="48" height="8" rx="2" fill="#3D4350"/>
        <line x1="4" y1="30" x2="52" y2="30" stroke="#5B6472" stroke-width="1" opacity=".4"/>
        <line x1="4" y1="46" x2="52" y2="46" stroke="#5B6472" stroke-width="1" opacity=".4"/>
    </svg>`,
    LD2: `<svg viewBox="0 0 48 72" width="48" height="72">
        <rect x="4" y="4" width="40" height="60" rx="5" fill="#E8ECF2" stroke="#5B6472" stroke-width="2"/>
        <rect x="8" y="8" width="32" height="10" rx="2" fill="#5B6472" opacity=".6"/>
        <rect x="4" y="56" width="40" height="8" rx="2" fill="#3D4350"/>
    </svg>`,
    FQA: `<svg viewBox="0 0 70 52" width="70" height="52">
        <rect x="2" y="8" width="66" height="36" rx="3" fill="#F0E8FF" stroke="#7B2D8B" stroke-width="2"/>
        <rect x="6" y="4" width="58" height="8" rx="2" fill="#7B2D8B" opacity=".7"/>
        <rect x="4" y="40" width="62" height="6" rx="1" fill="#5A1F68"/>
    </svg>`,
    BULK: `<svg viewBox="0 0 80 52" width="80" height="52">
        <rect x="4" y="10" width="72" height="32" rx="3" fill="#FFF8DC" stroke="#B8860B" stroke-width="2" stroke-dasharray="6,3"/>
        <text x="40" y="30" text-anchor="middle" font-size="13" font-weight="900" fill="#B8860B" font-family="Arial">BULK</text>
    </svg>`,
};

// Badge de prioridad de embarque (misma prioridad que Booking/Pre-Manifest).
// Colores progresivos: 1=azul, 2=verde, 3=naranja, 4+=continua el ciclo.
const ULD_PRIO_PALETTE = [
    { bg: '#DBEAFE', fg: '#1D4ED8' }, // 1 azul
    { bg: '#D1FAE5', fg: '#047857' }, // 2 verde
    { bg: '#FFE8CC', fg: '#C2410C' }, // 3 naranja
    { bg: '#EDE9FE', fg: '#6D28D9' }, // 4 morado
    { bg: '#FCE7F3', fg: '#BE185D' }, // 5 rosado
    { bg: '#E0F2FE', fg: '#0369A1' }, // 6 celeste
];
function uldPrioBadgeHTML(prio) {
    const p = parseInt(prio);
    if (!p || p <= 0) return '';
    const c = ULD_PRIO_PALETTE[(p - 1) % ULD_PRIO_PALETTE.length];
    return '<span style="display:inline-flex;align-items:center;justify-content:center;'
        + 'min-width:20px;height:20px;padding:0 5px;border-radius:6px;'
        + 'background:' + c.bg + ';color:' + c.fg + ';font-size:11px;font-weight:800;'
        + 'font-family:Arial,sans-serif;line-height:1;margin-left:6px;flex-shrink:0;">'
        + p + '</span>';
}

function uldGetImage(uld_type, is_bulk, uld_condition) {
    if (is_bulk) return ULD_SVG.BULK;
    const t = (uld_type||'').toUpperCase().substring(0,3);
    // Try image file first, fall back to SVG
    const imgPath = ULD_IMG_BASE + (t || 'AKE').toUpperCase() + '.webp';
    // Return img tag that falls back to SVG on error
    const svgFallback = (ULD_SVG[t] || ULD_SVG.AKE).replace(/"/g, "'").replace(/\n\s*/g, ' ');
    return '<img src="' + imgPath + '" width="80" height="72" style="display:block;object-fit:contain;"'
        + ' onerror="this.style.display=\'none\';this.nextSibling.style.display=\'block\'">'
        + '<span style="display:none;">' + (ULD_SVG[t] || ULD_SVG.AKE) + '</span>';
}

// ULD Summary bar
function uldRenderSummary() {
    const wrap = document.getElementById('uldSummaryBar');
    if (!wrap || !uldData.ulds || !uldData.ulds.length) {
        if (wrap) wrap.innerHTML = '';
        return;
    }
    const cargo = {}, empty = {}, damaged = {};
    let totalKg = 0, totalLb = 0;
    uldData.ulds.forEach(function(u) {
        if (u.is_bulk) return;
        const t = (u.uld_type || 'ULD').toUpperCase().substring(0,3);
        if (u.uld_condition === 'EMPTY')   { empty[t]   = (empty[t]||0)+1; }
        else if (u.uld_condition === 'DAMAGED') { damaged[t] = (damaged[t]||0)+1; }
        else { cargo[t] = (cargo[t]||0)+1; }
        totalKg += parseFloat(u.weight_kg)||0;
        totalLb += parseFloat(u.weight_lb)||0;
    });
    // Add BULK
    const bulkUld = uldData.ulds.find(function(u){ return u.is_bulk; });
    if (bulkUld) {
        cargo['BULK'] = (cargo['BULK']||0)+1;
        totalKg += parseFloat(bulkUld.weight_kg)||0;
        totalLb += parseFloat(bulkUld.weight_lb)||0;
    }

    function renderGroup(title, color, bgColor, obj) {
        const entries = Object.entries(obj);
        if (!entries.length) return '';
        return '<div style="background:' + bgColor + ';border:1px solid ' + color + '40;border-radius:6px;padding:8px 14px;min-width:90px;">'
            + '<div style="font-size:10px;font-weight:900;text-transform:uppercase;color:' + color + ';letter-spacing:.08em;margin-bottom:5px;">' + title + '</div>'
            + entries.map(function(e){ return '<div style="display:flex;justify-content:space-between;gap:14px;font-size:13px;">'
                + '<span style="color:#374151;">' + e[0] + ':</span>'
                + '<strong style="color:' + color + ';">' + e[1] + '</strong></div>'; }).join('')
            + '</div>';
    }

    wrap.innerHTML = '<div style="display:flex;gap:8px;align-items:flex-start;flex-wrap:wrap;justify-content:flex-end;">'
        + renderGroup('CARGO',   '#002244', '#EFF6FF', cargo)
        + renderGroup('EMPTY',   '#5B6472', '#F4F6F9', empty)
        + renderGroup('DAMAGED', '#C8102E', '#FFF0F0', damaged)
        + '<div style="background:#002244;border-radius:6px;padding:8px 16px;color:#fff;text-align:right;min-width:110px;">'
        + '<div style="font-size:10px;font-weight:700;text-transform:uppercase;opacity:.7;margin-bottom:3px;letter-spacing:.06em;">Total Weight</div>'
        + '<div style="font-size:16px;font-weight:900;">' + totalKg.toFixed(1) + ' <span style="font-size:12px;opacity:.7;">KG</span></div>'
        + '<div style="font-size:12px;font-weight:700;opacity:.8;">' + Math.round(totalLb).toLocaleString() + ' <span style="font-size:10px;opacity:.7;">LB</span></div>'
        + '</div></div>';
}

// Validate ULD number format: TYPE(3) + NUMBER(5) + OWNER(2) = 10 chars
function uldValidateNumber(num) {
    return /^[A-Z]{2,4}\d{4,6}[A-Z]{2}$/i.test(num.trim());
}

// Check all pieces assigned for report button
function uldCheckReadyForReport() {
    const btn = document.getElementById('uldBtnReport');
    if (!btn) return;
    if (!uldData.awbs || !uldData.awbs.length) { btn.disabled = true; btn.style.opacity = '.45'; return; }
    const allAssigned = uldData.awbs.every(function(a) { return a.pieces_remaining <= 0; });
    const allHaveWeight = uldData.ulds.every(function(u) { return u.weight_kg || u.is_bulk; });
    const ready = allAssigned && allHaveWeight && uldData.ulds.length > 0;
    btn.disabled = !ready;
    btn.style.opacity = ready ? '1' : '.45';
    btn.style.cursor  = ready ? 'pointer' : 'not-allowed';
}

function uldRenderAwbs() {
    const wrap = document.getElementById('uldAwbList');
    if (!wrap) return;

    // Only show AWBs with pieces_remaining > 0
    const pending = (uldData.awbs || []).filter(function(a) { return a.pieces_remaining > 0; });

    if (!uldData.awbs || !uldData.awbs.length) {
        wrap.innerHTML = '<div class="empty-state" style="font-size:12px;">Select flight and date first.</div>';
        uldCheckReadyForReport();
        return;
    }
    if (!pending.length) {
        wrap.innerHTML = '<div style="text-align:center;padding:20px;color:#0F6E56;font-size:12px;font-weight:700;">'
            + '\u2713 All pieces assigned</div>';
        uldCheckReadyForReport();
        return;
    }

    let html = '';
    pending.forEach(function(a) {
        const pct = a.pieces > 0 ? Math.round(((a.pieces - a.pieces_remaining)/a.pieces)*100) : 0;
        // Peso prorrateado de la porcion pendiente (aun no asignada a ningun ULD)
        const pendingKg = (a.gross_weight && a.pieces > 0)
            ? (parseFloat(a.gross_weight) * (a.pieces_remaining / a.pieces))
            : null;
        html += '<div class="uld-awb-chip"'
            + ' draggable="true"'
            + ' data-shipment-id="' + a.id + '"'
            + ' ondragstart="uldOnDragStart(event,' + a.id + ')"'
            + ' ondragend="uldOnDragEnd(event)"'
            + ' style="border:1px solid var(--border);background:#fff;border-radius:6px;'
            + 'padding:12px 14px;margin-bottom:10px;cursor:grab;transition:box-shadow .15s,border-color .15s;"'
            + ' onmouseenter="this.style.boxShadow=\'0 3px 10px rgba(0,34,68,.15)\';this.style.borderColor=\'#0066B2\'"'
            + ' onmouseleave="this.style.boxShadow=\'\';this.style.borderColor=\'var(--border)\'">'
            + '<div style="display:flex;align-items:center;font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-weight:700;font-size:14px;'
            + 'color:var(--delta-navy);margin-bottom:4px;">' + esc(a.awb||a.shipment_number) + uldPrioBadgeHTML(a.priority) + '</div>'
            + '<div style="font-size:12px;color:var(--text-mid);text-transform:uppercase;'
            + 'overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-bottom:6px;">'
            + esc(a.agent_name||'') + ' \u00b7 ' + esc(a.description||'') + '</div>'
            + '<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:5px;">'
            + '<span style="font-size:12px;color:#C8102E;font-weight:700;">'
            + 'Pendiente: ' + a.pieces_remaining + '/' + a.pieces + ' PCS' + (pendingKg!=null ? ' &middot; ' + pendingKg.toFixed(0) + ' Kg' : '') + '</span>'
            + '<span style="font-size:11px;color:var(--text-mid);font-weight:600;">' + pct + '%</span>'
            + '</div>'
            + '<div style="height:5px;background:#F0F2F5;border-radius:3px;">'
            + '<div style="width:' + pct + '%;height:100%;background:#FFC72C;border-radius:3px;transition:width .3s;"></div>'
            + '</div>'
            + '</div>';
    });

    // Also show fully assigned AWBs dimmed
    const done = (uldData.awbs || []).filter(function(a) { return a.pieces_remaining <= 0; });
    if (done.length) {
        html += '<div style="font-size:12px;color:var(--text-light);font-weight:700;'
            + 'text-transform:uppercase;margin:12px 0 8px;letter-spacing:.04em;">'
            + '✓ Assigned (' + done.length + ')</div>';
        done.forEach(function(a) {
            html += '<div style="border:1px solid #D1FAE5;background:#F0FDF4;border-radius:8px;'
                + 'padding:9px 14px;margin-bottom:8px;">'
                + '<div style="display:flex;align-items:center;font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-weight:700;font-size:13px;'
                + 'color:#0F6E56;">✓ ' + esc(a.awb||a.shipment_number) + uldPrioBadgeHTML(a.priority) + '</div>'
                + '<div style="font-size:11px;color:#6B7280;margin-top:2px;">' + a.pieces + ' PCS asignadas</div>'
                + '</div>';
        });
    }

    wrap.innerHTML = html;
    uldCheckReadyForReport();
}

function uldRenderUlds() {
    const wrap = document.getElementById('uldList');
    if (!wrap) return;
    if (!uldData.ulds || !uldData.ulds.length) {
        wrap.innerHTML = '<div style="border:2px dashed var(--border);border-radius:8px;'
            + 'padding:40px;text-align:center;color:var(--text-light);font-size:13px;">'
            + 'Create ULDs with the &quot;+ ULD&quot; button or BULK position</div>';
        uldCheckReadyForReport();
        return;
    }

    wrap.innerHTML = '<div id="uldDragList" style="display:flex;flex-direction:column;gap:10px;'
        + 'max-height:70vh;overflow-y:auto;padding-right:4px;padding-bottom:8px;"></div>';
    const list = document.getElementById('uldDragList');

    uldData.ulds.forEach(function(u, idx) {
        const isBulk   = u.is_bulk == 1;
        const imgSvg   = uldGetImage(u.uld_type, isBulk);
        const isEmpty  = u.uld_condition === 'EMPTY';
        const isDmg    = u.uld_condition === 'DAMAGED';
        const color    = isBulk ? '#B8860B' : isDmg ? '#C8102E' : isEmpty ? '#9AA3AF' : 'var(--delta-navy)';
        const cardBg   = isEmpty ? '#F9FAFB' : '#fff';
        const noWeight = !u.weight_kg && !isBulk;
        const prio     = u._displayPriority || (idx + 1);
        const prioColor = prio === 1 ? '#0F6E56' : prio <= 3 ? 'var(--delta-navy)' : '#6B7280';
        const totalPcsInUld = (u.awbs||[]).reduce(function(s,a){ return s+parseInt(a.pieces_in_uld||0); }, 0);

        // AWB rows
        let awbRows = '';
        let uldCalcKg = 0; // suma de pesos prorrateados de las guias asignadas (informativo)
        if (u.awbs && u.awbs.length) {
            awbRows += '<div style="display:grid;grid-template-columns:160px 90px 80px 80px 1fr 28px;'
                + 'gap:6px;padding:5px 10px;background:#F4F6F9;border-bottom:1px solid var(--border);'
                + 'font-size:9px;font-weight:700;text-transform:uppercase;color:var(--text-mid);align-items:center;">'
                + '<div>AWB No</div><div style="text-align:center;">PCS / TOTAL</div>'
                + '<div style="text-align:right;">GROSS Kg</div><div style="text-align:right;">GROSS Lbs</div>'
                + '<div>COMODITY</div><div></div></div>';
            u.awbs.forEach(function(a) {
                // Total real de piezas del AWB: preferir a.total_pieces, si no viene
                // (bug de backend) caer al registro maestro en uldData.awbs.
                const awbGlobalRef = (uldData.awbs || []).find(function(x){ return x.id === a.shipment_id; });
                const totalPcs = parseInt(a.total_pieces) || parseInt(awbGlobalRef && awbGlobalRef.pieces) || 0;
                const pcsInUld = parseInt(a.pieces_in_uld) || 0;
                // Prioridad de embarque: preferir el campo propio; si el backend no
                // lo trae en este sub-registro, caer al registro maestro del panel izquierdo.
                const awbPrio = a.priority || (awbGlobalRef && awbGlobalRef.priority) || 0;
                // Peso PRORRATEADO segun la proporcion de piezas que realmente
                // quedan en este ULD (no el peso total del AWB completo) —
                // corrige el calculo cuando una guia se asigna parcialmente.
                const portion = totalPcs > 0 ? (pcsInUld / totalPcs) : 1;
                const aKgNum = a.gross_weight ? parseFloat(a.gross_weight) * portion : 0;
                const aKg  = a.gross_weight ? aKgNum.toFixed(0) : '—';
                const aLbs = a.gross_weight ? Math.round(aKgNum*2.20462).toLocaleString() : '—';
                uldCalcKg += aKgNum;
                const dest = a.routing_dest || a.flt_dest || '';
                awbRows += '<div style="display:grid;grid-template-columns:160px 90px 80px 80px 1fr 28px;'
                    + 'gap:6px;padding:7px 10px;border-bottom:1px solid #F0F2F5;align-items:center;background:#fff;">'
                    + '<div style="display:flex;align-items:center;gap:6px;">'
                    + '<span style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-size:12px;font-weight:700;color:var(--delta-navy);white-space:nowrap;">' + esc(a.awb||a.shipment_number) + '</span>'
                    + (dest ? '<span style="font-size:10px;font-weight:700;color:#fff;background:var(--delta-navy);border-radius:3px;padding:1px 6px;">' + esc(dest) + '</span>' : '')
                    + uldPrioBadgeHTML(awbPrio)
                    + '</div>'
                    + '<div style="display:flex;align-items:center;gap:4px;justify-content:center;">'
                    + '<input type="number" min="1" max="' + totalPcs + '" value="' + pcsInUld + '"'
                    + ' onchange="uldUpdatePieces(' + u.id + ',' + a.shipment_id + ',this.value)"'
                    + ' style="width:52px;text-align:center;font-size:14px;font-weight:700;'
                    + 'border:2px solid var(--delta-sky);border-radius:5px;padding:3px 4px;color:var(--delta-navy);background:#EFF6FF;">'
                    + '<span style="font-size:11px;color:var(--text-mid);white-space:nowrap;">/ ' + totalPcs + '</span>'
                    + '</div>'
                    + '<div style="text-align:right;font-size:13px;font-weight:700;color:var(--delta-navy);">' + aKg + '</div>'
                    + '<div style="text-align:right;font-size:12px;color:var(--text-mid);">' + aLbs + '</div>'
                    + '<div><input type="text" value="' + esc(a.description||'') + '"'
                    + ' style="width:100%;font-size:14px;font-weight:700;color:var(--delta-navy);'
                    + 'border:2px solid var(--delta-sky);border-radius:5px;background:#EFF6FF;padding:5px 8px;" placeholder="Comodity..."></div>'
                    + '<div style="text-align:center;">'
                    + '<button onclick="uldRemoveAwb(' + u.id + ',' + a.shipment_id + ')" title="Remove from ULD"'
                    + ' style="background:none;border:none;color:var(--delta-red);cursor:pointer;font-size:18px;line-height:1;padding:0;">&#x00d7;</button>'
                    + '</div></div>';
            });
            // Subtotal calculado (informativo): suma de pesos prorrateados de las
            // guias asignadas. No sobreescribe el campo GROSS Kg manual del ULD
            // (que refleja el peso real pesado en balanza), sirve como referencia.
            awbRows += '<div style="padding:6px 10px;background:#F8FAFC;border-top:1px dashed var(--border);'
                + 'display:flex;justify-content:flex-end;gap:6px;font-size:11px;color:var(--text-mid);">'
                + '<span style="font-weight:700;">Assigned cargo (calc.):</span>'
                + '<span style="font-weight:900;color:var(--delta-navy);">' + uldCalcKg.toFixed(0) + ' Kg</span>'
                + '<span>&middot; ' + totalPcsInUld + ' pcs</span>'
                + '</div>';
        } else {
            awbRows = '<div style="padding:16px 12px;text-align:center;color:var(--text-light);font-size:11px;">Drag AWBs here</div>';
        }

        const div = document.createElement('div');
        div.id = 'uldCard_' + u.id;
        div.dataset.uldId = u.id;
        div.draggable = true;
        div.style.cssText = 'border:1px solid var(--border);border-radius:8px;background:' + cardBg
            + ';box-shadow:0 1px 4px rgba(0,0,0,.06);cursor:grab;transition:box-shadow .15s,opacity .15s;';

        // When mousedown is on an AWB chip, temporarily disable card dragging
        div.addEventListener('mousedown', function(e) {
            if (e.target.closest('.uld-awb-chip')) {
                div.draggable = false;
                setTimeout(function(){ div.draggable = true; }, 2000);
            }
        });

        div.innerHTML =
            // ── Card header
            '<div class="uld-dropzone" style="display:grid;grid-template-columns:32px 80px 170px 1fr auto;align-items:center;gap:0;border-bottom:1px solid var(--border);">'
            // Drag handle + Priority badge
            + '<div style="display:flex;flex-direction:column;align-items:center;justify-content:center;padding:6px 4px;background:#F8FAFC;border-right:1px solid var(--border);border-radius:7px 0 0 0;cursor:grab;">'
            + '<div style="font-size:7px;color:#9CA3AF;margin-bottom:2px;">&#9776;</div>'
            + '<div style="font-size:20px;font-weight:900;color:' + prioColor + ';line-height:1;">P' + prio + '</div>'
            + '</div>'
            // Image
            + '<div style="width:80px;min-width:80px;padding:8px 10px;border-right:1px solid var(--border);text-align:center;background:#F8FAFC;display:flex;align-items:center;justify-content:center;">'
            + imgSvg + '</div>'
            // Identifier
            + '<div style="padding:8px 12px;border-right:1px solid var(--border);">'
            + '<div style="font-weight:700;font-size:16px;color:' + color + ';">' + (isBulk ? 'BULK' : esc(u.uld_number)) + '</div>'
            + '<div style="font-size:11px;color:var(--text-mid);margin-top:2px;">'
            + (u.uld_type ? '<span style="font-weight:700;color:var(--delta-navy);">' + esc(u.uld_type) + '</span> &nbsp;' : '')
            + esc(u.dest||'') + '</div>'
            + '</div>'
            // Weight inputs
            + '<div style="padding:6px 12px;display:flex;align-items:center;gap:12px;">'
            + '<div><div style="font-size:9px;color:var(--text-mid);text-transform:uppercase;margin-bottom:2px;">GROSS Kg</div>'
            + '<input type="number" step="0.01" value="' + (u.weight_kg||'') + '" placeholder="0.00"'
            + ' id="uldKg_' + u.id + '"'
            + ' onchange="uldSaveField(' + u.id + String.fromCharCode(44) + String.fromCharCode(39) + 'weight_kg' + String.fromCharCode(39) + ',this.value);uldCalcLbs(' + u.id + ')"'
            + ' style="width:90px;text-align:right;padding:5px 8px;font-size:14px;font-weight:700;border:2px solid ' + (noWeight ? '#F87171' : 'var(--delta-sky)') + ';border-radius:5px;color:var(--delta-navy);background:#EFF6FF;">'
            + '</div>'
            + '<div><div style="font-size:9px;color:var(--text-mid);text-transform:uppercase;margin-bottom:2px;">GROSS Lbs</div>'
            + '<input type="text" id="uldLbs_' + u.id + '" readonly'
            + ' value="' + (u.weight_kg ? Math.round(parseFloat(u.weight_kg)*2.20462).toLocaleString() : '') + '"'
            + ' style="width:80px;text-align:right;padding:5px 8px;font-size:14px;font-weight:700;background:#F8FAFC;border:2px solid var(--border);border-radius:5px;color:var(--text-mid);">'
            + '</div>'
            + '<div><div style="font-size:9px;color:var(--text-mid);text-transform:uppercase;margin-bottom:2px;">TYPE</div>'
            + '<select onchange="uldSaveField(' + u.id + String.fromCharCode(44) + String.fromCharCode(39) + 'uld_condition' + String.fromCharCode(39) + ',this.value)" style="padding:4px 6px;font-size:12px;border:1px solid var(--border);border-radius:4px;">'
            + ['OK','DAMAGED','EMPTY'].map(function(c){ return '<option value="' + c + '"' + (u.uld_condition===c?' selected':'') + '>' + c + '</option>'; }).join('')
            + '</select>'
            + '</div>'
            + '</div>'
            // Delete button
            + '<div style="padding:8px 12px;">'
            + '<button onclick="uldDeleteCard(' + u.id + ')" title="Delete ULD" style="background:none;border:1px solid var(--delta-red);color:var(--delta-red);border-radius:5px;padding:4px 10px;cursor:pointer;font-size:11px;white-space:nowrap;">&#128465; Delete</button>'
            + '</div>'
            + '</div>'
            // AWB drop zone
            + '<div class="uld-dropzone" data-uld="' + u.id + '" '
            + 'ondragover="uldOnDragOverUld(event,' + u.id + ')" ondrop="uldOnDropOnUld(event,' + u.id + ')"'
            + ' style="min-height:50px;">' + awbRows + '</div>';

        // Drag events for ULD reordering — only fire when dragging the card itself (not a child AWB)
        div.addEventListener('dragstart', function(e) {
            if (e.target.classList.contains('uld-awb-chip') || e.target.closest('.uld-awb-chip')) {
                return;
            }
            e.stopPropagation();
            window._uldDragId      = u.id;
            window._uldDraggingUld = true;
            div.style.opacity      = '0.4';
            e.dataTransfer.effectAllowed = 'move';
            // Use a distinct key for ULD reorder so it never conflicts with AWB drags
            e.dataTransfer.setData('text/plain', 'uld:' + u.id);
        });
        div.addEventListener('dragend', function() {
            div.style.opacity = '1';
            window._uldDraggingUld = false;
            document.querySelectorAll('#uldDragList > div').forEach(function(el){
                el.style.borderTop = '';
                el.style.borderBottom = '';
            });
        });
        div.addEventListener('dragover', function(e) {
            // Only handle ULD reorder drags — let AWB drags fall through to .uld-dropzone
            const data = e.dataTransfer.types.includes('text/plain');
            if (!data) return;
            if (!window._uldDraggingUld) return; // AWB drag — don't intercept
            e.preventDefault();
            e.stopPropagation();
            document.querySelectorAll('#uldDragList > div').forEach(function(el){
                el.style.borderTop    = '';
                el.style.borderBottom = '';
            });
            const rect = div.getBoundingClientRect();
            const mid  = rect.top + rect.height / 2;
            div.style.borderTop    = e.clientY < mid  ? '3px solid var(--delta-sky)' : '';
            div.style.borderBottom = e.clientY >= mid ? '3px solid var(--delta-sky)' : '';
        });
        div.addEventListener('dragleave', function(e) {
            if (!window._uldDraggingUld) return;
            div.style.borderTop    = '';
            div.style.borderBottom = '';
        });
        div.addEventListener('drop', function(e) {
            const data = e.dataTransfer.getData('text/plain');
            if (!data || !data.startsWith('uld:')) return; // AWB drop — ignore
            e.preventDefault();
            e.stopPropagation();
            div.style.borderTop    = '';
            div.style.borderBottom = '';
            window._uldDraggingUld = false;
            const draggedId = parseInt(data.replace('uld:', ''));

            // Reorder in uldData.ulds
            const fromIdx = uldData.ulds.findIndex(function(x){ return x.id === draggedId; });
            let   toIdx   = uldData.ulds.findIndex(function(x){ return x.id === u.id; });
            if (fromIdx === -1 || toIdx === -1) return;

            const rect = div.getBoundingClientRect();
            if (e.clientY >= rect.top + rect.height / 2) toIdx++;
            if (fromIdx < toIdx) toIdx--;

            const [moved] = uldData.ulds.splice(fromIdx, 1);
            uldData.ulds.splice(toIdx, 0, moved);

            // Reassign display priorities
            uldData.ulds.forEach(function(x, i){ x._displayPriority = i + 1; });

            // Save new priority order to DB
            uldSavePriorityOrder();
            uldRenderUlds();
            uldRenderSummary();
        });

        list.appendChild(div);
    });

    uldCheckReadyForReport();
}

async function uldSavePriorityOrder() {
    const flt   = document.getElementById('mfFlight')?.value  || '';
    const date  = document.getElementById('mfDate')?.value    || '';
    const order = uldData.ulds.map(function(u, i){ return { id: u.id, priority: i + 1 }; });
    try {
        await apiFetch('/shipments/flight_ulds_api.php', {
            method: 'POST',
            body: JSON.stringify({ action: 'save_priority_order', flt_number: flt, flt_date: date, order: order })
        });
    } catch(e) {}
}

// ── AWB → ULD Drag & Drop (independent from ULD reorder) ─────────

function uldOnDragStart(event, shipmentId) {
    window._uldDraggingUld  = false;
    uldDragShipmentId       = shipmentId;
    event.dataTransfer.effectAllowed = 'move';
    // Use text/plain with prefix — most reliable cross-browser
    event.dataTransfer.setData('text/plain', 'awb:' + shipmentId);
    event.target.style.opacity = '0.5';
}

function uldOnDragEnd(event) {
    event.target.style.opacity = '1';
    // Don't null uldDragShipmentId here — drop fires after dragend in some browsers
    // It gets cleared in uldOnDropOnUld instead
    document.querySelectorAll('.uld-dropzone').forEach(function(z) {
        z.style.background = '';
        z.style.outline    = '';
    });
}

function uldOnDragOverUld(event, uldId) {
    if (window._uldDraggingUld) return;
    event.preventDefault();
    event.stopPropagation();
    event.dataTransfer.dropEffect = 'move';
    event.currentTarget.style.background = '#EFF6FF';
    event.currentTarget.style.outline    = '2px dashed var(--delta-sky)';
}

async function uldOnDropOnUld(event, uldId) {
    event.preventDefault();
    event.stopPropagation();

    event.currentTarget.style.background = '';
    event.currentTarget.style.outline    = '';

    const raw = event.dataTransfer.getData('text/plain') || '';
    let shipmentId = 0;
    if (raw.startsWith('awb:')) {
        shipmentId = parseInt(raw.replace('awb:', ''));
    } else if (uldDragShipmentId) {
        shipmentId = uldDragShipmentId;
    }

    uldDragShipmentId = null;

    if (!shipmentId || !uldId) {
        bkShowToast('Error: could not identify the AWB or ULD', '#C8102E');
        return;
    }

    // Find AWB in uldData
    const awb = uldData.awbs.find(function(a){ return a.id === shipmentId; });
    const uld = uldData.ulds.find(function(u){ return u.id === uldId; });
    if (!awb || !uld) return;
    if (awb.pieces_remaining <= 0) { bkShowToast('This AWB already has all its pieces assigned', '#854F0B'); return; }

    // Ask how many pieces to assign (default = all remaining)
    const pcsToAssign = awb.pieces_remaining; // assign all remaining

    try {
        const res = await apiFetch('/shipments/flight_ulds_api.php', {
            method: 'POST',
            body: JSON.stringify({
                action:       'assign_awb',
                uld_id:       uldId,
                shipment_id:  shipmentId,
                pieces:       pcsToAssign,
                flt_number:   document.getElementById('mfFlight')?.value || '',
                flt_date:     document.getElementById('mfDate')?.value   || '',
            })
        });

        if (res.success) {
            // Recarga autoritativa desde el servidor en vez de parchear el estado
            // local a mano: garantiza que lo que se ve en pantalla sea exactamente
            // lo que quedo persistido en la BD (evita divergencias entre el panel
            // y flight_uld_awbs, que era la causa de que una asignacion "se viera"
            // correcta pero desapareciera al volver a entrar al tab).
            await uldLoad();
            // Verificacion de sanidad: confirmar que la guia realmente quedo
            // asignada al ULD correcto tras recargar. Si no, avisar de inmediato
            // en vez de dejar que el usuario lo descubra minutos despues.
            const freshUld = (uldData.ulds || []).find(function(u2){ return u2.id === uldId; });
            const assignedOk = freshUld && (freshUld.awbs || []).some(function(a2){ return a2.shipment_id === shipmentId; });
            if (!assignedOk) {
                bkShowToast('Notice: the assignment was not reflected in the database. Please try again.', '#C8102E');
            }
        } else {
            bkShowToast('Error assigning AWB: ' + (res.message||''), '#C8102E');
        }
    } catch(e) {
        bkShowToast('Connection error while assigning AWB', '#C8102E');
    }
}



// ── Helper: smart insert priority (after last OK, before DAMAGED/EMPTY) ──
function uldNextInsertPriority(forBulk) {
    const ulds = uldData.ulds || [];
    // Count OK ULDs (non-bulk, condition OK or unset)
    const okCount = ulds.filter(function(u){
        return !u.is_bulk && (!u.uld_condition || u.uld_condition === 'OK');
    }).length;
    // New OK ULD goes after last OK
    if (!forBulk) return okCount + 1;
    // BULK goes after all OK
    return okCount + 1;
}

async function uldAddNew(isBulk) {
    const flt     = document.getElementById('mfFlight')?.value  || '';
    const date    = document.getElementById('mfDate')?.value    || '';
    const station = document.getElementById('mfStation')?.value || 'LIM';
    const mn      = document.getElementById('mfManifestNumber')?.value || '';
    if (!flt || !date) { alert('Select flight and date first.'); return; }

    if (isBulk) {
        const existing   = (uldData.ulds || []).filter(function(u){ return u.is_bulk; }).length;
        const uld_number = existing === 0 ? 'BULK' : 'BULK ' + (existing + 1);
        const priority   = uldNextInsertPriority(true);
        try {
            const res = await apiFetch('/shipments/flight_ulds_api.php', {
                method: 'POST',
                body: JSON.stringify({ action:'save_uld', flt_number:flt, flt_date:date,
                    station, manifest_number:mn, uld_number, dest:'',
                    is_bulk:1, priority, user_id:currentUser?.id||null })
            });
            if (res.success) await uldLoad();
            else alert(res.message);
        } catch(e) { console.error(e); }
        return;
    }

    // ULD modal
    const existing2 = document.getElementById('uldAddModal');
    if (existing2) existing2.remove();

    const STD = 'width:100%;border:2px solid var(--delta-sky);border-radius:5px;padding:8px 10px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#EFF6FF;box-sizing:border-box;';

    let uldOpts = '<option value="">&#8212; Select type &#8212;</option>';
    try {
        const catRes = await apiFetch('/catalog/catalog_api.php?table=uld_types&show_inactive=0');
        if (catRes.success && catRes.data) {
            catRes.data
                .sort(function(a,b){ return (a.uld_code||'').localeCompare(b.uld_code||''); })
                .forEach(function(u){
                    uldOpts += '<option value="' + esc(u.uld_code) + '">' + esc(u.uld_code) + '</option>';
                });
        }
    } catch(e) {
        AWB_ULD_TYPES.forEach(function(c){ uldOpts += '<option value="' + c + '">' + c + '</option>'; });
    }

    const modal = document.createElement('div');
    modal.id = 'uldAddModal';
    modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:9000;display:flex;align-items:center;justify-content:center;';
    modal.innerHTML = '<div style="background:#fff;border-radius:8px;width:440px;max-width:95vw;box-shadow:0 8px 40px rgba(0,0,0,.3);overflow:hidden;">'
        + '<div style="background:var(--delta-navy);color:#fff;padding:14px 18px;display:flex;align-items:center;justify-content:space-between;">'
        + '<div><div style="font-size:14px;font-weight:800;">+ New ULD</div>'
        + '<div style="font-size:11px;opacity:.75;margin-top:2px;">' + esc(flt) + ' &nbsp;&bull;&nbsp; ' + fmtDate(date) + '</div></div>'
        + '<button onclick="uldCloseAddModal()" style="background:none;border:none;color:#fff;font-size:20px;cursor:pointer;">&#x2715;</button>'
        + '</div>'
        + '<div style="padding:20px;">'
        + '<div style="display:grid;grid-template-columns:110px 1fr 80px;gap:10px;margin-bottom:14px;">'
        + '<div><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);display:block;margin-bottom:4px;">ULD *</label>'
        + '<select id="uldAddType" style="' + STD + 'padding:8px 4px;" onchange="uldAddPreview()">' + uldOpts + '</select></div>'
        + '<div><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);display:block;margin-bottom:4px;">Number *</label>'
        + '<input type="text" id="uldAddNumber" placeholder="12345" maxlength="5" oninput="uldNumInput(this)" onkeydown="uldKeyConfirm(event)" style="' + STD + 'text-align:center;"></div>'
        + '<div><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);display:block;margin-bottom:4px;">Airline</label>'
        + '<input type="text" id="uldAddOwner" value="DL" maxlength="2" oninput="uldAddPreview()" onkeydown="uldKeyConfirm(event)" style="' + STD + 'text-align:center;text-transform:uppercase;"></div>'
        + '</div>'
        + '<div style="background:#F0F7FF;border:1px solid var(--delta-sky);border-radius:6px;padding:12px 14px;margin-bottom:16px;text-align:center;">'
        + '<div style="font-size:10px;text-transform:uppercase;color:var(--text-mid);margin-bottom:4px;">Identificador del ULD</div>'
        + '<div id="uldAddPreviewBox" style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-size:22px;font-weight:900;color:#9CA3AF;letter-spacing:.08em;">&#8212;</div>'
        + '</div>'
        + '<div id="uldAddError" style="min-height:18px;font-size:12px;font-weight:600;color:var(--delta-red);margin-bottom:12px;"></div>'
        + '<div style="display:flex;gap:10px;">'
        + '<button onclick="uldCloseAddModal()" class="btn-secondary" style="flex:1;">Cancel</button>'
        + '<button onclick="uldAddConfirm()" class="btn-primary" style="flex:2;font-size:14px;font-weight:700;">&#43; Add ULD</button>'
        + '</div>'
        + '</div></div>';

    document.body.appendChild(modal);
    modal.addEventListener('click', function(e){ if(e.target===modal) modal.remove(); });
    setTimeout(function(){ document.getElementById('uldAddType')?.focus(); }, 100);
}

function uldNumInput(el) { el.value = el.value.replace(/[^0-9]/g, '').slice(0, 5); uldAddPreview(); }

function uldKeyConfirm(e) { if(e.key==='Enter') uldAddConfirm(); }

function uldCloseAddModal() { document.getElementById('uldAddModal')?.remove(); }

function uldAddPreview() {
    const type  = (document.getElementById('uldAddType')?.value   || '').toUpperCase().trim();
    const num   = (document.getElementById('uldAddNumber')?.value || '').toUpperCase().trim();
    const owner = (document.getElementById('uldAddOwner')?.value  || '').toUpperCase().trim();
    const box   = document.getElementById('uldAddPreviewBox');
    if (!box) return;
    if (type && num) { box.textContent = type + num + owner; box.style.color = 'var(--delta-navy)'; }
    else { box.textContent = '—'; box.style.color = '#9CA3AF'; }
    const err = document.getElementById('uldAddError');
    if (err) err.textContent = '';
}

async function uldAddConfirm() {
    const type   = (document.getElementById('uldAddType')?.value   || '').toUpperCase().trim();
    const num    = (document.getElementById('uldAddNumber')?.value || '').toUpperCase().trim();
    const owner  = (document.getElementById('uldAddOwner')?.value  || 'DL').toUpperCase().trim();
    const err    = document.getElementById('uldAddError');
    const setErr = function(msg) { if(err) err.textContent = msg; };

    if (!type)  { setErr('Select the ULD type.'); return; }
    if (!num)   { setErr('Enter the ULD number.'); return; }
    if (!/^[0-9]{1,5}$/.test(num)) { setErr('The number must be numeric, up to 5 digits.'); return; }
    if (owner.length !== 2) { setErr('El propietario debe tener exactamente 2 letras (ej: DL, LA).'); return; }

    const uld_number = type + num + owner;
    const exists = (uldData.ulds || []).some(function(u){ return u.uld_number === uld_number; });
    if (exists) { setErr('El ULD ' + uld_number + ' ya existe en este vuelo.'); return; }

    const flt     = document.getElementById('mfFlight')?.value  || '';
    const date    = document.getElementById('mfDate')?.value    || '';
    const station = document.getElementById('mfStation')?.value || 'LIM';
    const mn      = document.getElementById('mfManifestNumber')?.value || '';
    const priority = uldNextInsertPriority(false);

    try {
        const res = await apiFetch('/shipments/flight_ulds_api.php', {
            method: 'POST',
            body: JSON.stringify({ action:'save_uld', flt_number:flt, flt_date:date,
                station, manifest_number:mn, uld_number, dest:'',
                is_bulk:0, uld_type:type, priority, user_id:currentUser?.id||null })
        });
        if (res.success) {
            document.getElementById('uldAddModal')?.remove();
            await uldLoad();
        } else {
            setErr('Error: ' + (res.message || 'No se pudo crear el ULD.'));
        }
    } catch(e) { setErr('Connection error.'); }
}
function uldCalcLbs(uldId) {
    const kgInput  = document.getElementById('uldKg_'  + uldId);
    const lbsInput = document.getElementById('uldLbs_' + uldId);
    if (!kgInput || !lbsInput) return;
    const kg = parseFloat(kgInput.value) || 0;
    lbsInput.value = kg ? Math.round(kg * 2.20462).toLocaleString() : '';
}

async function uldSaveField(uldId, field, value) {
    const u = uldData.ulds.find(function(x){ return x.id == uldId; });
    if (!u) return;
    const flt     = document.getElementById('mfFlight')?.value || '';
    const date    = document.getElementById('mfDate')?.value   || '';
    const station = document.getElementById('mfStation')?.value || 'LIM';
    const updated = Object.assign({}, u, {[field]: value});
    try {
        await apiFetch('/shipments/flight_ulds_api.php', {
            method:'POST',
            body: JSON.stringify({ action:'save_uld', id:uldId,
                flt_number:flt, flt_date:date, station,
                uld_number:updated.uld_number, uld_type:updated.uld_type,
                dest:updated.dest, weight_kg:updated.weight_kg,
                description:updated.description, uld_condition:updated.uld_condition,
                is_bulk:updated.is_bulk, priority:updated.priority,
                manifest_number:updated.manifest_number,
                user_id:currentUser?.id||null })
        });
        if (field === 'weight_kg') await uldLoad();
    } catch(e) {}
}

async function uldUpdatePieces(uldId, shipmentId, newPcsRaw) {
    // Find AWB in local state
    const uld = uldData.ulds.find(function(u){ return u.id === uldId; });
    const awb = uld ? (uld.awbs||[]).find(function(a){ return a.shipment_id === shipmentId; }) : null;
    const awbGlobal = uldData.awbs.find(function(a){ return a.id === shipmentId; });
    if (!uld || !awb) return;

    const oldPcs = parseInt(awb.pieces_in_uld) || 0;
    // Total de piezas del AWB: preferir el campo del propio registro del ULD
    // (a.total_pieces), pero si el backend no lo trae (undefined/0), caer al
    // registro maestro (awbGlobal.pieces) que SIEMPRE viene del panel
    // izquierdo. Esto evita el bug donde total quedaba en 0 y cualquier
    // edición se forzaba a 0 piezas, eliminando toda la asignación al ULD.
    const total = parseInt(awb.total_pieces) || parseInt(awbGlobal && awbGlobal.pieces) || 0;

    let newPcs = parseInt(newPcsRaw);
    if (isNaN(newPcs) || newPcs < 0) newPcs = 0;
    if (total > 0 && newPcs > total) { newPcs = total; }
    if (total === 0) console.warn('uldUpdatePieces: no se pudo determinar el total de piezas del AWB', shipmentId);

    if (newPcs === oldPcs) { uldRenderUlds(); return; } // sin cambio real, solo re-renderizar (revierte el input si se limpió)

    if (newPcs === 0) {
        // Solo remover por completo si el usuario explícitamente puso 0
        await uldRemoveAwb(uldId, shipmentId);
        return;
    }

    try {
        const res = await apiFetch('/shipments/flight_ulds_api.php', {
            method: 'POST',
            body: JSON.stringify({
                action:      'assign_awb',
                uld_id:      uldId,
                shipment_id: shipmentId,
                pieces:      newPcs,
            })
        });

        if (res.success) {
            // Recarga autoritativa desde el servidor (en vez de parchear el
            // estado local a mano) para garantizar que lo mostrado coincide
            // siempre con lo realmente persistido en flight_uld_awbs.
            await uldLoad();
            const freshUld = (uldData.ulds || []).find(function(u2){ return u2.id === uldId; });
            const freshAwb = freshUld && (freshUld.awbs||[]).find(function(a2){ return a2.shipment_id === shipmentId; });
            if (!freshAwb || parseInt(freshAwb.pieces_in_uld) !== newPcs) {
                bkShowToast('Notice: the piece count change was not reflected correctly in the database.', '#C8102E');
            }
        } else {
            bkShowToast('Error updating pieces: ' + (res.message||''), '#C8102E');
            uldRenderUlds(); // revertir input visualmente
        }
    } catch(e) {
        bkShowToast('Connection error while updating pieces', '#C8102E');
        uldRenderUlds();
    }
}

async function uldRemoveAwb(uldId, shipmentId) {
    // Find in local state
    const uld = uldData.ulds.find(function(u){ return u.id === uldId; });
    const awb = uld ? (uld.awbs||[]).find(function(a){ return a.shipment_id === shipmentId; }) : null;
    const awbGlobal = uldData.awbs.find(function(a){ return a.id === shipmentId; });
    if (!uld || !awb) return;

    const pcsWereAssigned = parseInt(awb.pieces_in_uld) || 0;

    try {
        const res = await apiFetch('/shipments/flight_ulds_api.php', {
            method: 'POST',
            body: JSON.stringify({
                action:      'remove_awb',
                uld_id:      uldId,
                shipment_id: shipmentId,
            })
        });

        if (res.success) {
            // Recarga autoritativa desde el servidor: mismo motivo que en
            // uldOnDropOnUld/uldUpdatePieces, evita cualquier divergencia
            // entre el estado local y flight_uld_awbs.
            await uldLoad();
        } else {
            bkShowToast('Error removing AWB: ' + (res.message||''), '#C8102E');
        }
    } catch(e) {
        bkShowToast('Connection error', '#C8102E');
    }
}

async function uldDeleteUld(uldId) {
    if (!confirm('Delete this ULD and unassign all its AWBs?')) return;
    try {
        await apiFetch('/shipments/flight_ulds_api.php', {
            method:'POST',
            body: JSON.stringify({action:'delete_uld', id:uldId, user_id:currentUser?.id||null})
        });
        await uldLoad();
    } catch(e) {}
}

async function uldGenerateReport() {
    const html = await uldBuildReportHtml();
    if (!html) return;
    const win = window.open('', '_blank');
    win.document.write(html.replace('</body>', '<script>window.onload=function(){window.print();};<\/script></body>'));
    win.document.close();
}

// ── Menu Imprimir / Descargar / Enviar Email para Outbound Report ──
async function uldShowReportMenu() {
    const flt     = document.getElementById('mfFlight')?.value  || '';
    const date    = document.getElementById('mfDate')?.value    || '';
    const mn      = document.getElementById('mfManifestNumber')?.value || '';
    const station = document.getElementById('mfStation')?.value || 'LIM';

    reportModalOpen({
        reportType:  'OUTBOUND',
        title:       'Outbound Cargo Flight Report',
        icon:        '✈',
        contextLine: flt + ' • ' + fmtDate(date),
        subject:     'Outbound Cargo Flight Report | ' + flt + ' | ' + fmtDate(date),
        station:     station,
        recipientMode: 'notification_config',
        filenamePrefix: 'Outbound_Report_' + flt.replace(/[^A-Z0-9]/gi, ''),
        buildHtml:   function() { return uldBuildReportHtml(); },
        onSend: function(payload) {
            return apiFetch('/shipments/distro_send_api.php', {
                method: 'POST',
                body: JSON.stringify({
                    to:              payload.to,
                    flt_number:      flt,
                    flt_date:        date,
                    manifest_number: mn,
                    warehouse:       'OUTBOUND',
                    group:           'report',
                    station:         station,
                    distro_html:     payload.html,
                    pdf_base64:      payload.pdfBase64,
                    subject:         payload.subject,
                })
            });
        },
    });
}

// ── Construye el HTML del Outbound Cargo Flight Report ────────────
async function uldBuildReportHtml() {
    const flt     = document.getElementById('mfFlight')?.value     || '';
    const date    = document.getElementById('mfDate')?.value       || '';
    const station = document.getElementById('mfStation')?.value    || 'LIM';
    const mn      = document.getElementById('mfManifestNumber')?.value || '';
    const etd     = document.getElementById('mfEtd')?.value        || '';
    const acType  = document.getElementById('mfAcType')?.value     || '';

    if (!uldData.ulds || !uldData.ulds.length) {
        alert('Agrega al menos un ULD antes de generar el reporte.'); return null;
    }

    // ── Date label ───────────────────────────────────────────────
    const dateLabel = date ? (function(){
        const d = new Date(date + 'T12:00:00');
        const m = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
        return d.getDate() + '-' + m[d.getMonth()] + '-' + d.getFullYear();
    })() : '';

    const printedOn = (function(){
        const n = new Date();
        return String(n.getDate()).padStart(2,'0') + '/' + String(n.getMonth()+1).padStart(2,'0')
             + '/' + n.getFullYear() + ' ' + String(n.getHours()).padStart(2,'0')
             + ':' + String(n.getMinutes()).padStart(2,'0');
    })();

    // Route from first AWB
    let routeDest = '';
    for (const u of uldData.ulds) {
        if (u.awbs && u.awbs.length) { routeDest = u.awbs[0].flt_dest || ''; break; }
    }
    const route = station + (routeDest ? '-' + routeDest : '');

    // Logo URL
    const logoUrl = window.location.pathname.split('/').slice(0,2).join('/') + '/assets/img/delta-cargo-logo.png';

    // ── Build rows ───────────────────────────────────────────────
    let rows = '';
    let grandGrossKg = 0, grandGrossLb = 0, grandNetKg = 0, grandNetLb = 0, grandPcs = 0;
    let prio = 1;

    uldData.ulds.forEach(function(u) {
        const awbs      = u.awbs || [];
        const uldLabel  = u.is_bulk ? 'BULK' : esc(u.uld_number || '');
        const prioLabel = 'P' + prio++;
        const warehouse = esc((u.warehouse || '').replace('_',' ') || '\u2014');

        // Determine ULD dest: if all AWBs same dest → that dest, else flight dest
        const awbDests = awbs.map(function(a){ return a.routing_dest || a.flt_dest || ''; });
        const uniqueDests = [...new Set(awbDests.filter(Boolean))];
        const uldDest = uniqueDests.length === 1 ? uniqueDests[0] : (routeDest || '');

        const uGrossKg = parseFloat(u.weight_kg || 0);
        const uGrossLb = parseFloat(u.weight_lb || 0) || uGrossKg * 2.20462;
        grandGrossKg += uGrossKg;
        grandGrossLb += uGrossLb;

        if (!awbs.length) {
            rows += `<tr>
                <td class="uld-no">${uldLabel}</td>
                <td class="prio">${prioLabel}</td>
                <td class="dest c">${esc(uldDest)}</td>
                <td class="r gross-kg">${uGrossKg ? uGrossKg.toLocaleString('en-US',{minimumFractionDigits:0}) : ''}</td>
                <td class="r gross-lb">${uGrossLb ? Math.round(uGrossLb).toLocaleString('en-US') : ''}</td>
                <td class="c"></td><td></td>
                <td class="r"></td><td class="r"></td>
            </tr>`;
            return;
        }

        awbs.forEach(function(a, ai) {
            const netKg  = parseFloat(a.gross_weight || 0);
            const netLb  = netKg * 2.20462;
            const pcs    = parseInt(a.pieces_in_uld || a.total_pieces || 0);
            const awbDst = esc(a.routing_dest || a.flt_dest || '');
            grandNetKg += netKg;
            grandNetLb += netLb;
            grandPcs   += pcs;

            const isFirst = ai === 0;
            rows += `<tr class="${isFirst ? 'uld-first' : 'awb-row'}">
                <td class="uld-no">${isFirst ? uldLabel : ''}</td>
                <td class="prio">${isFirst ? prioLabel : ''}</td>
                <td class="dest c" style="color:${isFirst&&uldDest?'#C8102E':'#9AA3AF'};font-weight:${isFirst?'700':'400'};">${isFirst ? esc(uldDest) : ''}</td>
                <td class="r gross-kg">${isFirst && uGrossKg ? uGrossKg.toLocaleString('en-US',{minimumFractionDigits:0}) : ''}</td>
                <td class="r gross-lb">${isFirst && uGrossLb ? Math.round(uGrossLb).toLocaleString('en-US') : ''}</td>
                <td class="awb">${esc(a.awb || a.shipment_number || '')}</td>
                <td class="c">${pcs || ''}</td>
                <td class="commodity">${esc(a.description || '').toUpperCase()}</td>
                <td class="r net-kg">${netKg ? netKg.toLocaleString('en-US',{minimumFractionDigits:0}) : ''}</td>
                <td class="r net-lb">${netLb ? Math.round(netLb).toLocaleString('en-US') : ''}</td>
            </tr>`;
        });
    });

    // ── Resumen ejecutivo de ULDs (Correcciones Outbound Cargo Flight
    // Report #2/#3): cuenta de ULD por tipo, agrupados en 3 categorías —
    // con carga, vacíos y con daño. ──
    const uldTypeCounts = { loaded: {}, empty: {}, damaged: {} };
    uldData.ulds.forEach(function(u) {
        const label = u.is_bulk ? 'BULK' : ((u.uld_type || u.uld_number || '\u2014').split(' ')[0] || '\u2014');
        let bucket;
        if (u.uld_condition === 'DAMAGED') bucket = 'damaged';
        else if (u.uld_condition === 'EMPTY' || !(u.awbs && u.awbs.length)) bucket = 'empty';
        else bucket = 'loaded';
        uldTypeCounts[bucket][label] = (uldTypeCounts[bucket][label] || 0) + 1;
    });
    const uspRows = function(counts) {
        const keys = Object.keys(counts);
        if (!keys.length) return '<div class="usp-empty-msg">\u2014</div>';
        return keys.map(function(k) {
            return `<div class="usp-row"><span class="usp-type">${esc(k)}</span><span class="usp-count">${counts[k]}</span></div>`;
        }).join('');
    };
    const uspTotal = function(counts) { return Object.values(counts).reduce(function(s,n){ return s+n; }, 0); };

    const html = `<!DOCTYPE html><html><head><meta charset="UTF-8">
<title>Outbound Cargo Flight Report ${flt} ${date}</title>
<style>
* { box-sizing:border-box; margin:0; padding:0; }
body { font-family:Arial,sans-serif; font-size:11px; color:#1A1F29; padding:20px 16px 12px 16px; }

/* ── Header ── */
.doc-header { display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:6px; gap:20px; }
.logo-block  { display:flex; align-items:center; gap:16px; flex-shrink:0; }
.doc-title   { font-size:26px; font-weight:900; color:#1A1F29; letter-spacing:.02em; white-space:nowrap; }
.printed-on  { font-size:9px; color:#9AA3AF; text-align:right; white-space:nowrap; margin-top:4px; }
.hcards { display:flex; gap:10px; flex-wrap:wrap; margin-bottom:14px; }
.hcard { background:#F8FAFC; border:1px solid #E2E8F0; border-radius:8px;
         padding:7px 16px; display:flex; flex-direction:column; gap:2px; justify-content:center; }
.hcard-lbl { font-size:9px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:#6B7280; }
.hcard-val { font-size:15px; font-weight:800; color:#002244; }
.divider     { height:3px; background:linear-gradient(90deg,#C8102E 60%,#002244 100%); margin-bottom:14px; border-radius:2px; }

/* ── Executive ULD summary panel ── */
.usp-panel { display:flex; gap:12px; margin-bottom:16px; }
.usp-block { flex:1; border-radius:8px; padding:10px 14px; border:2px solid; }
.usp-loaded  { background:#EFF6FF; border-color:#002244; }
.usp-empty   { background:#F4F5F7; border-color:#9AA3AF; }
.usp-damaged { background:#FDECEC; border-color:#C8102E; }
.usp-head { display:flex; align-items:center; justify-content:space-between;
            font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.04em;
            margin-bottom:8px; color:#1A1F29; }
.usp-total { font-size:16px; font-weight:900; padding:1px 10px; border-radius:12px; color:#fff; }
.usp-loaded .usp-total  { background:#002244; }
.usp-empty .usp-total   { background:#6B7280; }
.usp-damaged .usp-total { background:#C8102E; }
.usp-row { display:flex; justify-content:space-between; font-size:12.5px; padding:3px 0;
           border-bottom:1px dashed rgba(0,0,0,.08); }
.usp-row:last-child { border-bottom:none; }
.usp-type  { font-weight:700; color:#1A1F29; }
.usp-count { font-weight:800; color:#1A1F29; }
.usp-empty-msg { font-size:11px; color:#9AA3AF; }

/* ── Table ── */
table.dws { width:100%; border-collapse:collapse; font-size:13px; }
table.dws thead th {
    background:#002244; color:#fff; padding:7px 6px;
    font-size:11px; text-transform:uppercase; font-weight:700;
    border:1px solid #001422; white-space:nowrap; letter-spacing:.02em;
}
table.dws tbody td { padding:7px 8px; border-bottom:1px solid #E5E7EB; font-size:13px; vertical-align:middle; }
table.dws tr.uld-first td { border-top:2px solid #CBD5E1; }
table.dws tr.awb-row td { background:#FAFAFA; }
table.dws tbody tr:hover td { background:#F0F4FF; }

.uld-no  { font-weight:700; color:#002244; white-space:nowrap; }
.prio    { font-weight:700; color:#C8102E; text-align:center; white-space:nowrap; }
.dest    { font-weight:700; }
.awb     { font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em; font-size:12px; white-space:nowrap; }
.commodity { text-transform:uppercase; font-size:13px; }
.c       { text-align:center; }
.r       { text-align:right; }
.gross-kg { font-weight:700; color:#002244; }
.gross-lb { font-weight:700; color:#C8102E; }
.net-kg  { color:#374151; }
.net-lb  { color:#9AA3AF; }

table.dws tfoot td {
    background:#002244; color:#fff; font-weight:700;
    font-size:14px; padding:10px 8px; border-top:3px solid #001422;
}
table.dws tfoot td.red { color:#FCA5A5; }
table.dws tfoot td.dim { color:#94A3B8; font-size:10px; font-weight:400; }

@media print {
    @page { size:A4 portrait; margin:20mm 8mm 8mm 8mm; }
    body { padding:0; }
    table.dws thead { display:table-header-group; }
    tr { page-break-inside:avoid; }
    .usp-block, .hcard { -webkit-print-color-adjust:exact !important; print-color-adjust:exact !important; }
}
</style></head><body>

<div class="doc-header">
    <div class="logo-block">
        <img src="${logoUrl}" alt="Delta Cargo" style="height:90px;width:auto;">
        <div class="doc-title">OUTBOUND CARGO FLIGHT REPORT</div>
    </div>
    <div class="printed-on">Printed On: ${printedOn}</div>
</div>

<div class="hcards">
    <div class="hcard"><span class="hcard-lbl">Flight No.</span><span class="hcard-val">${esc(flt)}</span></div>
    <div class="hcard"><span class="hcard-lbl">Flight Date</span><span class="hcard-val">${dateLabel}</span></div>
    <div class="hcard"><span class="hcard-lbl">Route</span><span class="hcard-val">${esc(route)}</span></div>
    <div class="hcard"><span class="hcard-lbl">ETD</span><span class="hcard-val">${esc(etd||'\u2014')}</span></div>
    <div class="hcard"><span class="hcard-lbl">Manifest No.</span><span class="hcard-val">${esc(mn||'\u2014')}</span></div>
</div>

<div class="divider"></div>

<!-- ── Executive ULD summary — Loaded / Empty / Damaged by type ── -->
<div class="usp-panel">
    <div class="usp-block usp-loaded">
        <div class="usp-head">&#128230; Loaded ULD <span class="usp-total">${uspTotal(uldTypeCounts.loaded)}</span></div>
        ${uspRows(uldTypeCounts.loaded)}
    </div>
    <div class="usp-block usp-empty">
        <div class="usp-head">&#128235; Empty ULD <span class="usp-total">${uspTotal(uldTypeCounts.empty)}</span></div>
        ${uspRows(uldTypeCounts.empty)}
    </div>
    <div class="usp-block usp-damaged">
        <div class="usp-head">&#9888; Damaged ULD <span class="usp-total">${uspTotal(uldTypeCounts.damaged)}</span></div>
        ${uspRows(uldTypeCounts.damaged)}
    </div>
</div>

<table class="dws">
<thead><tr>
    <th style="width:12%;">ULD NO</th>
    <th style="width:7%;">PRIORITY</th>
    <th style="width:7%;">DEST</th>
    <th style="width:9%;text-align:right;">GROSS KGS</th>
    <th style="width:9%;text-align:right;">GROSS LBS</th>
    <th style="width:14%;">AWB NO</th>
    <th style="width:5%;text-align:center;">PCS</th>
    <th style="width:22%;">COMMODITY</th>
    <th style="width:7%;text-align:right;">NET KGS</th>
    <th style="width:7%;text-align:right;">NET LBS</th>
</tr></thead>
<tbody>${rows}</tbody>
<tfoot><tr>
    <td colspan="3" style="text-align:right;padding-right:14px;letter-spacing:.05em;">GENERAL TOTALS</td>
    <td class="r">${grandGrossKg.toLocaleString('en-US',{minimumFractionDigits:0})}</td>
    <td class="r red">${Math.round(grandGrossLb).toLocaleString('en-US')}</td>
    <td class="dim" style="text-align:center;"></td>
    <td class="c">${grandPcs.toLocaleString('en-US')}</td>
    <td></td>
    <td class="r">${grandNetKg.toLocaleString('en-US',{minimumFractionDigits:0})}</td>
    <td class="r red">${Math.round(grandNetLb).toLocaleString('en-US')}</td>
</tr></tfoot>
</table>

</body></html>`;

    return html;
}
async function mfLoadSendHistory() {
    const mn   = document.getElementById('mfManifestNumber')?.value || '';
    const box  = document.getElementById('mfSendHistory');
    const rows = document.getElementById('mfSendHistoryRows');
    if (!box || !rows || !/^\d{4}-\d+$/.test(mn)) { if (box) box.style.display='none'; return; }

    try {
        const res = await apiFetch('/shipments/manifest_send_api.php?history=' + encodeURIComponent(mn));
        if (!res.success || !res.data || !res.data.length) { box.style.display='none'; return; }

        const VER  = {1:'1er Preliminar', 2:'2do Preliminar', 3:'Definitivo'};
        const TYPE = {PRE_MANIFEST:'Pre-Manifest', ACS:'ACS Send', FLIGHT_SUMMARY:'Flight Summary', DEPARTURE_NOTICE:'Departure Notice'};

        let html = '<table style="width:100%;border-collapse:collapse;font-size:11px;">';
        html += '<tr style="background:var(--delta-navy);color:#fff;">';
        html += '<th style="padding:5px 8px;text-align:left;">Type</th>';
        html += '<th style="padding:5px 8px;text-align:left;">Version</th>';
        html += '<th style="padding:5px 8px;text-align:left;">Sent</th>';
        html += '<th style="padding:5px 8px;text-align:left;">Recipients</th>';
        html += '<th style="padding:5px 8px;text-align:center;">Status</th></tr>';

        res.data.forEach(function(r, i) {
            const bg        = i%2===0 ? '#fff' : '#F4F6F9';
            const verLabel  = VER[r.version]   || (r.version + (r.version===1?'st':r.version===2?'nd':r.version===3?'rd':'th') + ' send');
            const typeLabel = TYPE[r.send_type] || r.send_type;
            let emails = '—';
            try { const arr = JSON.parse(r.recipients_json||'[]'); emails = arr.length ? arr.join(', ') : '—'; } catch(e){}
            const sc   = r.status==='OK' ? '#2ECC71' : '#C8102E';
            const sl   = r.status==='OK' ? '✅ OK' : '❌ Error';
            const sent = r.sent_at ? r.sent_at.replace('T',' ').substring(0,16) : '—';
            html += '<tr style="background:' + bg + ';">';
            html += '<td style="padding:5px 8px;font-weight:600;color:var(--delta-navy);">' + esc(typeLabel) + '</td>';
            html += '<td style="padding:5px 8px;">' + esc(verLabel) + '</td>';
            html += '<td style="padding:5px 8px;color:var(--text-mid);">' + esc(sent) + '</td>';
            html += '<td style="padding:5px 8px;color:var(--text-mid);max-width:260px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;" title="' + esc(emails) + '">' + esc(emails) + '</td>';
            html += '<td style="padding:5px 8px;text-align:center;font-weight:700;color:' + sc + ';">' + sl + '</td></tr>';
            if (r.status==='ERROR' && r.error_msg) {
                html += '<tr style="background:#FFF8E7;"><td colspan="5" style="padding:4px 8px;color:#854F0B;font-size:10px;">⚠ ' + esc(r.error_msg) + '</td></tr>';
            }
        });
        html += '</table>';
        rows.innerHTML = html;
        box.style.display = 'block';
    } catch(e) { box.style.display='none'; }
}

// ══════════════════════════════════════════════════════════════
//  MÓDULO FLIGHT DEPARTURE (fdep*)
//  Único lugar del sistema donde una guía cambia a Shipped/Not Shipped
//  (LOADED/OFFLOADED) o se procesa un Split AWB — Pre-Manifest quedó
//  limitado a preparar el vuelo (ver Incorporación módulo Flight
//  Departure). Reutiliza el mismo look & feel y las mismas celdas de
//  estado/Split que ya existían en Pre-Manifest (mfEmbarqueCell,
//  mfChangeEmbarque, mfShowSplitModal), con permisos propios.
// ══════════════════════════════════════════════════════════════
let fdepCanEdit = false;
let fdepRows = [];

function flightDepartureInit() {
    fdepCanEdit = hasPermission('FlightDeparture', 'edit');

    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-navy)"><path d="M2.5 19h19"/><path d="M17.8 19V9.5a2 2 0 0 0-.6-1.4L11.6 2.5a2 2 0 0 0-2.8 0L3.2 8.1a2 2 0 0 0-.6 1.4V19"/><path d="M9 19v-5h4v5"/></svg>
            <div class="module-title">Flight Departure</div>
        </div>

        <div class="toolbar" style="align-items:flex-end;">
            <div class="awb-field"><label>STATION</label><select id="fdepStation" class="awb-input" onchange="fdepLoadFlights()" style="width:80px;"></select></div>
            <div class="awb-field"><label>FLIGHT</label><select id="fdepFlight" class="awb-input" onchange="fdepLoad()" style="width:160px;"><option value="">— Select —</option></select></div>
            <div class="awb-field"><label>FLIGHT DATE</label><input type="date" id="fdepDate" class="awb-input" onchange="fdepLoad()" style="width:150px;border:2px solid var(--delta-sky);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#EFF6FF;"></div>
            <div class="awb-field"><label>ETD</label><input type="time" id="fdepEtd" class="awb-input" readonly title="Automatically retrieved from Flight Profile for this flight/date." style="width:110px;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;"></div>
            <div class="awb-field"><label>MANIFEST N°</label><input type="text" id="fdepManifestNumber" class="awb-input" readonly style="width:150px;text-align:center;border:1px solid var(--border);border-radius:5px;padding:5px 8px;font-size:14px;font-weight:700;color:var(--delta-navy);background:#F5F5F5;"></div>
            <div class="mf-badge-row" style="margin-left:auto;">
                <div class="mf-badge"><div class="mf-badge-label">Total AWBs</div><div class="mf-badge-value" id="fdepBadgeAwbs">—</div></div>
                <div class="mf-badge"><div class="mf-badge-label">Shipped</div><div class="mf-badge-value" id="fdepBadgeShipped">—</div></div>
                <div class="mf-badge"><div class="mf-badge-label">Not Shipped</div><div class="mf-badge-value" id="fdepBadgeNotShipped">—</div></div>
            </div>
        </div>

        ${fdepCanEdit ? `
        <div style="display:flex;gap:10px;align-items:center;margin-bottom:12px;flex-wrap:wrap;">
            <button class="btn-primary" id="fdepBtnShipped" onclick="fdepBulkStatus('LOADED')" disabled
                style="background:#0F6E56;display:flex;align-items:center;gap:6px;opacity:.5;">
                &#10003; Mark Shipped
            </button>
            <button class="btn-secondary" id="fdepBtnNotShipped" onclick="fdepBulkStatus('OFFLOADED')" disabled
                style="border-color:#C8102E;color:#C8102E;display:flex;align-items:center;gap:6px;opacity:.5;">
                &#10005; Mark Not Shipped
            </button>
            <span id="fdepSelCount" style="font-size:12px;color:var(--text-mid);font-weight:600;"></span>
        </div>` : ''}

        <div class="card" style="overflow-x:auto;margin-bottom:14px;">
            <table class="data-table">
                <thead>
                    <tr>
                        <th style="width:36px;text-align:center;"><input type="checkbox" id="fdepCheckAll" onchange="fdepToggleAll(this)"></th>
                        <th style="width:50px;text-align:center;">Prio</th>
                        <th>AWB No.</th>
                        <th>Agent</th>
                        <th style="text-align:right;">Pieces</th>
                        <th style="text-align:right;">Gross Weight</th>
                        <th style="min-width:160px;">Status</th>
                        <th style="width:70px;text-align:center;">Split</th>
                    </tr>
                </thead>
                <tbody id="fdepTableBody"><tr><td colspan="8" class="empty-state">Select a flight and date.</td></tr></tbody>
            </table>
        </div>

        <div id="fdepMsg" style="font-size:13px;font-weight:600;"></div>
    `;

    populateStationSelect('fdepStation').then(() => fdepLoadFlights());
}

async function fdepLoadFlights() {
    const station = document.getElementById('fdepStation')?.value || 'LIM';
    try {
        const res = await apiFetch('/flights/flights_api.php?station=' + station);
        const flights = res.success ? res.data : [];
        const sel = document.getElementById('fdepFlight');
        if (!sel) return;
        sel.innerHTML = '<option value="">— Select —</option>';
        flights.forEach(f => {
            const o = document.createElement('option');
            o.value = f.flt_number;
            o.textContent = f.flt_number + '  ' + f.flt_orig + '→' + f.flt_dest;
            sel.appendChild(o);
        });
    } catch(e) {}
}

async function fdepLoadEtd(flt, date) {
    const el = document.getElementById('fdepEtd');
    if (!el) return;
    try {
        const res = await apiFetch('/flights/flight_operations_api.php?flt_number=' + encodeURIComponent(flt) + '&flt_date=' + date);
        el.value = (res.success && res.data && res.data.dep_time) ? String(res.data.dep_time).substring(0,5) : '';
    } catch(e) { el.value = ''; }
}

async function fdepLoad() {
    const flt  = document.getElementById('fdepFlight')?.value || '';
    const date = document.getElementById('fdepDate')?.value   || '';
    const tbody = document.getElementById('fdepTableBody');
    if (!flt || !date) {
        tbody.innerHTML = '<tr><td colspan="8" class="empty-state">Select a flight and date.</td></tr>';
        fdepRows = [];
        fdepUpdateBadges();
        document.getElementById('fdepManifestNumber').value = '';
        document.getElementById('fdepEtd').value = '';
        return;
    }
    fdepLoadEtd(flt, date);
    try {
        const params = new URLSearchParams({ flt_number: flt, flt_date: date });
        const res = await apiFetch('/shipments/shipment_manifest_api.php?' + params.toString());
        if (!res.success) {
            tbody.innerHTML = '<tr><td colspan="8" class="empty-state">Connection error.</td></tr>';
            return;
        }
        document.getElementById('fdepManifestNumber').value = res.manifest_number || '\u2014';

        // Flight Departure solo trabaja sobre guías ya incluidas en el manifiesto
        // (in_manifest=1) — Warehouse/ULD/DAM ya deben estar consolidados desde
        // Pre-Manifest antes de llegar a esta etapa (Incorporación Flight
        // Departure #6). Se excluyen las canceladas.
        fdepRows = (res.data || []).filter(function(r) {
            return r.in_manifest == 1 && r.operational_status !== 'CANCELLED';
        });
        fdepRenderRows();
    } catch(e) {
        tbody.innerHTML = '<tr><td colspan="8" class="empty-state">Connection error.</td></tr>';
    }
}

function fdepRenderRows() {
    const tbody = document.getElementById('fdepTableBody');
    if (!tbody) return;
    if (!fdepRows.length) {
        tbody.innerHTML = '<tr><td colspan="8" class="empty-state">No AWBs in this manifest.</td></tr>';
        fdepUpdateBadges();
        return;
    }
    let html = '';
    fdepRows.forEach(function(r) {
        const prioColor = !r.priority ? '#9AA3AF' : r.priority===1 ? '#0066B2' : r.priority===2 ? '#1A7A4A' : r.priority===3 ? '#E07B00' : '#B8860B';
        const canSplit = parseInt(r.pieces||0) > 0;
        const isLoaded = r.operational_status === 'LOADED';
        const splitBtn = !fdepCanEdit ? ''
            : !canSplit ? ''
            : isLoaded
                ? `<button title="Not available while the AWB is Loaded" disabled
                    style="border:1px solid #D1D5DB;color:#9CA3AF;background:#F9FAFB;
                    border-radius:4px;padding:2px 6px;font-size:10px;cursor:not-allowed;white-space:nowrap;opacity:.6;">✈ Split</button>`
                : `<button onclick="mfShowSplitModal(${r.id})" title="Partial / Offload AWB"
                    style="background:none;border:1px solid var(--delta-red);color:var(--delta-red);
                    border-radius:4px;padding:2px 6px;font-size:10px;cursor:pointer;white-space:nowrap;">✈ Split</button>`;
        html += `<tr data-id="${r.id}">
            <td style="text-align:center;"><input type="checkbox" class="fdep-check" data-id="${r.id}" ${fdepCanEdit?'':'disabled'} onchange="fdepUpdateSelection()"></td>
            <td style="text-align:center;font-weight:700;color:${prioColor};">${r.priority || '\u2014'}</td>
            <td style="font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-weight:700;color:var(--delta-navy);white-space:nowrap;">${esc(r.awb || r.shipment_number || '')}</td>
            <td>${esc(r.agent_name || '\u2014')}</td>
            <td style="text-align:right;">${fmtWgt0(r.pieces)}</td>
            <td style="text-align:right;">${fmtWgt0(r.gross_weight)}</td>
            <td>${mfEmbarqueCell(r, false, fdepCanEdit)}</td>
            <td style="text-align:center;">${splitBtn}</td>
        </tr>`;
    });
    tbody.innerHTML = html;
    fdepUpdateSelection();
}

function fdepToggleAll(cb) {
    document.querySelectorAll('.fdep-check:not(:disabled)').forEach(function(c) { c.checked = cb.checked; });
    fdepUpdateSelection();
}

function fdepUpdateSelection() {
    const checked = Array.from(document.querySelectorAll('.fdep-check:checked'));
    const btnShipped    = document.getElementById('fdepBtnShipped');
    const btnNotShipped = document.getElementById('fdepBtnNotShipped');
    const label = document.getElementById('fdepSelCount');
    const n = checked.length;
    [btnShipped, btnNotShipped].forEach(function(b) {
        if (!b) return;
        b.disabled = n === 0;
        b.style.opacity = n === 0 ? '.5' : '1';
    });
    if (label) label.textContent = n ? n + ' selected' : '';
    fdepUpdateBadges();
}

function fdepUpdateBadges() {
    const total  = fdepRows.length;
    const shipped = fdepRows.filter(function(r){ return r.operational_status === 'LOADED'; }).length;
    const notShipped = fdepRows.filter(function(r){ return r.operational_status === 'OFFLOADED'; }).length;
    const setTxt = function(id, v) { const el = document.getElementById(id); if (el) el.textContent = v; };
    setTxt('fdepBadgeAwbs', total || '\u2014');
    setTxt('fdepBadgeShipped', shipped || '0');
    setTxt('fdepBadgeNotShipped', notShipped || '0');
}

// ── Cambio de estado masivo (Incorporación Flight Departure #4): aplica
// a todas las guías seleccionadas mediante checkboxes, con confirmación
// previa. Reutiliza el mismo endpoint que el cambio individual. ──
async function fdepBulkStatus(newStatus) {
    if (!fdepCanEdit) return;
    const checked = Array.from(document.querySelectorAll('.fdep-check:checked')).map(function(c) { return parseInt(c.dataset.id); });
    const msg = document.getElementById('fdepMsg');
    if (!checked.length) {
        if (msg) { msg.style.color = 'var(--delta-red)'; msg.textContent = 'Select at least one AWB.'; }
        return;
    }
    const label = newStatus === 'LOADED' ? 'SHIPPED' : 'NOT SHIPPED';
    if (!confirm('Confirm changing the status of the ' + checked.length + ' selected AWB(s) to ' + label + '?')) return;

    if (msg) { msg.style.color = '#854F0B'; msg.textContent = 'Updating ' + checked.length + ' AWB(s)...'; }
    let okCount = 0, errCount = 0;
    for (const shipmentId of checked) {
        try {
            const res = await apiFetch('/shipments/shipment_status_api.php', {
                method: 'POST',
                body: JSON.stringify({
                    shipment_id: shipmentId,
                    new_status:  newStatus,
                    reason:      'Bulk status change from Flight Departure',
                    user_id:     currentUser?.id || null,
                })
            });
            if (res.success) okCount++; else errCount++;
        } catch(e) { errCount++; }
    }
    if (msg) {
        msg.style.color = errCount ? 'var(--delta-red)' : '#0F6E56';
        msg.textContent = '\u2713 ' + okCount + ' AWB(s) updated to ' + label + (errCount ? ' — ' + errCount + ' failed.' : '.');
    }
    await fdepLoad();
}
</script>


<script>
// ══════════════════════════════════════════════════════════════
//  MÓDULO CONTROL DE CALIDAD (val*)
// ══════════════════════════════════════════════════════════════
let valRows = [];
let valFilterStatus = 'PENDING_REVIEW';

function validateInit() {
    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-navy)"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg>
            <div class="module-title">Control de Calidad</div>
            <div class="module-badge" id="valBadge">0 guías</div>
        </div>

        <div class="toolbar">
            <label class="tb-lbl">Estado de Calidad</label>
            <select id="valFilterStatus" onchange="valOnFilterChange()" style="width:180px;">
                <option value="PENDING_REVIEW">Pendiente de Revisión</option>
                <option value="VALIDATED">Validadas</option>
                <option value="NOT_READY">No Listas</option>
            </select>
            <div class="toolbar-sep"></div>
            <label class="tb-lbl">Estación</label>
            <select id="valFilterStation" onchange="valLoad()" style="width:80px;"></select>
            <div style="margin-left:auto;display:flex;gap:8px;align-items:center;">
                <span id="valSelCount" style="font-size:12px;color:var(--text-mid);font-weight:600;">0 seleccionada(s)</span>
                <button class="btn-secondary" onclick="valOpenColMgr()">
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
                    Columnas
                </button>
                <button class="btn-primary" id="valBtnValidate" onclick="valBulkValidate()" disabled>
                    <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M9 12l2 2 4-4"/><circle cx="12" cy="12" r="10"/></svg>
                    Validar Seleccionadas
                </button>
            </div>
        </div>

        <div style="background:#FFF8E1;border:1px solid #FFE082;border-radius:6px;padding:10px 14px;margin-bottom:14px;font-size:12px;color:#946C00;display:flex;align-items:center;gap:8px;">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
            Solo las guías marcadas como <b>Validadas</b> aparecerán en el dashboard de Revenues &amp; Avg Rate.
        </div>

        <div class="card" style="overflow-x:auto;">
            <div id="valTableWrap"></div>
        </div>
    `;
    populateStationSelect('valFilterStation', [['', 'Todas']]).then(() => valLoad());
}

// Columnas del listado de Control de Calidad
const VAL_COLS = [
    { key:'check',     label:'Selección',   def:true, fixed:true },
    { key:'awb',       label:'AWB',         def:true, fixed:true },
    { key:'flight',    label:'Vuelo',       def:true, fixed:true },
    { key:'date',      label:'Fecha',       def:true, fixed:true },
    { key:'manifest',  label:'Manifiesto',  def:true,  fixed:false },
    { key:'agent',     label:'Agente',      def:true,  fixed:false },
    { key:'shipper',   label:'Shipper',     def:true,  fixed:false },
    { key:'consignee', label:'Consignee',   def:false, fixed:false },
    { key:'pieces',    label:'Piezas',      def:true,  fixed:false },
    { key:'chg_wgt',   label:'Peso Carg.',  def:true,  fixed:false },
    { key:'status',    label:'Estado Op.',  def:true, fixed:true },
    { key:'quality',   label:'Calidad',     def:true, fixed:true },
    { key:'actions',   label:'Acciones',    def:true, fixed:true },
];
function valOpenColMgr() { colmgrOpen('validate', VAL_COLS, () => valRender()); }

function valOnFilterChange() {
    valFilterStatus = document.getElementById('valFilterStatus').value;
    valLoad();
}

async function valLoad() {
    const station = document.getElementById('valFilterStation')?.value || '';
    const params = new URLSearchParams({ status: valFilterStatus });
    if (station) params.set('station', station);

    try {
        const res = await apiFetch('/shipments/shipment_quality_api.php?' + params.toString());
        valRows = res.success ? res.data : [];
        valRender();
    } catch(e) {
        document.getElementById('valTableWrap').innerHTML = '<div class="empty-state">Error de conexión.</div>';
    }
}

function valRender() {
    document.getElementById('valBadge').textContent = valRows.length + ' guía' + (valRows.length !== 1 ? 's' : '');
    const wrap = document.getElementById('valTableWrap');

    if (!valRows.length) {
        wrap.innerHTML = '<div class="empty-state">No hay guías en este estado.</div>';
        valUpdateSelCount();
        return;
    }

    const showCheckbox = valFilterStatus === 'PENDING_REVIEW' && hasPermission('Validate','create');
    const settings = colmgrLoadSettings('validate', VAL_COLS).cols;

    let html = '<table class="data-table"><thead><tr>';
    if (showCheckbox) html += '<th style="width:36px;text-align:center;"><input type="checkbox" id="valCheckAll" onchange="valToggleAll(this)"></th>';
    html += '<th>AWB</th><th>Vuelo</th><th>Fecha</th>';
    if (settings.manifest)  html += '<th>Manifiesto</th>';
    if (settings.agent)     html += '<th>Agente</th>';
    if (settings.shipper)   html += '<th>Shipper</th>';
    if (settings.consignee) html += '<th>Consignee</th>';
    if (settings.pieces)    html += '<th style="text-align:right;">Piezas</th>';
    if (settings.chg_wgt)   html += '<th style="text-align:right;">Peso Carg.</th>';
    html += '<th>Estado Op.</th><th>Calidad</th><th>Acciones</th></tr></thead><tbody>';

    valRows.forEach(s => {
        html += '<tr>';
        if (showCheckbox) html += `<td style="text-align:center;"><input type="checkbox" class="val-check" data-id="${s.id}" onchange="valUpdateSelCount()"></td>`;
        html += `<td><strong style="font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:var(--delta-navy);">${esc(s.awb||s.shipment_number)}</strong></td>`;
        html += `<td>${esc(s.flt_number||'—')}</td>`;
        html += `<td style="white-space:nowrap;">${fmtDate(s.flt_date)}</td>`;
        if (settings.manifest)  html += `<td style="font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:#7B2D8B;font-size:12px;">${esc(s.manifest_number||'—')}</td>`;
        if (settings.agent)     html += `<td style="max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(s.agent_name||'—')}</td>`;
        if (settings.shipper)   html += `<td style="max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(s.shipper_name||'—')}</td>`;
        if (settings.consignee) html += `<td style="max-width:130px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(s.consignee_name||'—')}</td>`;
        if (settings.pieces)    html += `<td style="text-align:right;">${s.pieces||0}</td>`;
        if (settings.chg_wgt)   html += `<td style="text-align:right;">${fmtNum(s.chg_weight)} kg</td>`;
        html += `<td>${operationalStatusBadge(s.operational_status, s, 'valLoad', hasPermission('AWBEntry','edit'))}</td>`;
        html += `<td>${qualityStatusBadge(s.quality_status)}</td>`;

        let actions = '';
        if (hasPermission('AWBEntry','edit')) {
            actions += `<button class="action-btn-sm" onclick="(window.awbEntryEdit||function(x){alert('awbEntryEdit not defined - check console');})(${s.id})">✎ Revisar/Editar</button>`;
        }
        if (s.quality_status === 'PENDING_REVIEW' && hasPermission('Validate','create')) {
            actions += `<button class="action-btn-sm" style="border-color:#2ECC71;color:#1E7E34;" onclick="valValidateSingle(${s.id})">✓ Validar</button>`;
        }
        if (s.quality_status === 'VALIDATED' && hasPermission('Validate','create')) {
            actions += `<button class="action-btn-sm danger" onclick="valRevert(${s.id})">↩ Revertir</button>`;
        }
        html += `<td><div style="display:flex;gap:5px;flex-wrap:wrap;">${actions || '<span style="color:var(--text-light);font-size:11px;">—</span>'}</div></td>`;
        html += '</tr>';
    });
    html += '</tbody></table>';
    wrap.innerHTML = html;
    valUpdateSelCount();
}

function valToggleAll(checkbox) {
    document.querySelectorAll('.val-check').forEach(c => c.checked = checkbox.checked);
    valUpdateSelCount();
}
function valUpdateSelCount() {
    const n = document.querySelectorAll('.val-check:checked').length;
    const el = document.getElementById('valSelCount');
    if (el) el.textContent = n + ' seleccionada(s)';
    const btn = document.getElementById('valBtnValidate');
    if (btn) btn.disabled = n === 0;
}

async function valValidateSingle(id) {
    await valChangeQuality([id], 'VALIDATED');
}
async function valRevert(id) {
    if (!confirm('¿Revertir esta guía a Pendiente de Revisión? Dejará de aparecer en Revenues.')) return;
    await valChangeQuality([id], 'PENDING_REVIEW');
}
async function valBulkValidate() {
    const ids = Array.from(document.querySelectorAll('.val-check:checked')).map(c => parseInt(c.dataset.id));
    if (!ids.length) return;
    if (!confirm(`¿Validar ${ids.length} guía(s) seleccionada(s)? Pasarán a estar disponibles en Revenues.`)) return;
    await valChangeQuality(ids, 'VALIDATED');
}

async function valChangeQuality(ids, newStatus) {
    try {
        const res = await apiFetch('/shipments/shipment_quality_api.php', {
            method: 'POST',
            body: JSON.stringify({
                shipment_ids: ids,
                new_status: newStatus,
                user_id: currentUser?.id || null,
            })
        });
        if (res.success) {
            let msg = res.updated_count + ' actualizada(s)';
            if (res.skipped_count) msg += ', ' + res.skipped_count + ' omitida(s)';
            if (res.error_count) {
                msg += ', ' + res.error_count + ' con error';
                const firstErr = res.results.errors[0];
                if (firstErr) msg += ': ' + firstErr.message;
            }
            alert(msg);
            valLoad();
        } else {
            alert(res.message || 'Error al actualizar.');
        }
    } catch(e) {
        alert('Error de conexión.');
    }
}
</script>


<style>
.kpi-row { display: flex; gap: 14px; margin-bottom: 16px; flex-wrap: wrap; }
.kpi-card {
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  padding: 16px 20px; flex: 1; min-width: 160px; box-shadow: var(--shadow-sm);
  border-top: 3px solid transparent;
}
.kpi-card.c-revenue { border-top-color: var(--delta-red); }
.kpi-card.c-awbs    { border-top-color: var(--delta-navy); }
.kpi-card.c-rate    { border-top-color: var(--delta-accent); }
.kpi-card.c-weight  { border-top-color: #0066B2; }
.kpi-label {
  font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-mid);
  letter-spacing: .05em; margin-bottom: 6px;
}
.kpi-value {
  font-family: 'Barlow Condensed', sans-serif; font-size: 28px; font-weight: 800; color: var(--delta-navy);
}
.kpi-sub { font-size: 11px; color: var(--text-light); margin-top: 2px; }

.rev-grid {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 14px;
}
.rev-panel {
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  overflow: hidden; display: flex; flex-direction: column;
}
.rev-panel-header {
  display: flex; align-items: center; padding: 10px 16px; border-bottom: 1px solid var(--border);
  border-left: 3px solid var(--delta-red);
}
.rev-panel-title {
  font-family: 'Barlow Condensed', sans-serif; font-weight: 700; font-size: 14px;
  color: var(--delta-navy); text-transform: uppercase; letter-spacing: .03em;
}
.rev-panel-badge { margin-left: auto; font-size: 11px; color: var(--text-mid); }
.rev-panel-body { padding: 14px 16px; flex: 1; min-height: 0; }
.rev-canvas-container { overflow-y: auto; overflow-x: hidden; }
.rev-table-panel { background: #fff; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }

.pill-label {
  font-size: 10px; font-weight: 700; text-transform: uppercase; color: var(--text-mid);
  letter-spacing: .05em; margin-left: 10px; margin-right: 4px;
}
.pill-group { display: flex; gap: 4px; }
.slicer-btn {
  height: 30px; padding: 0 12px; border: 1px solid var(--border); background: #fff;
  border-radius: 4px; font-size: 12px; font-weight: 700; cursor: pointer; color: var(--text-mid);
}
.slicer-btn.active { background: var(--delta-navy); color: #fff; border-color: var(--delta-navy); }
.filter-chip {
  display: inline-flex; align-items: center; gap: 6px; background: #E8F0FE; color: var(--delta-sky);
  border-radius: 12px; padding: 4px 10px; font-size: 11px; font-weight: 700; cursor: pointer; margin-right: 6px;
}

/* ── Booking Forecast (bf*): grid del dashboard + multi-select ── */
.bf-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-bottom: 14px; }
.bf-chart-panel { grid-column: 1 / -1; }
.bf-multiselect { position: relative; display: inline-block; }
.bf-multiselect-btn {
  height: 30px; padding: 0 10px; border: 1px solid var(--border); background: #fff;
  border-radius: 4px; font-size: 12px; font-weight: 600; color: var(--text-mid); cursor: pointer;
  display: flex; align-items: center; gap: 6px; white-space: nowrap;
}
.bf-multiselect-btn.has-value { border-color: var(--delta-sky); color: var(--delta-navy); background: #EFF6FF; }
.bf-multiselect-panel {
  position: absolute; top: 34px; left: 0; z-index: 50; background: #fff; border: 1px solid var(--border);
  border-radius: 6px; box-shadow: 0 4px 16px rgba(0,0,0,.12); width: 240px; max-height: 280px;
  display: flex; flex-direction: column;
}
.bf-multiselect-search { padding: 8px; border-bottom: 1px solid var(--border); }
.bf-multiselect-search input { width: 100%; height: 28px; border: 1px solid var(--border); border-radius: 4px; padding: 0 8px; font-size: 12px; }
.bf-multiselect-list { overflow-y: auto; padding: 4px 0; flex: 1; }
.bf-multiselect-list label { display: flex; align-items: center; gap: 7px; padding: 5px 10px; font-size: 12px; cursor: pointer; }
.bf-multiselect-list label:hover { background: #F4F6F9; }
.bf-multiselect-footer { display: flex; justify-content: space-between; padding: 6px 10px; border-top: 1px solid var(--border); }
.bf-multiselect-footer button { background: none; border: none; font-size: 11px; font-weight: 700; color: var(--delta-sky); cursor: pointer; }
@media (max-width: 900px) {
  .bf-grid { grid-template-columns: 1fr; }
}
</style>

<script>
// ══════════════════════════════════════════════════════════════
//  MÓDULO REVENUES & AVG RATE (rev*)
//  Lee EXCLUSIVAMENTE shipments con quality_status = VALIDATED
//  Réplica funcional del dashboard v1: KPIs, Agent/Goods/Dest
//  breakdown (hbar interactivos), gráfico combinado con scroll
//  de meses (rueda del mouse), y tabla de detalle.
// ══════════════════════════════════════════════════════════════
let revAllData      = [];      // dataset completo (todas las estaciones del usuario)
let revChart         = null;   // instancia del gráfico combinado principal
let revSelectedMetric    = 'Freight';   // 'Freight' | 'Total Carrier'
let revSelectedRateMode  = 'Inc Min';   // 'Inc Min' | 'Rate net'
let revSelectedAgent = null, revSelectedGoods = null, revSelectedDest = null;

let revAgentChart = null, revGoodsChart = null, revDestChart = null;
const REV_ROW_PX = 26;

// ── Revenues chart (combinado, con scroll de meses) ──
let revChartAllKeys = [], revChartAllData = {};
let revChartWindowSize = 12, revChartOffset = 0;
let revChartSelAgent = null, revChartRevCol = 'Freight';

const REV_MONTH_NAMES = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
const revUsdFmt     = new Intl.NumberFormat('en-US', { style:'currency', currency:'USD', minimumFractionDigits:2, maximumFractionDigits:2 });
const revCompactFmt = new Intl.NumberFormat('en-US', { style:'currency', currency:'USD', notation:'compact', maximumFractionDigits:1 });
const revNoCentsFmt = new Intl.NumberFormat('en-US', { style:'currency', currency:'USD', maximumFractionDigits:0 });
const revIntFmt     = new Intl.NumberFormat('en-US');

function revenuesInit() {
    document.getElementById('mainContent').innerHTML = `
        <div class="toolbar" style="margin-bottom:14px;">
            <label class="tb-lbl">Station</label>
            <select id="revFilterStation" onchange="revLoad()" style="width:110px;"></select>
            <div class="toolbar-sep"></div>
            <span class="pill-label">Metric</span>
            <div class="pill-group">
                <button id="revBtnFreight" class="slicer-btn active" onclick="revSetMetric('Freight')">Freight</button>
                <button id="revBtnTotalCarrier" class="slicer-btn" onclick="revSetMetric('Total Carrier')">Total Carrier</button>
            </div>
            <span class="pill-label">Rate</span>
            <div class="pill-group">
                <button id="revBtnRateNet" class="slicer-btn" onclick="revSetRateMode('Rate net')">Rate Net</button>
                <button id="revBtnIncMin" class="slicer-btn active" onclick="revSetRateMode('Inc Min')">Inc Min</button>
            </div>
            <div style="margin-left:auto;">
                <button class="btn-secondary" onclick="revExportExcel()">
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                    Export Excel
                </button>
            </div>
        </div>

        <div id="revActiveFilters" style="margin-bottom:10px;"></div>

        <div class="kpi-row">
            <div class="kpi-card c-revenue"><div class="kpi-label">Total Revenue</div><div class="kpi-value" id="revKpiRevenue">—</div><div class="kpi-sub">selected period</div></div>
            <div class="kpi-card c-awbs"><div class="kpi-label">Total AWBs</div><div class="kpi-value" id="revKpiAwbs">—</div><div class="kpi-sub">shipments</div></div>
            <div class="kpi-card c-rate"><div class="kpi-label">Avg Rate / Kg</div><div class="kpi-value" id="revKpiRate">—</div><div class="kpi-sub">charged weight</div></div>
            <div class="kpi-card c-weight"><div class="kpi-label">Total Chg Weight</div><div class="kpi-value" id="revKpiWeight">—</div><div class="kpi-sub">kg</div></div>
        </div>

        <div class="rev-grid">
            <div class="rev-panel">
                <div class="rev-panel-header"><span class="rev-panel-title">Agent Breakdown</span><span class="rev-panel-badge" id="revAgentBadge">—</span></div>
                <div class="rev-panel-body"><div class="rev-canvas-container" id="revAgentContainer"><canvas id="revAgentChart"></canvas></div></div>
            </div>
            <div class="rev-panel">
                <div class="rev-panel-header"><span class="rev-panel-title">Revenues &amp; Average Rate</span><span class="rev-panel-badge" id="revChartBadge">12-month view</span></div>
                <div class="rev-panel-body" id="revChartPanelBody" style="height:300px;"><canvas id="revMainChart"></canvas></div>
            </div>
            <div class="rev-panel">
                <div class="rev-panel-header"><span class="rev-panel-title">Goods Type</span><span class="rev-panel-badge" id="revGoodsBadge">—</span></div>
                <div class="rev-panel-body"><div class="rev-canvas-container" id="revGoodsContainer"><canvas id="revGoodsChart"></canvas></div></div>
            </div>
            <div class="rev-panel">
                <div class="rev-panel-header"><span class="rev-panel-title">Destination</span><span class="rev-panel-badge" id="revDestBadge">—</span></div>
                <div class="rev-panel-body"><div class="rev-canvas-container" id="revDestContainer"><canvas id="revDestChart"></canvas></div></div>
            </div>
        </div>

        <div class="rev-table-panel">
            <div class="rev-panel-header"><span class="rev-panel-title">Data Details</span><span class="rev-panel-badge" id="revTableBadge">—</span></div>
            <div style="overflow-x:auto;">
                <table class="data-table">
                    <thead><tr>
                        <th>Flt. Date</th><th>AWB Number</th><th>Freight Forwarder</th><th>Dest</th>
                        <th>Description</th><th style="text-align:right;">Chg Wgt</th><th style="text-align:right;">Net Rate</th>
                        <th style="text-align:right;" id="revThMetric">Freight</th><th style="text-align:right;">Due Carrier</th><th style="text-align:right;">Tot Rev DL</th>
                    </tr></thead>
                    <tbody id="revTableBody"></tbody>
                </table>
            </div>
        </div>
    `;

    populateStationSelect('revFilterStation', [['', 'All Stations']]).then(() => revLoad());
}

// ── Cargar datos crudos (todas las guías VALIDATED) ──────────
async function revLoad() {
    const station = document.getElementById('revFilterStation')?.value || '';
    const params = new URLSearchParams({ quality: 'VALIDATED' });
    if (station) params.set('station', station);

    try {
        const res = await apiFetch('/shipments/shipments_api.php?' + params.toString());
        const raw = res.success ? res.data : [];
        revAllData = raw.map(r => {
            const fltDate = r.flt_date ? new Date(r.flt_date + 'T00:00:00') : null;
            const yr = fltDate ? fltDate.getFullYear() : 0;
            const mo = fltDate ? fltDate.getMonth() : 0;
            const freight = parseFloat(r.freight_revenue) || 0;
            const totalDc = parseFloat(r.total_dc) || 0;
            return {
                awb: r.awb || r.shipment_number,
                agent: r.agent_name || 'UNKNOWN',
                dest: r.flt_dest || '—',
                description: r.description || 'N/A',
                chgWgt: parseFloat(r.chg_weight) || 0,
                netRate: parseFloat(r.net_rate) || 0,
                freight: freight,
                dueAgent: parseFloat(r.due_agent) || 0,
                totalDc: totalDc,
                totalAwb: freight + totalDc,
                incMin: r.inc_min ? 1 : 0,
                fltDate: fltDate,
                monthYear: fltDate ? (REV_MONTH_NAMES[mo] + '-' + String(yr).slice(-2)) : '',
                yearMonthSort: fltDate ? yr * 100 + (mo + 1) : 0,
            };
        });
        revUpdateDashboard();
    } catch(e) {
        console.warn('revLoad error:', e);
    }
}

// ── Métrica / Rate toggles ───────────────────────────────────
function revSetMetric(m) {
    revSelectedMetric = m;
    document.getElementById('revBtnFreight').classList.toggle('active', m === 'Freight');
    document.getElementById('revBtnTotalCarrier').classList.toggle('active', m === 'Total Carrier');
    document.getElementById('revThMetric').innerText = m;
    revUpdateDashboard();
}
function revSetRateMode(f) {
    revSelectedRateMode = f;
    document.getElementById('revBtnRateNet').classList.toggle('active', f === 'Rate net');
    document.getElementById('revBtnIncMin').classList.toggle('active', f === 'Inc Min');
    revUpdateDashboard();
}

// ── Orquestador principal (replica updateDashboard de v1) ────
function revUpdateDashboard(skipChartRender) {
    if (!revAllData.length) {
        document.getElementById('revKpiRevenue').textContent = '$0';
        document.getElementById('revKpiAwbs').textContent = '0';
        document.getElementById('revKpiRate').textContent = '$0.00';
        document.getElementById('revKpiWeight').textContent = '0';
        return;
    }

    let currentSort;
    if (revChartAllKeys.length > 0) {
        const total = revChartAllKeys.length;
        const winSize = Math.min(revChartWindowSize, total);
        const endIdx = total - revChartOffset;
        currentSort = parseInt(revChartAllKeys[Math.min(endIdx - 1, total - 1)]);
    } else {
        const sorts = revAllData.map(d => d.yearMonthSort).filter(Boolean);
        currentSort = sorts.length ? Math.max(...sorts) : 0;
    }

    const selYear = Math.floor(currentSort / 100), selMonth = currentSort % 100;
    let earlyYear = selYear, earlyMo = selMonth - 11;
    if (earlyMo <= 0) { earlyMo += 12; earlyYear -= 1; }
    const earliestSort = earlyYear * 100 + earlyMo;

    const rateFilter = d => revSelectedRateMode === 'Rate net' ? d.incMin === 0 : true;

    let baseData = revAllData.filter(d => rateFilter(d) && d.yearMonthSort <= currentSort && d.yearMonthSort >= earliestSort);
    let chartData = revAllData.filter(rateFilter);

    const isAgent = d => !revSelectedAgent || d.agent === revSelectedAgent;
    const isGoods = d => !revSelectedGoods || d.description === revSelectedGoods;
    const isDest  = d => !revSelectedDest  || d.dest === revSelectedDest;

    const dataForAgent = baseData.filter(d => isGoods(d) && isDest(d));
    const dataForGoods = baseData.filter(d => isAgent(d) && isDest(d));
    const dataForDest  = baseData.filter(d => isAgent(d) && isGoods(d));
    const fullData      = baseData.filter(d => isAgent(d) && isGoods(d) && isDest(d));
    const revenueCol    = revSelectedMetric === 'Freight' ? 'freight' : 'totalAwb';
    const chartFiltered = chartData.filter(d => isGoods(d) && isDest(d));

    revUpdateKpis(fullData, revenueCol);
    revAgentChart = revRenderHBar('revAgentChart','revAgentContainer', dataForAgent,'agent', revenueCol, revAgentChart, revSelectedAgent, v => { revSelectedAgent = revSelectedAgent===v?null:v; revUpdateDashboard(); });
    revGoodsChart = revRenderHBar('revGoodsChart','revGoodsContainer', dataForGoods,'description', revenueCol, revGoodsChart, revSelectedGoods, v => { revSelectedGoods = revSelectedGoods===v?null:v; revUpdateDashboard(); });
    revDestChart  = revRenderHBar('revDestChart','revDestContainer',  dataForDest, 'dest', revenueCol, revDestChart, revSelectedDest,  v => { revSelectedDest  = revSelectedDest ===v?null:v; revUpdateDashboard(); });

    if (!skipChartRender) {
        revRenderMainChart(chartFiltered, revSelectedAgent, revenueCol, currentSort);
    } else {
        revChartSelAgent = revSelectedAgent; revChartRevCol = revenueCol;
        const grouped = {};
        chartFiltered.forEach(row => {
            const k = row.yearMonthSort;
            if (!grouped[k]) grouped[k] = { label: row.monthYear, revTotal:0, wgtTotal:0, revSelected:0, wgtSelected:0, revOthers:0, wgtOthers:0 };
            const r = row[revenueCol] || 0, w = row.chgWgt || 0;
            grouped[k].revTotal += r; grouped[k].wgtTotal += w;
            if (revSelectedAgent && row.agent === revSelectedAgent) { grouped[k].revSelected += r; grouped[k].wgtSelected += w; }
            else { grouped[k].revOthers += r; grouped[k].wgtOthers += w; }
        });
        revChartAllKeys = Object.keys(grouped).sort((a,b) => a-b);
        revChartAllData = grouped;
        revDrawMainChart();
    }

    revRenderTable(fullData, revenueCol);
    revRenderActiveFilters();
    revUpdateBadges(dataForAgent, dataForGoods, dataForDest, fullData);
}

function revUpdateKpis(data, revenueCol) {
    const totalRev = data.reduce((s,d) => s + (d[revenueCol]||0), 0);
    const totalAwbs = data.length;
    const totalWgt = data.reduce((s,d) => s + (d.chgWgt||0), 0);
    const avgRate = totalWgt > 0 ? totalRev / totalWgt : 0;
    document.getElementById('revKpiRevenue').textContent = revCompactFmt.format(totalRev);
    document.getElementById('revKpiAwbs').textContent = totalAwbs.toLocaleString('en-US');
    document.getElementById('revKpiRate').textContent = revUsdFmt.format(avgRate);
    document.getElementById('revKpiWeight').textContent = revIntFmt.format(totalWgt);
}

function revUpdateBadges(dAgent, dGoods, dDest, dFull) {
    const uniq = (arr, key) => new Set(arr.map(r => r[key])).size;
    document.getElementById('revAgentBadge').textContent = uniq(dAgent,'agent') + ' agents';
    document.getElementById('revGoodsBadge').textContent = uniq(dGoods,'description') + ' types';
    document.getElementById('revDestBadge').textContent = uniq(dDest,'dest') + ' routes';
    document.getElementById('revTableBadge').textContent = Math.min(dFull.length,50) + ' rows shown';
}

function revRenderActiveFilters() {
    const c = document.getElementById('revActiveFilters');
    c.innerHTML = '';
    const add = (label, value, clearFn) => {
        if (!value) return;
        const chip = document.createElement('div');
        chip.className = 'filter-chip';
        chip.innerHTML = `<span>${label}:</span> ${esc(value)} ✕`;
        chip.onclick = clearFn;
        c.appendChild(chip);
    };
    add('Agent', revSelectedAgent, () => { revSelectedAgent = null; revUpdateDashboard(); });
    add('Goods', revSelectedGoods, () => { revSelectedGoods = null; revUpdateDashboard(); });
    add('Dest',  revSelectedDest,  () => { revSelectedDest  = null; revUpdateDashboard(); });
}

// ── Gráfico combinado (barras + línea) con scroll de meses ───
function revRenderMainChart(data, selAgent, revenueCol, currentSort) {
    revChartSelAgent = selAgent;
    revChartRevCol = revenueCol;

    const grouped = {};
    data.forEach(row => {
        const k = row.yearMonthSort;
        if (!grouped[k]) grouped[k] = { label: row.monthYear, revTotal:0, wgtTotal:0, revSelected:0, wgtSelected:0, revOthers:0, wgtOthers:0 };
        const r = row[revenueCol] || 0, w = row.chgWgt || 0;
        grouped[k].revTotal += r; grouped[k].wgtTotal += w;
        if (selAgent && row.agent === selAgent) { grouped[k].revSelected += r; grouped[k].wgtSelected += w; }
        else { grouped[k].revOthers += r; grouped[k].wgtOthers += w; }
    });

    revChartAllKeys = Object.keys(grouped).sort((a,b) => a-b);
    revChartAllData = grouped;

    if (currentSort && revChartAllKeys.length > 0) {
        const sortIdx = revChartAllKeys.indexOf(String(currentSort));
        revChartOffset = sortIdx >= 0 ? Math.max(0, revChartAllKeys.length - 1 - sortIdx) : 0;
    } else {
        revChartOffset = 0;
    }

    // Re-vincular el listener de scroll (clonar para limpiar listeners previos)
    const panelBody = document.getElementById('revChartPanelBody');
    const newPanelBody = panelBody.cloneNode(false);
    while (panelBody.firstChild) newPanelBody.appendChild(panelBody.firstChild);
    panelBody.parentNode.replaceChild(newPanelBody, panelBody);
    newPanelBody.id = 'revChartPanelBody';
    newPanelBody.style.cursor = 'ew-resize';
    newPanelBody.title = 'Scroll para navegar meses';

    newPanelBody.addEventListener('wheel', (e) => {
        e.preventDefault(); e.stopPropagation();
        const maxOffset = Math.max(0, revChartAllKeys.length - revChartWindowSize);
        if (e.deltaY > 0 || e.deltaX > 0) revChartOffset = Math.min(revChartOffset + 1, maxOffset);
        else revChartOffset = Math.max(revChartOffset - 1, 0);
        revDrawMainChart();
        revUpdateDashboard(true);
    }, { passive: false });

    let touchStartX = 0;
    newPanelBody.addEventListener('touchstart', e => { touchStartX = e.touches[0].clientX; }, { passive: true });
    newPanelBody.addEventListener('touchend', e => {
        const diff = touchStartX - e.changedTouches[0].clientX;
        const maxOffset = Math.max(0, revChartAllKeys.length - revChartWindowSize);
        if (Math.abs(diff) > 30) {
            if (diff > 0) revChartOffset = Math.min(revChartOffset + 1, maxOffset);
            else revChartOffset = Math.max(revChartOffset - 1, 0);
            revDrawMainChart();
            revUpdateDashboard(true);
        }
    }, { passive: true });

    revChartOffset = 0;
    revDrawMainChart();
}

function revDrawMainChart() {
    const allKeys = revChartAllKeys, grouped = revChartAllData;
    const selAgent = revChartSelAgent, revenueCol = revChartRevCol;

    const total = allKeys.length;
    const winSize = Math.min(revChartWindowSize, total);
    const endIdx = total - revChartOffset;
    const startIdx = Math.max(0, endIdx - winSize);
    const keys = allKeys.slice(startIdx, endIdx);
    const labels = keys.map(k => grouped[k].label);

    const barData = [], lineTotal = [], lineSel = [], lineOth = [];
    keys.forEach(k => {
        const g = grouped[k];
        barData.push(selAgent ? g.revSelected : g.revTotal);
        lineTotal.push(g.wgtTotal > 0 ? +(g.revTotal / g.wgtTotal).toFixed(2) : 0);
        lineSel.push(g.wgtSelected > 0 ? +(g.revSelected / g.wgtSelected).toFixed(2) : 0);
        lineOth.push(g.wgtOthers > 0 ? +(g.revOthers / g.wgtOthers).toFixed(2) : 0);
    });

    const badge = document.getElementById('revChartBadge');
    if (badge) {
        const canGoBack = revChartOffset < Math.max(0, total - winSize);
        const rangeLabel = labels.length ? labels[0] + ' → ' + labels[labels.length-1] : winSize + '-month view';
        badge.innerHTML = `
            <button onclick="revScroll(1)" title="Meses anteriores" style="background:none;border:1px solid #ccc;border-radius:4px;padding:2px 7px;cursor:${canGoBack?'pointer':'default'};opacity:${canGoBack?1:.3};font-size:13px;margin-right:4px;line-height:1;">◀</button>
            <span style="font-size:11px;color:#555;">${rangeLabel}</span>
            <button onclick="revScroll(-1)" title="Meses siguientes" style="background:none;border:1px solid #ccc;border-radius:4px;padding:2px 7px;cursor:${revChartOffset>0?'pointer':'default'};opacity:${revChartOffset>0?1:.3};font-size:13px;margin-left:4px;line-height:1;">▶</button>
        `;
    }

    const canvas = document.getElementById('revMainChart');
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    if (revChart) revChart.destroy();
    const gradient = ctx.createLinearGradient(0,0,0,260);
    gradient.addColorStop(0, '#0066B2'); gradient.addColorStop(1, '#002244');

    const datasets = [];
    if (!selAgent) {
        datasets.push({ type:'line', label:'Avg Rate', data:lineTotal, borderColor:'#C8102E', backgroundColor:'#C8102E', borderWidth:2.5, fill:false, yAxisID:'y1', tension:0.3, pointRadius:5, pointBackgroundColor:'#fff', pointBorderColor:'#C8102E', pointBorderWidth:2 });
    } else {
        datasets.push({ type:'line', label:'Avg — Agent', data:lineSel, borderColor:'#FFC72C', backgroundColor:'#FFC72C', borderWidth:2.5, fill:false, yAxisID:'y1', tension:0.3, pointRadius:5 });
        datasets.push({ type:'line', label:'Avg — Others', data:lineOth, borderColor:'#2ECC71', backgroundColor:'#2ECC71', borderWidth:2, fill:false, yAxisID:'y1', tension:0.3, pointRadius:5 });
    }
    datasets.push({ type:'bar', label:'Revenue', data:barData, backgroundColor:gradient, yAxisID:'y', borderRadius:4 });

    revChart = new Chart(ctx, {
        type: 'bar', data: { labels, datasets },
        options: {
            responsive: true, maintainAspectRatio: false,
            layout: { padding: { top: 20, bottom: 10 } },
            plugins: {
                legend: { position:'top', align:'start', labels:{ color:'#002244', font:{family:"'Barlow',sans-serif",size:11,weight:'600'}, boxWidth:10, padding:14 } },
                tooltip: { callbacks: { label: c => c.dataset.label+': '+(c.dataset.type==='line'?revUsdFmt.format(c.raw):revNoCentsFmt.format(c.raw)) } }
            },
            scales: {
                y:  { display:false, position:'left' },
                y1: { display:false, position:'right', grid:{ drawOnChartArea:false } },
                x:  { grid:{ display:false }, ticks:{ color:'#002244', font:{family:"'Barlow',sans-serif",size:11,weight:'600'} } }
            }
        }
    });
}

function revScroll(dir) {
    const maxOffset = Math.max(0, revChartAllKeys.length - revChartWindowSize);
    revChartOffset = Math.max(0, Math.min(revChartOffset - dir, maxOffset));
    revDrawMainChart();
    revUpdateDashboard(true);
}

// ── Gráficos de barras horizontales (Agent/Goods/Dest) ────────
function revRenderHBar(canvasId, containerId, data, groupCol, revCol, chartInstance, selValue, onClickCb) {
    const grouped = {};
    data.forEach(row => { const k = row[groupCol] || 'UNKNOWN'; grouped[k] = (grouped[k]||0) + (row[revCol]||0); });
    const sorted = Object.entries(grouped).sort((a,b) => b[1]-a[1]);
    const labels = sorted.map(x => x[0]), barData = sorted.map(x => x[1]);
    const bgColors = labels.map(l => { if (!selValue) return '#0066B2'; return l===selValue ? '#C8102E' : '#C7D6E5'; });

    // Mostrar siempre 10 filas a tamaño legible dentro del contenedor visible;
    // si hay más de 10, el canvas crece y el contenedor scrollea verticalmente.
    const VISIBLE_ROWS = 10;
    const container = document.getElementById(containerId);
    const visibleHeight = VISIBLE_ROWS * REV_ROW_PX + 20;
    if (container) container.style.height = visibleHeight + 'px';

    const canvasH = Math.max(visibleHeight, labels.length * REV_ROW_PX + 20);

    const canvas = document.getElementById(canvasId);
    if (!canvas) return chartInstance;
    canvas.height = canvasH;
    canvas.style.height = canvasH + 'px';
    if (chartInstance) chartInstance.destroy();

    return new Chart(canvas.getContext('2d'), {
        type: 'bar',
        data: { labels, datasets: [{ data: barData, backgroundColor: bgColors, borderRadius: 3 }] },
        options: {
            indexAxis: 'y', responsive: true, maintainAspectRatio: false,
            onClick: (evt, elements) => { if (elements.length) onClickCb(labels[elements[0].index]); },
            plugins: {
                legend: { display: false },
                tooltip: { callbacks: { label: c => revUsdFmt.format(c.raw) } }
            },
            scales: {
                x: { display: false },
                y: { ticks: { color:'#1A1F29', font:{family:"'Barlow',sans-serif",size:11,weight:'600'} }, grid:{ display:false } }
            }
        }
    });
}

// ── Tabla de detalle ──────────────────────────────────────────
function revRenderTable(data, revenueCol) {
    const tbody = document.getElementById('revTableBody');
    if (!tbody) return;
    let html = '';
    data.slice(0,50).forEach(row => {
        const f = row.fltDate;
        const dateStr = f ? (String(f.getDate()).padStart(2,'0')+'-'+REV_MONTH_NAMES[f.getMonth()]) : '';
        const metricV = row[revenueCol] || 0;
        html += '<tr>';
        html += `<td>${dateStr}</td>`;
        html += `<td><strong style="font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:var(--delta-navy);">${esc(row.awb)}</strong></td>`;
        html += `<td style="max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc((row.agent||'').substring(0,28))}</td>`;
        html += `<td>${esc(row.dest)}</td>`;
        html += `<td style="max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc((row.description||'').substring(0,20))}</td>`;
        html += `<td style="text-align:right;">${fmtNum(row.chgWgt)}</td>`;
        html += `<td style="text-align:right;">${revUsdFmt.format(row.netRate)}</td>`;
        html += `<td style="text-align:right;">${revUsdFmt.format(metricV)}</td>`;
        html += `<td style="text-align:right;">${revUsdFmt.format(row.totalDc)}</td>`;
        html += `<td style="text-align:right;font-weight:700;">${revUsdFmt.format(metricV + row.totalDc)}</td>`;
        html += '</tr>';
    });
    tbody.innerHTML = html;
}

function revExportExcel() {
    if (!revAllData.length) { alert('No hay datos para exportar.'); return; }
    const revenueCol = revSelectedMetric === 'Freight' ? 'freight' : 'totalAwb';
    const data = revAllData.map(r => ({
        AWB: r.awb, Fecha: r.fltDate ? r.fltDate.toISOString().slice(0,10) : '',
        Agente: r.agent, Dest: r.dest, Descripcion: r.description,
        'Peso Carg.': r.chgWgt, 'Tarifa': r.netRate,
        [revSelectedMetric]: r[revenueCol], 'Due Carrier': r.totalDc,
        'Total': r[revenueCol] + r.totalDc,
    }));
    const ws = XLSX.utils.json_to_sheet(data);
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'Revenues');
    XLSX.writeFile(wb, 'Revenues_' + new Date().toISOString().slice(0,10) + '.xlsx');
}
</script>



<style>
.cat-layout { display: flex; gap: 16px; height: calc(100vh - 160px); }
.cat-nav {
  width: 200px; flex-shrink: 0; background: #fff; border: 1px solid var(--border);
  border-radius: 8px; overflow-y: auto; padding: 8px;
}
.cat-nav-item {
  padding: 10px 12px; border-radius: 5px; cursor: pointer; font-size: 13px;
  font-weight: 600; color: var(--text-mid); margin-bottom: 2px;
}
.cat-nav-item:hover { background: #F4F6F9; }
.cat-nav-item.active { background: #E8F0FE; color: var(--delta-sky); }
.cat-content { flex: 1; min-width: 0; display: flex; flex-direction: column; }
.cat-form-panel {
  background: #fff; border: 1px solid var(--border); border-radius: 8px;
  padding: 16px 18px; margin-bottom: 14px; display: none;
}
.cat-form-panel.open { display: block; }
.cat-form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 10px; }
.cat-form-grid .full { grid-column: 1 / -1; }
.cat-form-field label {
  font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--text-mid);
  letter-spacing: .04em; display: block; margin-bottom: 4px;
}
</style>

<script>
// ══════════════════════════════════════════════════════════════
//  MÓDULO CATALOG MANAGEMENT (cat*)
// ══════════════════════════════════════════════════════════════
let catCurrentTable = 'shipper';
let catCurrentData = [];
let catEditingId = null;

const CAT_CONFIG = {
    shipper: { title: 'Shippers', pk: 'shipper_id', activeCol: 'is_active',
        cols: ['shipper_code','station','legal_name','customer_type','city','country_code','is_active'],
        headers: ['Código','Estación','Razón Social','Tipo','Ciudad','País','Estado'],
        fields: [
            { key:'legal_name', label:'Razón Social *', type:'text', required:true, full:true },
            { key:'commercial_name', label:'Nombre Comercial', type:'text', full:true },
            { key:'shipper_code', label:'Código', type:'text' },
            { key:'station', label:'Estación *', type:'airport_select', required:true },
            { key:'customer_type', label:'Tipo de Cliente', type:'select',
              options:[['CORPORATE','CORPORATE — Empresa / Org. Comercial'],['INDIVIDUAL','INDIVIDUAL — Persona Natural'],['GOVERNMENT','GOVERNMENT — Entidad Gubernamental'],['OTHER','OTHER — Otro']] },
            { key:'tax_id', label:'RUC / Tax ID', type:'text' },
            { key:'address_line1', label:'Dirección', type:'text', full:true },
            { key:'city', label:'Ciudad', type:'text' },
            { key:'country_code', label:'País (2 letras)', type:'text', maxlen:2 },
            { key:'contact_name', label:'Contacto', type:'text' },
            { key:'contact_phone', label:'Teléfono', type:'text' },
            { key:'contact_email', label:'Email', type:'email', full:true },
            { key:'is_active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    consignee: { title: 'Consignees', pk: 'consignee_id', activeCol: 'is_active',
        cols: ['consignee_code','station','legal_name','customer_type','city','country_code','is_active'],
        headers: ['Código','Estación','Razón Social','Tipo','Ciudad','País','Estado'],
        fields: [
            { key:'legal_name', label:'Razón Social *', type:'text', required:true, full:true },
            { key:'commercial_name', label:'Nombre Comercial', type:'text', full:true },
            { key:'consignee_code', label:'Código', type:'text' },
            { key:'station', label:'Estación *', type:'airport_select', required:true },
            { key:'customer_type', label:'Tipo de Cliente', type:'select',
              options:[['CORPORATE','CORPORATE — Empresa / Org. Comercial'],['INDIVIDUAL','INDIVIDUAL — Persona Natural'],['GOVERNMENT','GOVERNMENT — Entidad Gubernamental'],['OTHER','OTHER — Otro']] },
            { key:'tax_id', label:'RUC / Tax ID', type:'text' },
            { key:'address_line1', label:'Dirección', type:'text', full:true },
            { key:'city', label:'Ciudad', type:'text' },
            { key:'country_code', label:'País (2 letras)', type:'text', maxlen:2 },
            { key:'contact_name', label:'Contacto', type:'text' },
            { key:'contact_phone', label:'Teléfono', type:'text' },
            { key:'contact_email', label:'Email', type:'email', full:true },
            { key:'is_active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    freight_forwarder: { title: 'Freight Forwarders', pk: 'ff_id', activeCol: 'is_active',
        cols: ['ff_code','station','legal_name','iata_agent_code','cass_code','city','is_active'],
        headers: ['Código','Estación','Razón Social','Cód. IATA','CASS','Ciudad','Estado'],
        fields: [
            { key:'legal_name', label:'Razón Social *', type:'text', required:true, full:true },
            { key:'commercial_name', label:'Nombre Comercial', type:'text', full:true },
            { key:'ff_code', label:'Código', type:'text' },
            { key:'station', label:'Estación *', type:'airport_select', required:true },
            { key:'iata_agent_code', label:'Cód. IATA Agente', type:'text' },
            { key:'cass_code', label:'Cód. CASS', type:'text' },
            { key:'is_cass_member', label:'Miembro CASS', type:'select', options:[['1','Sí'],['0','No']] },
            { key:'address_line1', label:'Dirección', type:'text', full:true },
            { key:'city', label:'Ciudad', type:'text' },
            { key:'country_code', label:'País (2 letras)', type:'text', maxlen:2 },
            { key:'contact_email', label:'Email', type:'email', full:true },
            { key:'is_active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    product_code: { title: 'Product Codes', pk: 'product_id', activeCol: 'active',
        cols: ['product_code','product_name','iata_category','active'],
        headers: ['Código','Nombre','Categoría IATA','Estado'],
        fields: [
            { key:'product_code', label:'Código *', type:'text', required:true },
            { key:'product_name', label:'Nombre *', type:'text', required:true, full:true },
            { key:'iata_category', label:'Categoría IATA', type:'text' },
            { key:'requires_special_handling', label:'Special Handling', type:'select', options:[['1','Sí'],['0','No']] },
            { key:'dg_required', label:'Mercancía Peligrosa', type:'select', options:[['1','Sí'],['0','No']] },
            { key:'temperature_control', label:'Control Temperatura', type:'select', options:[['1','Sí'],['0','No']] },
            { key:'active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    goods_catalog: { title: 'Goods Catalog', pk: 'goods_id', activeCol: 'active',
        cols: ['goods_code','goods_description','hs_code','active'],
        headers: ['Código','Descripción','HS Code','Estado'],
        fields: [
            { key:'goods_code', label:'Código', type:'text' },
            { key:'goods_description', label:'Descripción *', type:'text', required:true, full:true },
            { key:'hs_code', label:'HS Code', type:'text' },
            { key:'dg_flag', label:'Mercancía Peligrosa', type:'select', options:[['1','Sí'],['0','No']] },
            { key:'perishable_flag', label:'Perecible', type:'select', options:[['1','Sí'],['0','No']] },
            { key:'active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    airport: { title: 'Airports', pk: 'airport_id', activeCol: 'active',
        cols: ['iata_code','icao_code','airport_name','city_name','country_code','active'],
        headers: ['IATA','ICAO','Nombre','Ciudad','País','Estado'],
        fields: [
            { key:'iata_code', label:'Código IATA (3) *', type:'text', required:true, maxlen:3, upper:true },
            { key:'icao_code', label:'Código ICAO (4)', type:'text', maxlen:4, upper:true },
            { key:'airport_name', label:'Nombre *', type:'text', required:true, full:true },
            { key:'city_name', label:'Ciudad', type:'text' },
            { key:'country_code', label:'País (2)', type:'text', maxlen:2, upper:true },
            { key:'timezone_name', label:'Zona Horaria', type:'text' },
            { key:'active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    other_charges_catalog: { title: 'Other Charges', pk: 'id', activeCol: 'active',
        cols: ['charge_code','charge_name','applies_to','active'],
        headers: ['Código','Nombre','Aplica a','Estado'],
        fields: [
            { key:'charge_code', label:'Código IATA *', type:'text', required:true, maxlen:5, upper:true },
            { key:'charge_name', label:'Nombre *', type:'text', required:true, full:true },
            { key:'description', label:'Descripción', type:'text', full:true },
            { key:'applies_to', label:'Aplica a', type:'select', options:[['carrier','Carrier'],['agent','Agent'],['both','Ambos']] },
            { key:'active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    flights: { title: 'Flights', pk: 'id', activeCol: 'active',
        cols: ['flt_number','station','flt_orig','flt_dest','aircraft','capacity_kg','active'],
        headers: ['Vuelo','Estación','Orig','Dest','Aeronave','Cap. KG','Estado'],
        fields: [
            { key:'flt_number',  label:'Número de Vuelo *', type:'flt_number', required:true,
              hint:'Formato: Código IATA aerolínea (2 letras) + número. Ej: DL150, LA2478' },
            { key:'station',     label:'Estación *', type:'airport_select', required:true,
              hint:'Aeropuerto base de operación' },
            { key:'flt_orig',    label:'Origen (IATA) *', type:'airport_select', required:true },
            { key:'flt_dest',    label:'Destino (IATA) *', type:'airport_select', required:true },
            { key:'aircraft',    label:'Tipo Aeronave', type:'aircraft_select',
              hint:'Seleccionar desde catálogo de aeronaves' },
            { key:'capacity_kg', label:'Capacidad KG *', type:'number', required:true,
              hint:'Payload máximo aprobado de carga en KG' },
            { key:'active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    notification_config: { title: 'Notificaciones Email', pk: 'id', activeCol: 'active',
        cols: ['station','report_type','recipient_name','recipient_email','active'],
        headers: ['Estación','Tipo Reporte','Destinatario','Email','Estado'],
        fields: [
            { key:'station',         label:'Estación *', type:'airport_select', required:true },
            { key:'report_type',     label:'Tipo de Reporte *', type:'select', required:true,
              options:[
                ['DISTRO_TALMA',      'Hoja Distribución — TALMA'],
                ['DISTRO_FRIO_AEREO', 'Hoja Distribución — Frío Aéreo'],
                ['OUTBOUND',          'Reporte Outbound Cargo'],
                ['FLIGHT_SUMMARY',    'Flight Summary Report'],
                ['MANIFEST',          'Air Cargo Manifest'],
              ]},
            { key:'recipient_name',  label:'Nombre Destinatario *', type:'text', required:true },
            { key:'recipient_email', label:'Email *', type:'email', required:true, full:true },
            { key:'active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    uld_types: { title: 'ULD Types', pk: 'id', activeCol: 'active',
        cols: ['uld_code','description','category','max_weight_kg','active'],
        headers: ['Código','Descripción','Categoría','Peso Máx. KG','Estado'],
        fields: [
            { key:'uld_code',      label:'Código IATA *', type:'text', required:true, maxlen:10, upper:true },
            { key:'description',   label:'Descripción *', type:'text', required:true, full:true },
            { key:'category',      label:'Categoría', type:'select',
              options:[['PALLET','PALLET'],['CONTAINER','CONTAINER'],['BULK','BULK'],['OTHER','OTHER']] },
            { key:'max_weight_kg', label:'Peso Máximo (KG)', type:'number' },
            { key:'sort_order',    label:'Orden', type:'number' },
            { key:'active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
    aircraft_types: { title: 'Aircraft Types', pk: 'id', activeCol: 'active',
        cols: ['icao_code','iata_code','manufacturer','full_name','active'],
        headers: ['ICAO','IATA','Fabricante','Aeronave','Estado'],
        fields: [
            { key:'icao_code',    label:'Código ICAO *', type:'text', required:true, maxlen:10, upper:true },
            { key:'iata_code',    label:'Código IATA *', type:'text', required:true, maxlen:10, upper:true },
            { key:'manufacturer', label:'Fabricante *', type:'text', required:true },
            { key:'model',        label:'Modelo *', type:'text', required:true },
            { key:'full_name',    label:'Nombre Completo *', type:'text', required:true, full:true },
            { key:'active', label:'Estado', type:'select', options:[['1','Activo'],['0','Inactivo']] },
        ]
    },
};

function catalogInit() {
    const canEdit = hasPermission('Catalog','edit') || hasPermission('Catalog','create');

    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-navy)"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/></svg>
            <div class="module-title">Catalog Management</div>
        </div>

        <div class="cat-layout">
            <div class="cat-nav" id="catNav"></div>
            <div class="cat-content">
                <div class="toolbar">
                    <span id="catPanelTitle" style="font-family:'Barlow Condensed',sans-serif;font-weight:700;font-size:16px;color:var(--delta-navy);"></span>
                    <label style="display:flex;align-items:center;gap:6px;font-size:12px;margin-left:16px;"><input type="checkbox" id="catShowInactive" onchange="catLoadTable()"> Mostrar inactivos</label>
                    <input type="text" id="catSearch" placeholder="Buscar..." oninput="catFilterTable()" style="margin-left:auto;width:200px;height:34px;border:1px solid var(--border);border-radius:4px;padding:0 10px;">
                    ${canEdit ? '<button class="btn-primary" onclick="catOpenForm()">+ Nuevo</button>' : ''}
                </div>
                <div class="cat-form-panel" id="catFormPanel">
                    <div style="display:flex;align-items:center;gap:10px;">
                        <span id="catFormTitle" style="font-family:'Barlow Condensed',sans-serif;font-weight:700;font-size:15px;color:var(--delta-navy);"></span>
                        <button class="btn-secondary" style="margin-left:auto;" onclick="catCloseForm()">Cancelar</button>
                        <button class="btn-primary" onclick="catSaveRecord()">Guardar</button>
                    </div>
                    <div class="cat-form-grid" id="catFormGrid"></div>
                    <div id="catFormMsg" style="font-size:12px;font-weight:600;margin-top:8px;"></div>
                </div>
                <div class="card" style="overflow-x:auto;flex:1;">
                    <div id="catTableWrap"></div>
                </div>
            </div>
        </div>
    `;

    const nav = document.getElementById('catNav');
    nav.innerHTML = Object.keys(CAT_CONFIG).map(t =>
        `<div class="cat-nav-item ${t===catCurrentTable?'active':''}" id="catnav-${t}" onclick="catSelectTable('${t}')">${CAT_CONFIG[t].title}</div>`
    ).join('');

    catSelectTable(catCurrentTable);
}

function catSelectTable(table) {
    catCurrentTable = table;
    document.querySelectorAll('.cat-nav-item').forEach(el => el.classList.remove('active'));
    document.getElementById('catnav-' + table).classList.add('active');
    document.getElementById('catPanelTitle').textContent = CAT_CONFIG[table].title;
    document.getElementById('catSearch').value = '';
    catCloseForm();
    catLoadTable();
}

async function catLoadTable() {
    const showInactive = document.getElementById('catShowInactive').checked ? '1' : '0';
    const wrap = document.getElementById('catTableWrap');
    wrap.innerHTML = '<div class="empty-state">Cargando...</div>';
    try {
        const res = await apiFetch(`/catalog/catalog_api.php?table=${catCurrentTable}&show_inactive=${showInactive}`);
        if (!res.success) { wrap.innerHTML = '<div class="empty-state">' + esc(res.message) + '</div>'; return; }
        catCurrentData = res.data || [];
        catRenderTable(catCurrentData);
    } catch(e) {
        wrap.innerHTML = '<div class="empty-state">Error de conexión.</div>';
    }
}

function catFilterTable() {
    const q = document.getElementById('catSearch').value.toLowerCase();
    if (!q) { catRenderTable(catCurrentData); return; }
    const filtered = catCurrentData.filter(row => JSON.stringify(row).toLowerCase().includes(q));
    catRenderTable(filtered);
}

function catRenderTable(data) {
    const cfg = CAT_CONFIG[catCurrentTable];
    const wrap = document.getElementById('catTableWrap');
    const canEdit = hasPermission('Catalog','edit');
    const canDelete = hasPermission('Catalog','delete');

    if (!data.length) { wrap.innerHTML = '<div class="empty-state">Sin registros.</div>'; return; }

    let html = '<table class="data-table"><thead><tr>';
    cfg.headers.forEach(h => html += `<th>${h}</th>`);
    html += '<th>Acciones</th></tr></thead><tbody>';

    data.forEach(row => {
        html += '<tr>';
        cfg.cols.forEach(c => {
            let val = row[c];
            if (c === cfg.activeCol) {
                val = (val == 1) ? '<span class="status-badge st-CONFIRMED">Activo</span>' : '<span class="status-badge st-CANCELLED">Inactivo</span>';
            } else {
                val = esc(val ?? '—');
            }
            html += `<td>${val}</td>`;
        });
        let actions = '';
        if (canEdit) actions += `<button class="action-btn-sm" onclick='catEditRecord(${row[cfg.pk]})'>✎ Editar</button>`;
        if (canDelete && row[cfg.activeCol] == 1) actions += `<button class="action-btn-sm danger" onclick="catDeactivate(${row[cfg.pk]})">Desactivar</button>`;
        // Physical delete for notification_config (no FK references)
        if (canDelete && catCurrentTable === 'notification_config') {
            actions += `<button class="action-btn-sm danger" onclick="catPhysicalDelete(${row[cfg.pk]})" title="Eliminar permanentemente" style="background:#7F1D1D;">&#128465; Eliminar</button>`;
        }
        html += `<td><div style="display:flex;gap:5px;">${actions}</div></td>`;
        html += '</tr>';
    });
    html += '</tbody></table>';
    wrap.innerHTML = html;
}

function catOpenForm() {
    catEditingId = null;
    document.getElementById('catFormTitle').textContent = 'Nuevo registro';
    catRenderForm({});
    document.getElementById('catFormPanel').classList.add('open');
}
function catEditRecord(id) {
    const cfg = CAT_CONFIG[catCurrentTable];
    const row = catCurrentData.find(r => r[cfg.pk] == id);
    if (!row) return;
    catEditingId = id;
    document.getElementById('catFormTitle').textContent = 'Editar registro';
    catRenderForm(row);
    document.getElementById('catFormPanel').classList.add('open');
}
function catCloseForm() {
    document.getElementById('catFormPanel')?.classList.remove('open');
    document.getElementById('catFormMsg').textContent = '';
    catEditingId = null;
}

// ── Catalog caches ───────────────────────────────────────────────
let _catAircraftCache = null;
let _catAirportCache  = null;

async function _catLoadAirports() {
    if (_catAirportCache) return _catAirportCache;
    try {
        const res = await apiFetch('/catalog/catalog_api.php?table=airport&show_inactive=0');
        _catAirportCache = res.success ? res.data : [];
    } catch(e) { _catAirportCache = []; }
    return _catAirportCache;
}

async function _catLoadAircraft() {
    if (_catAircraftCache) return _catAircraftCache;
    try {
        const res = await apiFetch('/catalog/catalog_api.php?table=aircraft_types&show_inactive=0');
        _catAircraftCache = res.success ? res.data : [];
    } catch(e) { _catAircraftCache = []; }
    return _catAircraftCache;
}

async function catRenderForm(row) {
    const cfg = CAT_CONFIG[catCurrentTable];
    const grid = document.getElementById('catFormGrid');

    const needsAirport  = cfg.fields.some(function(f){ return f.type === 'airport_select'; });
    const needsAircraft = cfg.fields.some(function(f){ return f.type === 'aircraft_select'; });
    if (needsAirport)  await _catLoadAirports();
    if (needsAircraft) await _catLoadAircraft();

    grid.innerHTML = cfg.fields.map(function(f) {
        const val = row[f.key] !== undefined ? row[f.key] : '';
        let inputHtml = '';

        if (f.type === 'select') {
            inputHtml = '<select id="catf_' + f.key + '" class="awb-input" style="width:100%;">'
                + f.options.map(function(o){
                    return '<option value="' + o[0] + '"' + (String(val)===o[0]?' selected':'') + '>' + o[1] + '</option>';
                }).join('') + '</select>';

        } else if (f.type === 'airport_select') {
            const airports = _catAirportCache || [];
            inputHtml = '<select id="catf_' + f.key + '" class="awb-input" style="width:100%;">'
                + '<option value="">— Seleccionar aeropuerto —</option>'
                + airports.map(function(a) {
                    const label = a.iata_code + ' — ' + (a.airport_name||'') + (a.city_name ? ', ' + a.city_name : '');
                    const selected = (val === a.iata_code) ? ' selected' : '';
                    return '<option value="' + esc(a.iata_code) + '"' + selected + '>' + esc(label) + '</option>';
                }).join('')
                + '</select>';

        } else if (f.type === 'aircraft_select') {
            const aircraft = _catAircraftCache || [];
            inputHtml = '<select id="catf_' + f.key + '" class="awb-input" style="width:100%;">'
                + '<option value="">— Seleccionar aeronave —</option>'
                + aircraft.map(function(a) {
                    const label = a.icao_code + ' / ' + a.iata_code + ' — ' + a.full_name;
                    const selected = (val === a.icao_code) ? ' selected' : '';
                    return '<option value="' + esc(a.icao_code) + '"' + selected + '>' + esc(label) + '</option>';
                }).join('')
                + '</select>';

        } else if (f.type === 'flt_number') {
            inputHtml = '<input id="catf_' + f.key + '" class="awb-input" type="text"'
                + ' value="' + esc(val) + '" maxlength="8" autocomplete="off"'
                + ' style="width:100%;text-transform:uppercase;letter-spacing:.05em;font-weight:700;font-size:15px;"'
                + ' oninput="this.value=this.value.toUpperCase();catValidateFltNumber(this)"'
                + ' onblur="catValidateFltNumber(this)">'
                + '<div id="catf_flt_number_err" style="font-size:11px;color:#C8102E;margin-top:3px;display:none;"></div>';

        } else if (f.type === 'number') {
            inputHtml = '<input id="catf_' + f.key + '" class="awb-input" type="number"'
                + ' value="' + esc(val) + '" step="0.01" min="0" style="width:100%;">';

        } else {
            inputHtml = '<input id="catf_' + f.key + '" class="awb-input"'
                + ' type="' + (f.type==='email'?'email':'text') + '"'
                + ' value="' + esc(val) + '"'
                + (f.maxlen ? ' maxlength="' + f.maxlen + '"' : '')
                + (f.upper  ? ' oninput="this.value=this.value.toUpperCase()"' : '')
                + (f.placeholder ? ' placeholder="' + f.placeholder + '"' : '')
                + ' style="width:100%;">';
        }

        const hint = f.hint
            ? '<div style="font-size:10px;color:var(--text-mid);margin-top:3px;">' + f.hint + '</div>'
            : '';
        return '<div class="cat-form-field ' + (f.full?'full':'') + '">'
            + '<label>' + f.label + '</label>'
            + inputHtml + hint + '</div>';
    }).join('');
}

function catValidateFltNumber(input) {
    const val   = input.value.trim().toUpperCase();
    const errEl = document.getElementById('catf_flt_number_err');
    if (!errEl) return true;
    if (!val) { errEl.style.display = 'none'; input.style.borderColor = ''; return false; }
    const valid = /^[A-Z]{2}\d{1,5}$/.test(val);
    if (!valid) {
        errEl.textContent = 'Formato incorrecto. Use: 2 letras (aerolínea IATA) + número. Ej: DL150, LA2478, AA918';
        errEl.style.display = 'block';
        input.style.borderColor = '#C8102E';
        return false;
    }
    errEl.style.display = 'none';
    input.style.borderColor = '#2ECC71';
    return true;
}

// Legacy stubs for compatibility
let _aircraftCache = null;
async function catAircraftSearch(input) {}
function catAircraftSelect(icao, fullName) {
    const inp = document.getElementById('catf_aircraft');
    if (inp) inp.value = icao;
}

async function catSaveRecord() {
    const cfg = CAT_CONFIG[catCurrentTable];
    const msg = document.getElementById('catFormMsg');
    const body = {};
    let missingRequired = false;

    // Validate flight number if present
    const fltInput = document.getElementById('catf_flt_number');
    if (fltInput && !catValidateFltNumber(fltInput)) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'El número de vuelo tiene un formato incorrecto.';
        return;
    }

    cfg.fields.forEach(f => {
        const el = document.getElementById('catf_' + f.key);
        if (!el) return;
        const val = el.value.trim();
        if (f.required && !val) missingRequired = true;
        body[f.key] = val;
    });

    if (missingRequired) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Completa los campos obligatorios (*).';
        return;
    }

    msg.style.color = 'var(--text-mid)'; msg.textContent = 'Guardando...';

    try {
        const url = catEditingId
            ? `/catalog/catalog_api.php?table=${catCurrentTable}&id=${catEditingId}`
            : `/catalog/catalog_api.php?table=${catCurrentTable}`;
        const method = catEditingId ? 'PUT' : 'POST';
        const res = await apiFetch(url, { method, body: JSON.stringify(body) });
        if (res.success) {
            msg.style.color = '#2ECC71'; msg.textContent = res.message;
            catLoadTable();
            setTimeout(catCloseForm, 700);
        } else {
            msg.style.color = 'var(--delta-red)'; msg.textContent = res.message || 'Error al guardar.';
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)'; msg.textContent = 'Error de conexión.';
    }
}

async function catDeactivate(id) {
    if (!confirm('¿Desactivar este registro?')) return;
    try {
        const res = await apiFetch(`/catalog/catalog_api.php?table=${catCurrentTable}&id=${id}`, { method: 'DELETE' });
        if (res.success) catLoadTable();
        else alert(res.message || 'Error al desactivar.');
    } catch(e) { alert('Error de conexión.'); }
}

async function catPhysicalDelete(id) {
    if (!confirm('¿Eliminar permanentemente este registro?\nEsta acción no se puede deshacer.')) return;
    try {
        const res = await apiFetch(`/catalog/catalog_api.php?table=${catCurrentTable}&id=${id}&hard=1`, { method: 'DELETE' });
        if (res.success) catLoadTable();
        else alert(res.message || 'Error al eliminar.');
    } catch(e) { alert('Error de conexión.'); }
}
</script>


<style>
.admin-layout { display: flex; flex-direction: column; gap: 16px; }
.admin-section-title {
  font-family: 'Barlow Condensed', sans-serif; font-size: 16px; font-weight: 700;
  color: var(--delta-navy); text-transform: uppercase; letter-spacing: .04em;
  margin-bottom: 10px; display: flex; align-items: center; gap: 8px;
}
.perm-matrix { width: 100%; border-collapse: collapse; font-size: 13px; }
.perm-matrix thead th {
  background: var(--delta-navy); color: #fff; padding: 9px 12px; text-align: left;
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em;
}
.perm-matrix thead th.center { text-align: center; width: 80px; }
.perm-matrix tbody td { padding: 7px 12px; border-bottom: 1px solid #EEF1F5; }
.perm-matrix tbody td.center { text-align: center; }
.perm-matrix tbody td.na { text-align: center; color: #ddd; }
.role-editor {
  background: #F8FAFC; border: 1px solid var(--border); border-radius: 6px;
  padding: 16px; margin-top: 14px; display: none;
}
.role-editor.open { display: block; }
.adm-tabs { display: flex; gap: 8px; margin-bottom: 16px; border-bottom: 1px solid var(--border); }
.adm-tab {
  background: none; border: none; border-bottom: 3px solid transparent;
  padding: 10px 4px 9px; margin-right: 18px; cursor: pointer;
  font-family: 'Barlow Condensed', sans-serif; font-size: 15px; font-weight: 700;
  color: var(--text-mid); text-transform: uppercase; letter-spacing: .04em;
  transition: color .12s, border-color .12s;
}
.adm-tab:hover { color: var(--delta-navy); }
.adm-tab-active { color: var(--delta-navy); border-bottom-color: var(--delta-red); }
.adm-tab-panel { display: none; }
.adm-tab-panel.active { display: block; }
</style>

<script>
// ══════════════════════════════════════════════════════════════
//  MÓDULO USER MANAGEMENT (adm*)
// ══════════════════════════════════════════════════════════════
let admUsersList = [];
let admRolesList = [];
let admFormRoleId = null;
let admEditingUserId = null;

// Módulos completos disponibles para la matriz de permisos
function admAllModules() {
    const mods = [];
    MENU_TREE.forEach(item => {
        if (item.children) item.children.forEach(c => mods.push({ id: c.id, label: c.label, actions: c.actions || ['view'] }));
        else mods.push({ id: item.id, label: item.label, actions: item.actions || ['view'] });
    });
    return mods;
}

function adminInit() {
    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-navy)"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/></svg>
            <div class="module-title">User Management</div>
        </div>

        <div class="adm-tabs">
            <button class="adm-tab adm-tab-active" id="admTabBtnUsers" onclick="admSwitchTab('users')">Users</button>
            <button class="adm-tab" id="admTabBtnRoles" onclick="admSwitchTab('roles')">Roles</button>
        </div>

        <div class="admin-layout">
            <!-- ── Users ── -->
            <div class="adm-tab-panel active" id="admPanelUsers">
                <div class="card" style="padding:16px 18px;">
                    <div class="admin-section-title" id="admUserFormTitle">New User</div>
                    <div style="display:grid;grid-template-columns:1fr 1fr 1fr 1.3fr;gap:10px;margin-bottom:10px;">
                        <div class="cat-form-field"><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Full Name</label><input id="admFullName" class="awb-input" type="text" style="width:100%;"></div>
                        <div class="cat-form-field"><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Job Title</label><input id="admJobTitle" class="awb-input" type="text" style="width:100%;"></div>
                        <div class="cat-form-field"><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Username</label><input id="admUsername" class="awb-input" type="text" placeholder="unique login" style="width:100%;"></div>
                        <div class="cat-form-field"><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Email *</label><input id="admEmail" class="awb-input" type="email" style="width:100%;"></div>
                    </div>
                    <div style="display:grid;grid-template-columns:1fr 1fr 1fr 1fr auto;gap:10px;align-items:end;margin-bottom:14px;">
                        <div class="cat-form-field"><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Password</label><input id="admPass" class="awb-input" type="text" placeholder="(leave empty to keep current)" style="width:100%;"></div>
                        <div class="cat-form-field"><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Role *</label><select id="admRoleId" class="awb-input" onchange="admOnRoleChange()" style="width:100%;"></select></div>
                        <div class="cat-form-field"><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Stations</label><input id="admStations" class="awb-input" type="text" placeholder="LIM,UIO or ALL" style="width:100%;"></div>
                        <div class="cat-form-field"><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Status</label><select id="admStatus" class="awb-input" style="width:100%;"><option value="Activo">Active</option><option value="Inactivo">Inactive</option></select></div>
                        <div style="display:flex;gap:8px;">
                            <button class="btn-primary" id="admBtnSave" onclick="admSaveUser()">Save</button>
                            <button class="btn-secondary" id="admBtnCancel" onclick="admClearUserForm()" style="display:none;">Cancel</button>
                        </div>
                    </div>
                    <div id="admRolePermsPreview" style="display:none;margin-bottom:14px;"></div>
                    <div id="admMsg" style="font-size:12px;font-weight:600;margin-bottom:10px;"></div>
                    <div id="admUsersTableWrap"></div>
                </div>
            </div>

            <!-- ── Roles ── -->
            <div class="adm-tab-panel" id="admPanelRoles">
                <div class="card" style="padding:16px 18px;">
                    <div class="admin-section-title">
                        Roles
                        <button class="btn-secondary" style="margin-left:auto;height:30px;" onclick="admRoleNew()">+ New Role</button>
                    </div>
                    <div id="admRolesTableWrap"></div>

                    <div class="role-editor" id="admRoleEditor">
                        <div style="font-family:'Barlow Condensed',sans-serif;font-weight:700;font-size:14px;color:var(--delta-navy);margin-bottom:12px;" id="admRoleEditorTitle"></div>
                        <input type="hidden" id="admRoleEditId">
                        <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:14px;">
                            <div><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Role Name *</label><input id="admRoleName" class="awb-input" style="width:100%;margin-top:4px;"></div>
                            <div><label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Description</label><input id="admRoleDesc" class="awb-input" style="width:100%;margin-top:4px;"></div>
                        </div>

                        <label style="display:flex;align-items:center;gap:8px;font-size:12px;font-weight:700;color:var(--delta-navy);cursor:pointer;background:#FFF8E1;border:1px solid #FFE082;border-radius:6px;padding:8px 12px;margin-bottom:12px;">
                            <input type="checkbox" id="admRoleAll" onchange="admRoleToggleAllAccess(this)">
                            FULL SYSTEM ACCESS (overrides the permission matrix)
                        </label>

                        <div style="border:1px solid var(--border);border-radius:6px;overflow:hidden;">
                            <table class="perm-matrix">
                                <thead><tr><th>Module</th><th class="center">View</th><th class="center">Create</th><th class="center">Edit</th><th class="center">Delete</th><th class="center">All</th></tr></thead>
                                <tbody id="admRolePermMatrix"></tbody>
                            </table>
                        </div>

                        <div style="display:flex;gap:8px;margin-top:14px;">
                            <button class="btn-primary" onclick="admRoleSave()">Save Role</button>
                            <button class="btn-secondary" onclick="admRoleCancel()">Cancel</button>
                            <div id="admRoleMsg" style="font-size:12px;font-weight:600;align-self:center;margin-left:8px;"></div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    `;

    admLoadAll();
}

function admSwitchTab(tab) {
    document.getElementById('admTabBtnUsers').classList.toggle('adm-tab-active', tab === 'users');
    document.getElementById('admTabBtnRoles').classList.toggle('adm-tab-active', tab === 'roles');
    document.getElementById('admPanelUsers').classList.toggle('active', tab === 'users');
    document.getElementById('admPanelRoles').classList.toggle('active', tab === 'roles');
}

async function admLoadAll() {
    try {
        const [resU, resR] = await Promise.all([
            apiFetch('/auth/get_users.php'),
            apiFetch('/auth/roles_api.php'),
        ]);
        admUsersList = resU.success ? resU.data : [];
        admRolesList = resR.success ? resR.data : [];
        admPopulateRoleSelect();
        admRenderUsersTable();
        admRenderRolesTable();
    } catch(e) {
        document.getElementById('admUsersTableWrap').innerHTML = '<div class="empty-state">Error de conexión.</div>';
    }
}

function admPopulateRoleSelect() {
    const sel = document.getElementById('admRoleId');
    const cur = sel.value;
    sel.innerHTML = '<option value="">— Seleccionar —</option>' +
        admRolesList.map(r => `<option value="${r.id}">${esc(r.role_name)}</option>`).join('');
    if (cur) sel.value = cur;
}

function admRenderUsersTable() {
    const wrap = document.getElementById('admUsersTableWrap');
    if (!admUsersList.length) { wrap.innerHTML = '<div class="empty-state">No users.</div>'; return; }

    let html = '<table class="data-table"><thead><tr><th>Full Name</th><th>Username</th><th>Email</th><th>Job Title</th><th>Role</th><th>Stations</th><th>Status</th><th>Actions</th></tr></thead><tbody>';
    admUsersList.forEach(u => {
        const badgeClass = u.status === 'Activo' ? 'st-CONFIRMED' : 'st-CANCELLED';
        const statusLabel = u.status === 'Activo' ? 'Active' : 'Inactive';
        html += '<tr>';
        html += `<td><b>${esc(u.full_name || '—')}</b></td>`;
        html += `<td>${esc(u.username || '—')}</td>`;
        html += `<td>${esc(u.email)}</td>`;
        html += `<td>${esc(u.job_title || '—')}</td>`;
        html += `<td>${esc(u.role_name || '—')}</td>`;
        html += `<td>${esc(u.stations || '—')}</td>`;
        html += `<td><span class="status-badge ${badgeClass}">${statusLabel}</span></td>`;
        html += `<td><button class="action-btn-sm" onclick="admEditUser(${u.id})">✎ Edit</button></td>`;
        html += '</tr>';
    });
    html += '</tbody></table>';
    wrap.innerHTML = html;
}

function admEditUser(id) {
    const u = admUsersList.find(x => x.id === id);
    if (!u) return;
    admEditingUserId = id;
    document.getElementById('admFullName').value = u.full_name || '';
    document.getElementById('admJobTitle').value = u.job_title || '';
    document.getElementById('admUsername').value = u.username || '';
    document.getElementById('admEmail').value = u.email;
    document.getElementById('admPass').value = '';
    document.getElementById('admStations').value = u.stations || '';
    document.getElementById('admStatus').value = u.status;
    document.getElementById('admRoleId').value = u.role_id || '';
    document.getElementById('admUserFormTitle').textContent = 'Edit User: ' + (u.full_name || u.email);
    document.getElementById('admBtnCancel').style.display = 'inline-flex';
    admOnRoleChange();
}

function admOnRoleChange() {
    admFormRoleId = document.getElementById('admRoleId').value ? parseInt(document.getElementById('admRoleId').value) : null;
    const wrap = document.getElementById('admRolePermsPreview');
    if (!admFormRoleId) { wrap.style.display = 'none'; return; }
    const role = admRolesList.find(r => r.id == admFormRoleId);
    if (!role) { wrap.style.display = 'none'; return; }

    const mp = role.menu_permissions || {};
    let labels = [];
    if (mp.ALL && mp.ALL.view) {
        labels = ['Acceso total al sistema'];
    } else {
        const mods = admAllModules();
        labels = Object.keys(mp).map(modId => {
            const mod = mods.find(m => m.id === modId);
            const acts = Object.keys(mp[modId]).filter(a => mp[modId][a]);
            const actLabels = { view:'Ver', create:'Crear', edit:'Editar', delete:'Eliminar' };
            return (mod ? mod.label : modId) + ' (' + acts.map(a => actLabels[a]||a).join(', ') + ')';
        });
    }
    wrap.innerHTML = labels.map(l => `<span style="display:inline-block;background:#E8F0FE;color:#1967D2;border-radius:10px;padding:2px 9px;font-size:11px;font-weight:700;margin:2px;">${esc(l)}</span>`).join('');
    wrap.style.display = 'block';
}

async function admSaveUser() {
    const fullName = document.getElementById('admFullName').value.trim();
    const jobTitle = document.getElementById('admJobTitle').value.trim();
    const username = document.getElementById('admUsername').value.trim();
    const email = document.getElementById('admEmail').value.trim();
    const pass = document.getElementById('admPass').value;
    const roleId = document.getElementById('admRoleId').value;
    const stations = document.getElementById('admStations').value.trim();
    const status = document.getElementById('admStatus').value;
    const msg = document.getElementById('admMsg');

    if (!email || (!admEditingUserId && !pass) || !roleId) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Email, Rol son obligatorios; Contraseña es obligatoria al crear.';
        return;
    }
    if (username && /\s/.test(username)) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'El Username no debe contener espacios.';
        return;
    }

    msg.style.color = 'var(--text-mid)'; msg.textContent = 'Guardando...';

    try {
        const body = { email, role_id: parseInt(roleId), stations, status, full_name: fullName, job_title: jobTitle, username };
        if (admEditingUserId) body.id = admEditingUserId;
        if (pass) body.password = pass;

        const res = await apiFetch('/auth/save_user.php', { method: 'POST', body: JSON.stringify(body) });
        if (res.success) {
            msg.style.color = '#2ECC71'; msg.textContent = res.message;
            admClearUserForm();
            admLoadAll();
        } else {
            msg.style.color = 'var(--delta-red)'; msg.textContent = res.message;
        }
    } catch(e) {
        msg.style.color = 'var(--delta-red)'; msg.textContent = 'Error de conexión.';
    }
}

function admClearUserForm() {
    admEditingUserId = null;
    document.getElementById('admFullName').value = '';
    document.getElementById('admJobTitle').value = '';
    document.getElementById('admUsername').value = '';
    document.getElementById('admEmail').value = '';
    document.getElementById('admPass').value = '';
    document.getElementById('admStations').value = '';
    document.getElementById('admRoleId').value = '';
    document.getElementById('admStatus').value = 'Activo';
    document.getElementById('admRolePermsPreview').style.display = 'none';
    document.getElementById('admUserFormTitle').textContent = 'New User';
    document.getElementById('admBtnCancel').style.display = 'none';
}

// ── Roles ──────────────────────────────────────────────────────
function admRenderRolesTable() {
    const wrap = document.getElementById('admRolesTableWrap');
    let html = '<table class="data-table"><thead><tr><th>Rol</th><th>Descripción</th><th>Permisos</th><th>Sistema</th><th>Acciones</th></tr></thead><tbody>';
    admRolesList.forEach(r => {
        const mp = r.menu_permissions || {};
        const mpText = (mp.ALL && mp.ALL.view) ? 'ACCESO TOTAL' : (Object.keys(mp).length + ' módulo(s)');
        html += '<tr>';
        html += `<td><b>${esc(r.role_name)}</b></td>`;
        html += `<td>${esc(r.description || '')}</td>`;
        html += `<td style="font-size:11px;">${mpText}</td>`;
        html += `<td>${r.is_system ? '<span style="color:var(--delta-sky);font-size:11px;">Sistema</span>' : ''}</td>`;
        html += `<td>${r.is_system ? '' : `<button class="action-btn-sm" onclick="admRoleEdit(${r.id})">✎ Editar</button>`}</td>`;
        html += '</tr>';
    });
    html += '</tbody></table>';
    wrap.innerHTML = html;
}

function admRoleNew() {
    document.getElementById('admRoleEditId').value = '';
    document.getElementById('admRoleName').value = '';
    document.getElementById('admRoleDesc').value = '';
    document.getElementById('admRoleEditorTitle').textContent = 'Nuevo Rol';
    admRoleRenderMatrix(null);
    document.getElementById('admRoleEditor').classList.add('open');
}
function admRoleEdit(id) {
    const role = admRolesList.find(r => r.id == id);
    if (!role) return;
    document.getElementById('admRoleEditId').value = role.id;
    document.getElementById('admRoleName').value = role.role_name;
    document.getElementById('admRoleDesc').value = role.description || '';
    document.getElementById('admRoleEditorTitle').textContent = 'Editar Rol: ' + role.role_name;
    admRoleRenderMatrix(role.menu_permissions);
    document.getElementById('admRoleEditor').classList.add('open');
}
function admRoleCancel() { document.getElementById('admRoleEditor').classList.remove('open'); }

function admRoleRenderMatrix(currentPerms) {
    const tbody = document.getElementById('admRolePermMatrix');
    const allChk = document.getElementById('admRoleAll');
    const isAllAccess = currentPerms && currentPerms.ALL && currentPerms.ALL.view;
    allChk.checked = !!isAllAccess;

    const mods = admAllModules();
    let html = '';
    mods.forEach(m => {
        const p = (currentPerms && currentPerms[m.id]) || {};
        const cell = (action) => {
            if (!m.actions.includes(action)) return '<td class="na">—</td>';
            const checked = !!p[action];
            return `<td class="center"><input type="checkbox" class="adm-perm-chk" data-mod="${m.id}" data-action="${action}" ${checked?'checked':''} onchange="admPermChanged(this)"></td>`;
        };
        const allChecked = m.actions.every(a => !!p[a]);
        html += `<tr><td style="font-weight:600;">${esc(m.label)}</td>`;
        html += cell('view') + cell('create') + cell('edit') + cell('delete');
        html += `<td class="center"><input type="checkbox" class="adm-perm-row-all" data-mod="${m.id}" ${allChecked?'checked':''} onchange="admToggleRow(this)"></td></tr>`;
    });
    tbody.innerHTML = html;
    admMatrixSetDisabled(!!isAllAccess);
}

function admPermChanged(checkbox) {
    const mod = checkbox.dataset.mod;
    const rowChks = document.querySelectorAll(`.adm-perm-chk[data-mod="${mod}"]`);
    const rowAllChk = document.querySelector(`.adm-perm-row-all[data-mod="${mod}"]`);
    if (rowAllChk) {
        const applicable = Array.from(rowChks).filter(cb => !cb.disabled);
        rowAllChk.checked = applicable.length > 0 && applicable.every(cb => cb.checked);
    }
}
function admToggleRow(checkbox) {
    const mod = checkbox.dataset.mod;
    document.querySelectorAll(`.adm-perm-chk[data-mod="${mod}"]`).forEach(cb => { if(!cb.disabled) cb.checked = checkbox.checked; });
}
function admRoleToggleAllAccess(checkbox) { admMatrixSetDisabled(checkbox.checked); }
function admMatrixSetDisabled(disabled) {
    document.querySelectorAll('.adm-perm-chk, .adm-perm-row-all').forEach(cb => {
        cb.disabled = disabled;
        cb.parentElement.style.opacity = disabled ? '0.35' : '1';
    });
}

async function admRoleSave() {
    const id = document.getElementById('admRoleEditId').value;
    const name = document.getElementById('admRoleName').value.trim();
    const desc = document.getElementById('admRoleDesc').value.trim();
    const msgEl = document.getElementById('admRoleMsg');

    if (!name) { msgEl.style.color = 'var(--delta-red)'; msgEl.textContent = 'El nombre es obligatorio.'; return; }

    let perms = {};
    if (document.getElementById('admRoleAll').checked) {
        perms = { ALL: { view: true, create: true, edit: true, delete: true } };
    } else {
        document.querySelectorAll('.adm-perm-chk:checked').forEach(cb => {
            const mod = cb.dataset.mod, action = cb.dataset.action;
            if (!perms[mod]) perms[mod] = {};
            perms[mod][action] = true;
        });
    }

    msgEl.style.color = 'var(--text-mid)'; msgEl.textContent = 'Guardando...';
    try {
        const res = await apiFetch('/auth/roles_api.php', {
            method: 'POST',
            body: JSON.stringify({ id: id || null, role_name: name, description: desc, menu_permissions: perms })
        });
        if (res.success) {
            msgEl.style.color = '#2ECC71'; msgEl.textContent = '✅ Rol guardado.';
            admLoadAll();
            setTimeout(() => { document.getElementById('admRoleEditor').classList.remove('open'); msgEl.textContent=''; }, 1200);
        } else {
            msgEl.style.color = 'var(--delta-red)'; msgEl.textContent = res.message;
        }
    } catch(e) {
        msgEl.style.color = 'var(--delta-red)'; msgEl.textContent = 'Error de conexión.';
    }
}
</script>


<script>
// ══════════════════════════════════════════════════════════════
//  MÓDULO METRIC LATAM (placeholder funcional)
// ══════════════════════════════════════════════════════════════
function metricInit() {
    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-navy)"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
            <div class="module-title">METRIC Latam</div>
        </div>
        <div class="card" style="padding:40px;text-align:center;">
            <div class="empty-state">
                Módulo METRIC Latam — pendiente de definición funcional.<br>
                <span style="font-size:12px;">Cuando se definan los requisitos específicos de este reporte, se conectará a <code>shipments_api.php</code> igual que los demás módulos.</span>
            </div>
        </div>
    `;
}

// ══════════════════════════════════════════════════════════════
//  MÓDULO BOOKING FORECAST (bf*)  —  antes "Booking Report"
//  Herramienta de análisis y proyección de carga futura reservada
//  por Booking: KG asegurados por Station/Date Range/Flight/Agent/
//  PROD/Destination, con gráfico semanal → drill-down diario (clic
//  en la barra de una semana), breakdown por Agent y Destination
//  (crossfilter interactivo, mismo patrón que el módulo Revenues),
//  y detalle de AWBs. Usa CHG WGT como peso principal (Sección 11
//  del requerimiento) — GROSS WGT queda disponible solo en el
//  detalle. BOOKING FORECAST ≠ ACTUAL SHIPPED CARGO (Sección 12):
//  se excluyen siempre las guías CANCELLED, y por defecto también
//  LOADED/OFFLOADED (ya embarcadas → dejaron de ser "proyección").
// ══════════════════════════════════════════════════════════════
let bfAllData = [];            // dataset crudo del rango/estación seleccionados (fetch único; todo lo demás es crossfilter client-side)
let bfCurrentFullData = [];    // último subconjunto totalmente filtrado (KPIs/tabla) — usado por Export Excel
let bfSelectedAgents = new Set();
let bfSelectedProds  = new Set();
let bfSelectedDest   = '';
let bfSelectedFlight = '';
let bfSelectedDay = null;      // 'YYYY-MM-DD' — filtro opcional de KPI/tabla al hacer clic en una barra diaria (independiente del scroll)
let bfGranularity = 'week';    // 'week' | 'day' — SOLO cambia con clic en una barra semanal o Reset Zoom (zoom), nunca con el scroll
let bfChartOffset = 0;         // ventana visible del eje X (0 = inicio del rango) — SOLO la mueve el scroll de la rueda (pan)
let bfChartAllPeriods = [];    // todos los períodos (semanas o días) del rango de fechas seleccionado, ya agrupados
let bfChartLastData = [];      // último dataset crossfiltrado usado para construir bfChartAllPeriods (reutilizado al perforar una semana)
let bfRangeFrom = '', bfRangeTo = ''; // límites del Date Range activo — generan la línea de tiempo completa (con huecos en 0) para que el scroll sea continuo
let bfDatePicker = null;
let bfAgentChart = null, bfDestChart = null, bfMainChart = null;
let bfOutsideListenerBound = false;
const BF_ROW_PX = 26;
const BF_DAY_NAMES = ['Dom','Lun','Mar','Mié','Jue','Vie','Sáb'];
const BF_COLORS = ['#0066B2','#C8102E','#1A7A4A','#FFC72C','#7B2D8B','#E07B00','#00838F'];
const BF_WEEK_WINDOW = 8;  // semanas visibles simultáneamente en el gráfico
const BF_DAY_WINDOW  = 7;  // días visibles simultáneamente en el gráfico

function reportsInit() {
    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-navy)"><path d="M3 3v18h18"/><path d="M18 17V9"/><path d="M13 17V5"/><path d="M8 17v-3"/></svg>
            <div class="module-title">Booking Forecast</div>
            <div class="module-badge" id="bfBadge">0 AWBs</div>
        </div>

        <div class="toolbar" style="flex-wrap:wrap;row-gap:8px;">
            <label class="tb-lbl">Station</label>
            <select id="bfFilterStation" onchange="bfLoad()" style="width:90px;"></select>
            <div class="toolbar-sep"></div>
            <label class="tb-lbl">Date Range</label>
            <input type="text" id="bfFilterDateRange" placeholder="Rango de fechas..." readonly style="width:190px;cursor:pointer;">
            <div class="toolbar-sep"></div>
            <label class="tb-lbl">Flight</label>
            <select id="bfFilterFlight" onchange="bfOnHeaderFilterChange()" style="width:130px;">
                <option value="">All Flights</option>
            </select>
            <div class="toolbar-sep"></div>
            <label class="tb-lbl">Destination</label>
            <select id="bfFilterDest" onchange="bfOnHeaderFilterChange()" style="width:120px;">
                <option value="">All Destinations</option>
            </select>
            <div class="toolbar-sep"></div>
            <div class="bf-multiselect">
                <button type="button" class="bf-multiselect-btn" id="bfAgentBtn" onclick="bfMultiOpen('agent')">Agent / FF <span id="bfAgentBtnCount"></span> &#9662;</button>
                <div class="bf-multiselect-panel" id="bfAgentPanel" style="display:none;"></div>
            </div>
            <div class="bf-multiselect">
                <button type="button" class="bf-multiselect-btn" id="bfProdBtn" onclick="bfMultiOpen('prod')">PROD <span id="bfProdBtnCount"></span> &#9662;</button>
                <div class="bf-multiselect-panel" id="bfProdPanel" style="display:none;"></div>
            </div>
            <div class="toolbar-sep"></div>
            <label style="display:flex;align-items:center;gap:5px;font-size:12px;cursor:pointer;white-space:nowrap;padding:0 8px;border:1px solid var(--border);border-radius:4px;height:30px;background:#fff;">
                <input type="checkbox" id="bfIncludeShipped" onchange="bfLoad()" style="width:14px;height:14px;cursor:pointer;">
                Incluir Embarcadas
            </label>
            <div style="margin-left:auto;">
                <button class="btn-secondary" onclick="bfExportExcel()">
                    <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
                    Exportar Excel
                </button>
            </div>
        </div>

        <div id="bfActiveFilters" style="margin-bottom:10px;"></div>

        <div class="kpi-row">
            <div class="kpi-card c-weight"><div class="kpi-label">Total Booked KG</div><div class="kpi-value" id="bfKpiKg">—</div><div class="kpi-sub">CHG WGT</div></div>
            <div class="kpi-card c-awbs"><div class="kpi-label">Total AWBs</div><div class="kpi-value" id="bfKpiAwbs">—</div><div class="kpi-sub">bookings</div></div>
            <div class="kpi-card c-rate"><div class="kpi-label">Total Pieces</div><div class="kpi-value" id="bfKpiPieces">—</div><div class="kpi-sub">piezas</div></div>
            <div class="kpi-card c-revenue"><div class="kpi-label">Avg KG / AWB</div><div class="kpi-value" id="bfKpiAvg">—</div><div class="kpi-sub">promedio</div></div>
        </div>

        <div class="bf-grid">
            <div class="rev-panel bf-chart-panel">
                <div class="rev-panel-header">
                    <span class="rev-panel-title">Booking Forecast — Booked KG</span>
                    <span class="rev-panel-badge" id="bfChartBadge">—</span>
                    <button class="btn-secondary" id="bfResetZoomBtn" style="display:none;margin-left:10px;height:24px;padding:0 10px;font-size:11px;" onclick="bfResetZoom()">&#8634; Reset Zoom</button>
                </div>
                <div class="rev-panel-body" style="height:320px;display:flex;flex-direction:column;">
                    <div style="font-size:11px;color:var(--text-light);margin-bottom:4px;flex-shrink:0;">Scroll (rueda del mouse) para desplazarte en el tiempo · Clic en una barra de semana para ver el detalle diario.</div>
                    <div id="bfChartWrap" style="flex:1;min-height:0;position:relative;cursor:ew-resize;" title="Scroll para desplazar el período"><canvas id="bfMainChart"></canvas></div>
                </div>
            </div>
            <div class="rev-panel">
                <div class="rev-panel-header"><span class="rev-panel-title">Freight Forwarder / Agent</span><span class="rev-panel-badge" id="bfAgentBadge">—</span></div>
                <div class="rev-panel-body"><div class="rev-canvas-container" id="bfAgentContainer"><canvas id="bfAgentChart"></canvas></div></div>
            </div>
            <div class="rev-panel">
                <div class="rev-panel-header"><span class="rev-panel-title">Destination</span><span class="rev-panel-badge" id="bfDestBadge">—</span></div>
                <div class="rev-panel-body"><div class="rev-canvas-container" id="bfDestContainer"><canvas id="bfDestChart"></canvas></div></div>
            </div>
        </div>

        <div class="rev-table-panel">
            <div class="rev-panel-header"><span class="rev-panel-title">Booking Detail</span><span class="rev-panel-badge" id="bfTableBadge">—</span></div>
            <div style="overflow-x:auto;">
                <table class="data-table">
                    <thead><tr>
                        <th>Date</th><th>Flight</th><th>Origin</th><th>Destination</th><th>Agent</th><th>AWB</th><th>PROD</th>
                        <th style="text-align:right;">Pieces</th><th style="text-align:right;">Gross Wgt</th><th style="text-align:right;">Chg Wgt</th>
                    </tr></thead>
                    <tbody id="bfTableBody"></tbody>
                </table>
            </div>
        </div>
    `;

    bfSelectedAgents = new Set(); bfSelectedProds = new Set();
    bfSelectedDest = ''; bfSelectedFlight = ''; bfSelectedDay = null;
    bfGranularity = 'week'; bfChartOffset = 0;

    bfInitDateRangePicker();
    populateStationSelect('bfFilterStation').then(() => bfLoad());
}

// ── Date Range: por defecto hoy → +30 días (proyección hacia adelante) ──
function bfInitDateRangePicker() {
    const el = document.getElementById('bfFilterDateRange');
    if (!el) return;
    const fmt = d => d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');
    const from = new Date();
    const to = new Date(); to.setDate(to.getDate() + 30);
    bfDatePicker = flatpickr(el, {
        mode: 'range', dateFormat: 'Y-m-d', locale: 'es',
        defaultDate: [fmt(from), fmt(to)],
        onChange: (selectedDates) => { if (selectedDates.length === 1 || selectedDates.length === 2) bfLoad(); }
    });
}

// ── Helpers de fecha: semana ISO (lunes a domingo) ───────────────
function bfDateStr(d) { return d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0'); }
function bfMondayOf(dateObj) {
    const d = new Date(dateObj);
    const day = d.getDay();
    d.setDate(d.getDate() + (day === 0 ? -6 : 1 - day));
    d.setHours(0,0,0,0);
    return d;
}
function bfISOWeekNumber(dateObj) {
    const d = new Date(Date.UTC(dateObj.getFullYear(), dateObj.getMonth(), dateObj.getDate()));
    const dayNum = d.getUTCDay() || 7;
    d.setUTCDate(d.getUTCDate() + 4 - dayNum);
    const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
    return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
}

// ── Único fetch al servidor (Station + Date Range). Todo lo demás
// (Flight/Agent/PROD/Destination/drill-down) es crossfilter en JS
// sobre bfAllData, igual que el módulo Revenues. ──────────────────
async function bfLoad() {
    bfGranularity = 'week'; bfChartOffset = 0; bfSelectedDay = null;

    let fromDate = '', toDate = '';
    if (bfDatePicker && bfDatePicker.selectedDates.length >= 1) {
        const fmt = d => d.getFullYear()+'-'+String(d.getMonth()+1).padStart(2,'0')+'-'+String(d.getDate()).padStart(2,'0');
        fromDate = fmt(bfDatePicker.selectedDates[0]);
        toDate = bfDatePicker.selectedDates.length >= 2 ? fmt(bfDatePicker.selectedDates[1]) : fromDate;
    }
    bfRangeFrom = fromDate; bfRangeTo = toDate;
    const station = document.getElementById('bfFilterStation')?.value || '';
    const includeShipped = document.getElementById('bfIncludeShipped')?.checked || false;
    const params = new URLSearchParams({ station: station, from: fromDate, to: toDate });

    try {
        const res = await apiFetch('/shipments/shipments_api.php?' + params.toString());
        const raw = res.success ? res.data : [];
        bfAllData = raw
            .filter(r => {
                if (r.operational_status === 'CANCELLED') return false;
                if (!includeShipped && (r.operational_status === 'LOADED' || r.operational_status === 'OFFLOADED')) return false;
                return true;
            })
            .filter(r => r.flt_date) // Sección 12: solo bookings con fecha de vuelo válida
            .map(r => {
                const fltDateObj = new Date(r.flt_date + 'T00:00:00');
                const monday = bfMondayOf(fltDateObj);
                return {
                    awb: r.awb || r.shipment_number,
                    fltNumber: r.flt_number || '—',
                    fltOrig: r.flt_orig || '—',
                    fltDest: r.flt_dest || '—',
                    agent: r.agent_name || 'UNKNOWN',
                    prod: r.product_code || 'N/A',
                    pieces: parseInt(r.pieces) || 0,
                    grossWgt: parseFloat(r.gross_weight) || 0,
                    chgWgt: parseFloat(r.chg_weight) || 0,
                    status: r.operational_status,
                    dateStr: r.flt_date,
                    weekKey: bfDateStr(monday),
                    weekLabel: 'Week ' + bfISOWeekNumber(fltDateObj),
                };
            });
        bfPopulateFilterOptions();
        bfUpdateDashboard();
    } catch(e) {
        console.warn('bfLoad error:', e);
        bfAllData = [];
        bfPopulateFilterOptions();
        bfUpdateDashboard();
        document.getElementById('bfTableBody').innerHTML = '<tr><td colspan="10" style="text-align:center;color:var(--delta-red);padding:20px;">Error de conexión.</td></tr>';
    }
}

// ── Puebla Flight/Destination (select únicos) y limpia de los sets
// Agent/PROD cualquier valor que ya no exista en el dataset recargado ──
function bfPopulateFilterOptions() {
    const flights = [...new Set(bfAllData.map(r => r.fltNumber))].filter(Boolean).sort();
    const dests   = [...new Set(bfAllData.map(r => r.fltDest))].filter(Boolean).sort();

    const fltSel = document.getElementById('bfFilterFlight');
    if (fltSel) {
        const cur = bfSelectedFlight;
        fltSel.innerHTML = '<option value="">All Flights</option>' + flights.map(f => `<option value="${esc(f)}">${esc(f)}</option>`).join('');
        if (cur && flights.includes(cur)) fltSel.value = cur; else bfSelectedFlight = '';
    }
    const destSel = document.getElementById('bfFilterDest');
    if (destSel) {
        const cur = bfSelectedDest;
        destSel.innerHTML = '<option value="">All Destinations</option>' + dests.map(d => `<option value="${esc(d)}">${esc(d)}</option>`).join('');
        if (cur && dests.includes(cur)) destSel.value = cur; else bfSelectedDest = '';
    }

    const agentSet = new Set(bfAllData.map(r => r.agent));
    const prodSet  = new Set(bfAllData.map(r => r.prod));
    bfSelectedAgents = new Set([...bfSelectedAgents].filter(a => agentSet.has(a)));
    bfSelectedProds  = new Set([...bfSelectedProds].filter(p => prodSet.has(p)));
    bfUpdateMultiBtnLabels();
}

function bfOnHeaderFilterChange() {
    bfSelectedFlight = document.getElementById('bfFilterFlight')?.value || '';
    bfSelectedDest   = document.getElementById('bfFilterDest')?.value || '';
    bfChartOffset = 0; // Sección 8: al cambiar un filtro, reposicionar el eje X al nuevo conjunto de datos
    bfUpdateDashboard();
}

// ── Multi-select (checkbox popover) para Agent / PROD ────────────
function bfMultiOpen(type) {
    const panelId = type === 'agent' ? 'bfAgentPanel' : 'bfProdPanel';
    const otherId = type === 'agent' ? 'bfProdPanel' : 'bfAgentPanel';
    const panel = document.getElementById(panelId);
    const wasOpen = panel.style.display !== 'none';
    document.getElementById(otherId).style.display = 'none';
    if (wasOpen) { panel.style.display = 'none'; return; }
    bfRenderMultiPanel(type);
    panel.style.display = 'flex';
    if (!bfOutsideListenerBound) {
        document.addEventListener('click', bfMultiOutsideClick);
        bfOutsideListenerBound = true;
    }
}
function bfMultiOutsideClick(e) {
    const agentWrap = document.getElementById('bfAgentBtn')?.closest('.bf-multiselect');
    const prodWrap  = document.getElementById('bfProdBtn')?.closest('.bf-multiselect');
    if (agentWrap && !agentWrap.contains(e.target)) document.getElementById('bfAgentPanel').style.display = 'none';
    if (prodWrap  && !prodWrap.contains(e.target))  document.getElementById('bfProdPanel').style.display = 'none';
}
function bfRenderMultiPanel(type) {
    const isAgent = type === 'agent';
    const values = [...new Set(bfAllData.map(r => isAgent ? r.agent : r.prod))].filter(Boolean).sort();
    const selectedSet = isAgent ? bfSelectedAgents : bfSelectedProds;
    const panel = document.getElementById(isAgent ? 'bfAgentPanel' : 'bfProdPanel');
    panel.style.flexDirection = 'column';
    panel.innerHTML = `
        <div class="bf-multiselect-search"><input type="text" placeholder="Buscar..." oninput="bfMultiFilterList('${type}', this.value)"></div>
        <div class="bf-multiselect-list" id="bf${isAgent?'Agent':'Prod'}List">
            ${values.length ? values.map(v => `<label><input type="checkbox" value="${esc(v)}" ${selectedSet.has(v)?'checked':''} onchange="bfMultiToggle('${type}', this.value, this.checked)">${esc(v)}</label>`).join('') : '<div style="padding:10px;font-size:12px;color:var(--text-light);">Sin datos.</div>'}
        </div>
        <div class="bf-multiselect-footer">
            <button type="button" onclick="bfMultiClear('${type}')">Limpiar</button>
            <button type="button" onclick="bfMultiOpen('${type}')">Cerrar</button>
        </div>
    `;
}
function bfMultiFilterList(type, q) {
    const listEl = document.getElementById(type === 'agent' ? 'bfAgentList' : 'bfProdList');
    if (!listEl) return;
    const needle = q.trim().toLowerCase();
    listEl.querySelectorAll('label').forEach(lbl => {
        const txt = lbl.textContent.trim().toLowerCase();
        lbl.style.display = !needle || txt.includes(needle) ? 'flex' : 'none';
    });
}
function bfMultiToggle(type, value, checked) {
    const set = type === 'agent' ? bfSelectedAgents : bfSelectedProds;
    if (checked) set.add(value); else set.delete(value);
    bfChartOffset = 0; // Sección 8: reposicionar el eje X al nuevo conjunto de datos
    bfUpdateMultiBtnLabels();
    bfUpdateDashboard();
}
function bfMultiClear(type) {
    if (type === 'agent') bfSelectedAgents.clear(); else bfSelectedProds.clear();
    bfChartOffset = 0;
    bfRenderMultiPanel(type);
    bfUpdateMultiBtnLabels();
    bfUpdateDashboard();
}
function bfUpdateMultiBtnLabels() {
    const agentBtn = document.getElementById('bfAgentBtn');
    const prodBtn  = document.getElementById('bfProdBtn');
    if (agentBtn) {
        agentBtn.classList.toggle('has-value', bfSelectedAgents.size > 0);
        document.getElementById('bfAgentBtnCount').textContent = bfSelectedAgents.size ? '(' + bfSelectedAgents.size + ')' : '';
    }
    if (prodBtn) {
        prodBtn.classList.toggle('has-value', bfSelectedProds.size > 0);
        document.getElementById('bfProdBtnCount').textContent = bfSelectedProds.size ? '(' + bfSelectedProds.size + ')' : '';
    }
}

// ── Predicados de filtro (todos los filtros combinados — Sección 2) ──
function bfMatchesAgent(row)  { return bfSelectedAgents.size === 0 || bfSelectedAgents.has(row.agent); }
function bfMatchesProd(row)   { return bfSelectedProds.size === 0  || bfSelectedProds.has(row.prod); }
function bfMatchesDest(row)   { return !bfSelectedDest   || row.fltDest === bfSelectedDest; }
function bfMatchesFlight(row) { return !bfSelectedFlight || row.fltNumber === bfSelectedFlight; }
function bfMatchesPeriod(row) {
    // El scroll/zoom del gráfico NO filtra KPIs/tabla (Sección 8: solo los
    // filtros de cabecera lo hacen) — únicamente el clic explícito en una
    // barra diaria aplica este filtro opcional.
    return !bfSelectedDay || row.dateStr === bfSelectedDay;
}

// ── Orquestador principal: recalcula KPIs, gráficos y tabla cada
// vez que cambia cualquier filtro (Sección 2 — todos combinados) ──
function bfUpdateDashboard() {
    document.getElementById('bfBadge').textContent = bfAllData.length + ' AWB' + (bfAllData.length !== 1 ? 's' : '');

    // Cada panel de breakdown excluye su propia dimensión del filtro (mismo
    // patrón crossfilter del módulo Revenues) para poder seguir mostrando
    // las demás opciones disponibles bajo el resto de filtros activos.
    const dataForAgent = bfAllData.filter(r => bfMatchesFlight(r) && bfMatchesProd(r) && bfMatchesDest(r) && bfMatchesPeriod(r));
    const dataForDest  = bfAllData.filter(r => bfMatchesFlight(r) && bfMatchesAgent(r) && bfMatchesProd(r) && bfMatchesPeriod(r));
    const dataForChart = bfAllData.filter(r => bfMatchesFlight(r) && bfMatchesAgent(r) && bfMatchesProd(r) && bfMatchesDest(r)); // sin período: el propio gráfico decide la agrupación (semana/día)
    const fullData      = bfAllData.filter(r => bfMatchesFlight(r) && bfMatchesAgent(r) && bfMatchesProd(r) && bfMatchesDest(r) && bfMatchesPeriod(r));
    bfCurrentFullData = fullData;

    bfUpdateKpis(fullData);
    bfAgentChart = bfRenderHBar('bfAgentChart', 'bfAgentContainer', dataForAgent, 'agent', bfAgentChart, v => {
        if (bfSelectedAgents.has(v)) bfSelectedAgents.delete(v); else bfSelectedAgents.add(v);
        bfChartOffset = 0;
        bfUpdateMultiBtnLabels(); bfUpdateDashboard();
    });
    bfDestChart = bfRenderHBar('bfDestChart', 'bfDestContainer', dataForDest, 'fltDest', bfDestChart, v => {
        bfSelectedDest = bfSelectedDest === v ? '' : v;
        bfChartOffset = 0;
        const sel = document.getElementById('bfFilterDest'); if (sel) sel.value = bfSelectedDest;
        bfUpdateDashboard();
    });
    bfRenderMainChart(dataForChart);
    bfRenderTable(fullData);
    bfRenderActiveFilters();
    bfUpdateBadges(dataForAgent, dataForDest, fullData);
}

function bfUpdateKpis(data) {
    const totalKg = data.reduce((s,d) => s + d.chgWgt, 0);
    const totalAwbs = data.length;
    const totalPieces = data.reduce((s,d) => s + d.pieces, 0);
    const avgKg = totalAwbs > 0 ? totalKg / totalAwbs : 0;
    document.getElementById('bfKpiKg').textContent = fmtWgt0(totalKg);
    document.getElementById('bfKpiAwbs').textContent = totalAwbs.toLocaleString('en-US');
    document.getElementById('bfKpiPieces').textContent = totalPieces.toLocaleString('en-US');
    document.getElementById('bfKpiAvg').textContent = fmtWgt0(avgKg);
}

function bfUpdateBadges(dAgent, dDest, dFull) {
    const uniq = (arr, key) => new Set(arr.map(r => r[key])).size;
    document.getElementById('bfAgentBadge').textContent = uniq(dAgent,'agent') + ' agentes';
    document.getElementById('bfDestBadge').textContent = uniq(dDest,'fltDest') + ' destinos';
    document.getElementById('bfTableBadge').textContent = Math.min(dFull.length,200) + ' de ' + dFull.length + ' registros';
}

function bfRenderActiveFilters() {
    const c = document.getElementById('bfActiveFilters');
    if (!c) return;
    c.innerHTML = '';
    const addChip = (label, value, clearFn) => {
        if (!value) return;
        const chip = document.createElement('div');
        chip.className = 'filter-chip';
        chip.innerHTML = `<span>${esc(label)}:</span> ${esc(value)} \u2715`;
        chip.onclick = clearFn;
        c.appendChild(chip);
    };
    [...bfSelectedAgents].forEach(a => addChip('Agent', a, () => { bfSelectedAgents.delete(a); bfChartOffset = 0; bfUpdateMultiBtnLabels(); bfUpdateDashboard(); }));
    [...bfSelectedProds].forEach(p => addChip('PROD', p, () => { bfSelectedProds.delete(p); bfChartOffset = 0; bfUpdateMultiBtnLabels(); bfUpdateDashboard(); }));
    addChip('Destination', bfSelectedDest, () => { bfSelectedDest = ''; bfChartOffset = 0; const sel = document.getElementById('bfFilterDest'); if (sel) sel.value = ''; bfUpdateDashboard(); });
    addChip('Flight', bfSelectedFlight, () => { bfSelectedFlight = ''; bfChartOffset = 0; const sel = document.getElementById('bfFilterFlight'); if (sel) sel.value = ''; bfUpdateDashboard(); });
    if (bfGranularity === 'day') addChip('Vista', 'Diaria (zoom)', () => bfResetZoom());
    if (bfSelectedDay) addChip('Día', fmtDate(bfSelectedDay), () => { bfSelectedDay = null; bfUpdateDashboard(); });
}

// ── Gráfico principal: línea de tiempo navegable (Sección 4-6-10 del
// requerimiento original + corrección de scroll de esta iteración).
// ZOOM (clic en una barra semanal) cambia bfGranularity 'week'→'day' —
// esto es lo único que cambia el nivel de detalle. SCROLL (rueda del
// mouse sobre el gráfico) solo mueve bfChartOffset, la ventana visible
// del eje X — nunca toca bfGranularity ni filtra KPIs/tabla (Sección 8:
// eso lo gobiernan únicamente los filtros de cabecera). ──────────────

// Genera la línea de tiempo COMPLETA del Date Range seleccionado (con
// huecos en 0 para las semanas/días sin bookings) para que el scroll
// recorra un eje continuo, no solo los períodos que tienen datos.
function bfGenerateWeeklyPeriods(fromStr, toStr) {
    if (!fromStr || !toStr) return [];
    const start = bfMondayOf(new Date(fromStr + 'T00:00:00'));
    const end = new Date(toStr + 'T00:00:00');
    const periods = [];
    let cur = new Date(start);
    while (cur <= end) {
        periods.push({ key: bfDateStr(cur), label: 'Week ' + bfISOWeekNumber(cur), byProd: {} });
        cur.setDate(cur.getDate() + 7);
    }
    return periods;
}
function bfGenerateDailyPeriods(fromStr, toStr) {
    if (!fromStr || !toStr) return [];
    const start = new Date(fromStr + 'T00:00:00');
    const end = new Date(toStr + 'T00:00:00');
    const periods = [];
    let cur = new Date(start);
    while (cur <= end) {
        periods.push({ key: bfDateStr(cur), label: BF_DAY_NAMES[cur.getDay()] + ' ' + String(cur.getDate()).padStart(2,'0'), byProd: {} });
        cur.setDate(cur.getDate() + 1);
    }
    return periods;
}
function bfBuildPeriods(data, granularity) {
    const skeleton = granularity === 'week' ? bfGenerateWeeklyPeriods(bfRangeFrom, bfRangeTo) : bfGenerateDailyPeriods(bfRangeFrom, bfRangeTo);
    const byKey = {};
    skeleton.forEach(p => { byKey[p.key] = p; });
    data.forEach(row => {
        const key = granularity === 'week' ? row.weekKey : row.dateStr;
        const p = byKey[key];
        if (!p) return; // fuera del rango seleccionado (no debería ocurrir)
        p.byProd[row.prod] = (p.byProd[row.prod] || 0) + row.chgWgt;
    });
    return skeleton;
}

// Llamado desde bfUpdateDashboard() en cada cambio de filtro: reconstruye
// la línea de tiempo completa para la granularidad activa, ajusta el
// offset si quedó fuera de rango, (re)ata el listener de scroll una sola
// vez, y dibuja. NO se llama en cada tick de scroll (ver bfChartScroll). ──
function bfRenderMainChart(data) {
    bfChartLastData = data;
    bfChartAllPeriods = bfBuildPeriods(data, bfGranularity);

    const windowSize = bfGranularity === 'week' ? BF_WEEK_WINDOW : BF_DAY_WINDOW;
    const maxOffset = Math.max(0, bfChartAllPeriods.length - windowSize);
    bfChartOffset = Math.min(Math.max(0, bfChartOffset), maxOffset);

    bfBindChartWheel();
    bfDrawMainChart();
}

// Dibuja únicamente la ventana visible (bfChartOffset → +windowSize) de
// bfChartAllPeriods. Esta es la única función que corre en cada scroll
// tick — no recalcula datos, solo re-slice + redibuja (mismo patrón que
// revDrawMainChart en el módulo Revenues). ────────────────────────────
function bfDrawMainChart() {
    const windowSize = bfGranularity === 'week' ? BF_WEEK_WINDOW : BF_DAY_WINDOW;
    const total = bfChartAllPeriods.length;
    const maxOffset = Math.max(0, total - windowSize);
    bfChartOffset = Math.min(Math.max(0, bfChartOffset), maxOffset);

    const startIdx = bfChartOffset;
    const visible = bfChartAllPeriods.slice(startIdx, startIdx + windowSize);
    const labels = visible.map(p => p.label);

    // Top N PROD por peso total (dentro de la ventana visible), resto
    // agrupado en "Others" para no saturar la leyenda apilada.
    const prodTotals = {};
    visible.forEach(p => Object.entries(p.byProd).forEach(([prod, kg]) => { prodTotals[prod] = (prodTotals[prod]||0) + kg; }));
    const MAX_PRODS = 7;
    const topProds = Object.entries(prodTotals).sort((a,b) => b[1]-a[1]).slice(0, MAX_PRODS).map(x => x[0]);
    const hasOthers = Object.keys(prodTotals).length > topProds.length;

    const datasets = topProds.map((prod, i) => ({
        label: prod, stack: 'kg', borderRadius: 3,
        backgroundColor: BF_COLORS[i % BF_COLORS.length],
        data: visible.map(p => +(p.byProd[prod] || 0).toFixed(1)),
    }));
    if (hasOthers) {
        datasets.push({
            label: 'Others', stack: 'kg', borderRadius: 3, backgroundColor: '#C7D6E5',
            data: visible.map(p => {
                const known = topProds.reduce((s,pr) => s + (p.byProd[pr]||0), 0);
                const total2 = Object.values(p.byProd).reduce((s,v) => s+v, 0);
                return +(total2 - known).toFixed(1);
            }),
        });
    }

    const badge = document.getElementById('bfChartBadge');
    if (badge) {
        const modeLabel = bfGranularity === 'week' ? 'vista semanal' : 'vista diaria';
        const rangeLabel = visible.length ? visible[0].label + ' \u2192 ' + visible[visible.length-1].label : 'Sin datos';
        const posLabel = total ? (startIdx+1) + '\u2013' + (startIdx+visible.length) + ' de ' + total : '';
        badge.textContent = rangeLabel + ' (' + modeLabel + ')' + (posLabel ? ' · ' + posLabel : '');
    }
    const resetBtn = document.getElementById('bfResetZoomBtn');
    if (resetBtn) resetBtn.style.display = bfGranularity === 'day' ? 'inline-block' : 'none';

    const canvas = document.getElementById('bfMainChart');
    if (!canvas) return;
    if (bfMainChart) bfMainChart.destroy();
    bfMainChart = new Chart(canvas.getContext('2d'), {
        type: 'bar',
        data: { labels, datasets },
        options: {
            responsive: true, maintainAspectRatio: false,
            onClick: (evt, elements) => {
                if (!elements.length) return;
                const clicked = visible[elements[0].index];
                if (bfGranularity === 'week') {
                    // ZOOM: entra a vista diaria posicionando el scroll en el
                    // lunes de la semana clickeada (mismo dataset, sin refetch).
                    bfGranularity = 'day';
                    const dailyPeriods = bfBuildPeriods(bfChartLastData, 'day');
                    const idx = dailyPeriods.findIndex(p => p.key === clicked.key);
                    bfChartOffset = idx >= 0 ? idx : 0;
                    bfSelectedDay = null;
                    bfRenderMainChart(bfChartLastData);
                } else {
                    // En vista diaria un clic ya no hace zoom (es el nivel más
                    // fino) — solo aplica/quita el filtro opcional de KPI/tabla.
                    bfSelectedDay = bfSelectedDay === clicked.key ? null : clicked.key;
                    bfUpdateDashboard();
                }
            },
            plugins: {
                legend: { position:'top', align:'start', labels:{ color:'#002244', font:{family:"'Barlow',sans-serif",size:11,weight:'600'}, boxWidth:10, padding:12 } },
                tooltip: { mode:'index', intersect:false, callbacks: { label: c => c.dataset.label + ': ' + fmtWgt0(c.raw) + ' kg' } }
            },
            scales: {
                x: { stacked: true, grid:{ display:false }, ticks:{ color:'#002244', font:{family:"'Barlow',sans-serif",size:11,weight:'600'} } },
                y: { stacked: true, grid:{ color:'#EEF1F5' }, ticks:{ color:'#556', callback: v => fmtWgt0(v) } }
            },
            // 'pointer' justo sobre una barra (clickeable); 'ew-resize' en el
            // resto del canvas, recordando que ahí el scroll desplaza el eje X.
            onHover: (evt, elements) => { if (evt.native?.target) evt.native.target.style.cursor = elements.length ? 'pointer' : 'ew-resize'; }
        }
    });
}

// ── Scroll (pan) del eje X: solo mueve bfChartOffset y redibuja —
// nunca cambia bfGranularity ni recalcula KPIs/tabla (Secciones 3, 4, 6). ──
function bfChartScroll(dir) {
    const windowSize = bfGranularity === 'week' ? BF_WEEK_WINDOW : BF_DAY_WINDOW;
    const maxOffset = Math.max(0, bfChartAllPeriods.length - windowSize);
    bfChartOffset = Math.max(0, Math.min(bfChartOffset + dir, maxOffset));
    bfDrawMainChart();
}

// Ata el listener de rueda/touch al contenedor del gráfico UNA sola vez
// por render completo (clona el nodo para no acumular listeners viejos —
// mismo patrón que revRenderMainChart). No se re-llama en cada scroll. ──
function bfBindChartWheel() {
    const wrap = document.getElementById('bfChartWrap');
    if (!wrap) return;
    const newWrap = wrap.cloneNode(false);
    while (wrap.firstChild) newWrap.appendChild(wrap.firstChild);
    wrap.parentNode.replaceChild(newWrap, wrap);
    newWrap.id = 'bfChartWrap';

    newWrap.addEventListener('wheel', (e) => {
        e.preventDefault(); e.stopPropagation();
        // Scroll hacia abajo (deltaY > 0) → avanzar en el período; hacia arriba → retroceder (Sección 6)
        bfChartScroll(e.deltaY > 0 ? 1 : -1);
    }, { passive: false });

    let touchStartX = 0;
    newWrap.addEventListener('touchstart', e => { touchStartX = e.touches[0].clientX; }, { passive: true });
    newWrap.addEventListener('touchend', e => {
        const diff = touchStartX - e.changedTouches[0].clientX;
        if (Math.abs(diff) > 30) bfChartScroll(diff > 0 ? 1 : -1);
    }, { passive: true });
}

function bfResetZoom() {
    bfGranularity = 'week';
    bfChartOffset = 0;
    bfSelectedDay = null;
    bfUpdateDashboard();
}

// ── Gráficos de barras horizontales: Agent / Destination (clic
// para agregar/quitar del filtro — mismo patrón que Revenues) ──
function bfRenderHBar(canvasId, containerId, data, groupCol, chartInstance, onClickCb) {
    const grouped = {};
    data.forEach(row => { const k = row[groupCol] || 'UNKNOWN'; grouped[k] = (grouped[k]||0) + row.chgWgt; });
    const sorted = Object.entries(grouped).sort((a,b) => b[1]-a[1]);
    const labels = sorted.map(x => x[0]), barData = sorted.map(x => x[1]);

    const isAgentPanel = groupCol === 'agent';
    const bgColors = labels.map(l => {
        if (isAgentPanel) return bfSelectedAgents.has(l) ? '#C8102E' : (bfSelectedAgents.size ? '#C7D6E5' : '#0066B2');
        return l === bfSelectedDest ? '#C8102E' : (bfSelectedDest ? '#C7D6E5' : '#0066B2');
    });

    const VISIBLE_ROWS = 8;
    const container = document.getElementById(containerId);
    const visibleHeight = VISIBLE_ROWS * BF_ROW_PX + 20;
    if (container) container.style.height = visibleHeight + 'px';
    const canvasH = Math.max(visibleHeight, labels.length * BF_ROW_PX + 20);

    const canvas = document.getElementById(canvasId);
    if (!canvas) return chartInstance;
    canvas.height = canvasH;
    canvas.style.height = canvasH + 'px';
    if (chartInstance) chartInstance.destroy();

    return new Chart(canvas.getContext('2d'), {
        type: 'bar',
        data: { labels, datasets: [{ data: barData, backgroundColor: bgColors, borderRadius: 3 }] },
        options: {
            indexAxis: 'y', responsive: true, maintainAspectRatio: false,
            onClick: (evt, elements) => { if (elements.length) onClickCb(labels[elements[0].index]); },
            plugins: {
                legend: { display: false },
                tooltip: { callbacks: { label: c => fmtWgt0(c.raw) + ' kg' } }
            },
            scales: {
                x: { display: false },
                y: { ticks: { color:'#1A1F29', font:{family:"'Barlow',sans-serif",size:11,weight:'600'} }, grid:{ display:false } }
            },
            onHover: (evt, elements) => { if (evt.native?.target) evt.native.target.style.cursor = elements.length ? 'pointer' : 'default'; }
        }
    });
}

// ── Tabla de detalle (Sección 9) — refleja exactamente los
// registros considerados en KPIs/gráficos con los filtros activos ──
function bfRenderTable(data) {
    const tbody = document.getElementById('bfTableBody');
    if (!tbody) return;
    if (!data.length) {
        tbody.innerHTML = '<tr><td colspan="10" style="text-align:center;color:var(--text-light);padding:20px;">Sin bookings para los filtros seleccionados.</td></tr>';
        return;
    }
    let html = '';
    data.slice(0, 200).forEach(row => {
        html += '<tr>';
        html += `<td style="white-space:nowrap;">${fmtDate(row.dateStr)}</td>`;
        html += `<td>${esc(row.fltNumber)}</td>`;
        html += `<td>${esc(row.fltOrig)}</td>`;
        html += `<td>${esc(row.fltDest)}</td>`;
        html += `<td style="max-width:150px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">${esc(row.agent)}</td>`;
        html += `<td><strong class="awb-mono" style="color:var(--delta-navy);">${esc(row.awb)}</strong></td>`;
        html += `<td>${esc(row.prod)}</td>`;
        html += `<td style="text-align:right;">${row.pieces}</td>`;
        html += `<td style="text-align:right;">${fmtWgt0(row.grossWgt)}</td>`;
        html += `<td style="text-align:right;font-weight:700;">${fmtWgt0(row.chgWgt)}</td>`;
        html += '</tr>';
    });
    tbody.innerHTML = html;
}

function bfExportExcel() {
    if (!bfCurrentFullData.length) { alert('No hay datos para exportar.'); return; }
    const data = bfCurrentFullData.map(r => ({
        Fecha: r.dateStr, Vuelo: r.fltNumber, Origen: r.fltOrig, Destino: r.fltDest,
        Agente: r.agent, AWB: r.awb, PROD: r.prod, Piezas: r.pieces,
        'Peso Bruto': r.grossWgt, 'Peso Cargable': r.chgWgt,
        'Estado Operativo': OPERATIONAL_LABELS[r.status] || r.status,
    }));
    const ws = XLSX.utils.json_to_sheet(data);
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'Booking Forecast');
    XLSX.writeFile(wb, 'Booking_Forecast_' + new Date().toISOString().slice(0,10) + '.xlsx');
}
</script>

<script>
// ══════════════════════════════════════════════════════════════
//  MÓDULO POST-FLIGHT REPORTS (pf*)
//  Tarea #5 — Notificación de Salida · Flight Summary · Canceladas
// ══════════════════════════════════════════════════════════════

// ══════════════════════════════════════════════════════════════
//  FLIGHT DETAIL (fd*) — datos operativos del vuelo por fecha
//  (tipo de aeronave, payload, PAX O/B, NR PAX O/B, bags in bulk,
//  fuel, dep/arr time). El número de vuelo ya existe en el
//  catálogo "Flights" (Catalog Management); acá Operaciones
//  registra la información específica de CADA fecha en que ese
//  vuelo opera. No depende de que exista un manifiesto — los
//  datos se guardan directo en flight_operations y de ahí los
//  lee también Post-Flight (misma tabla, mismo endpoint).
// ══════════════════════════════════════════════════════════════
// ── Flight Profile: dirty-state tracking (unsaved-changes guard) ──
let fdDirty = false;
function fdMarkDirty() { fdDirty = true; }

// Runs onProceed() immediately if there are no unsaved changes; otherwise
// shows the "Unsaved Changes" confirmation dialog first. onCancel() runs
// only if the user picks "Cancel" (i.e. stay / abort the navigation).
function fdConfirmUnsavedThen(onProceed, onCancel) {
    if (!fdDirty) { onProceed(); return; }
    const overlay = document.createElement('div');
    overlay.className = 'modal-overlay';
    overlay.id = 'fdUnsavedModal';
    overlay.innerHTML = `
        <div class="modal-box">
            <div class="modal-box-header">Unsaved Changes</div>
            <div class="modal-box-body">
                <p style="font-size:13px;color:var(--text-mid);line-height:1.5;">
                    You have unsaved changes in the Flight Profile information.<br>
                    Do you want to save the changes before leaving?
                </p>
            </div>
            <div class="modal-box-footer">
                <button class="btn-secondary" id="fdUnsavedCancel">Cancel</button>
                <button class="btn-secondary" id="fdUnsavedDiscard">Discard Changes</button>
                <button class="btn-primary" id="fdUnsavedSave">Save and Continue</button>
            </div>
        </div>`;
    document.body.appendChild(overlay);
    document.getElementById('fdUnsavedCancel').onclick = () => { overlay.remove(); if (onCancel) onCancel(); };
    document.getElementById('fdUnsavedDiscard').onclick = () => { overlay.remove(); fdDirty = false; onProceed(); };
    document.getElementById('fdUnsavedSave').onclick = async () => {
        overlay.remove();
        const ok = await fdSave();
        if (ok) onProceed();
        else fdConfirmUnsavedThen(onProceed, onCancel); // save failed: re-offer the choice
    };
}

// Wraps a <select>/<input> onchange handler with the dirty-state guard:
// reverts the field to its previous value if the user cancels.
function fdGuardedFieldChange(el, proceedFn) {
    const prev = el.dataset.fdPrev !== undefined ? el.dataset.fdPrev : '';
    const next = el.value;
    fdConfirmUnsavedThen(
        () => { el.dataset.fdPrev = next; proceedFn(); },
        () => { el.value = prev; }
    );
}

// Warn on browser tab close / refresh while there are unsaved changes.
window.addEventListener('beforeunload', function(e) {
    if (fdDirty) { e.preventDefault(); e.returnValue = ''; }
});

function flightDetailInit() {
    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M17.8 19.2 16 11l3.5-3.5C21 6 21.5 4 21 3c-1-.5-3 0-4.5 1.5L13 8 4.8 6.2c-.5-.1-1 .1-1.3.5l-.7.7c-.4.4-.3 1.1.2 1.4L9 12l-2 3H4l-1 1 3 2 2 3 1-1v-3l3-2 3.4 5.8c.3.5 1 .6 1.4.2l.7-.7c.4-.3.6-.8.5-1.3z"/></svg>
            <div class="module-title">Flight Profile</div>
        </div>

        <!-- ── Flight / date selector ── -->
        <div style="background:#fff;border:1px solid var(--border);border-radius:8px;
             padding:12px 16px;margin-bottom:12px;display:flex;align-items:flex-end;
             gap:12px;flex-wrap:wrap;box-shadow:0 1px 4px rgba(0,0,0,.06);">
            <div class="awb-field" style="margin:0;">
                <label style="font-size:10px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">STATION</label>
                <select id="fdStation" class="awb-input" style="width:80px;" onchange="fdOnStationChange(this)"></select>
            </div>
            <div class="awb-field" style="margin:0;">
                <label style="font-size:10px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">FLIGHT</label>
                <select id="fdFlight" class="awb-input" style="width:190px;" onchange="fdOnFlightChange(this)">
                    <option value="">— Select —</option>
                </select>
            </div>
            <div class="awb-field" style="margin:0;">
                <label style="font-size:10px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">FLIGHT DATE</label>
                <input type="date" id="fdDate" class="awb-input" style="width:150px;" onchange="fdOnDateChange(this)">
            </div>
            <button class="btn-secondary" onclick="fdLoad()" style="align-self:flex-end;white-space:nowrap;">
                &#8635; Refresh
            </button>
            <div id="fdFlightInfo" style="align-self:center;font-size:12px;color:var(--text-mid);margin-left:4px;"></div>
        </div>

        <div id="fdStatus" style="min-height:16px;font-size:13px;margin-bottom:8px;"></div>

        <!-- ── Flight operational data form ── -->
        <div id="fdFormWrap" style="display:none;background:#F8FAFC;border:1px solid var(--border);
             border-radius:8px;padding:14px 16px;">
            <div style="font-size:10px;font-weight:700;text-transform:uppercase;color:var(--text-mid);
                 letter-spacing:.06em;margin-bottom:10px;">FLIGHT OPERATIONAL DATA</div>
            <div style="display:flex;gap:14px;flex-wrap:wrap;align-items:flex-end;">
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">SHIP #</label>
                    <input type="text" id="fdShipNumber" class="awb-input" placeholder="9876" style="width:90px;text-transform:uppercase;" oninput="fdMarkDirty()">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">AIRCRAFT TYPE</label>
                    <select id="fdAcType" class="awb-input" style="width:170px;" onchange="fdOnAcTypeChange()">
                        <option value="">— Select —</option>
                    </select>
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">PAYLOAD (KG)</label>
                    <input type="number" id="fdPayload" class="awb-input" placeholder="0" style="width:105px;" oninput="fdMarkDirty()">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">PAX O/B</label>
                    <input type="number" id="fdPaxOb" class="awb-input" placeholder="0" style="width:80px;" oninput="fdMarkDirty()">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">NR PAX O/B</label>
                    <input type="number" id="fdNrPax" class="awb-input" placeholder="0" style="width:80px;" oninput="fdMarkDirty()">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">BAGS IN BULK</label>
                    <input type="number" id="fdBagsBulk" class="awb-input" placeholder="0" style="width:90px;" oninput="fdMarkDirty()">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">FUEL (LBS)</label>
                    <input type="number" id="fdFuel" class="awb-input" placeholder="0" style="width:95px;" oninput="fdMarkDirty()">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">ETD</label>
                    <input type="time" id="fdDepTime" class="awb-input" style="width:100px;" onchange="fdMarkDirty()">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:10px;">ARR DEST (LOCAL)</label>
                    <input type="time" id="fdArrTime" class="awb-input" style="width:100px;" onchange="fdMarkDirty()">
                </div>
                <button class="btn-primary" id="fdSaveBtn" onclick="fdSave()" style="align-self:flex-end;">
                    &#128190; Save
                </button>
            </div>
            <div id="fdMeta" style="margin-top:10px;font-size:11px;color:var(--text-light);"></div>
        </div>
    `;
    fdDirty = false;
    fdLoadAircraftTypes();
    populateStationSelect('fdStation').then(() => {
        const st = document.getElementById('fdStation');
        if (st) st.dataset.fdPrev = st.value || '';
        fdLoadFlights();
    });
    const dateEl = document.getElementById('fdDate');
    dateEl.value = new Date().toISOString().slice(0,10);
    dateEl.dataset.fdPrev = dateEl.value;
}

// ── Aircraft Type combo, sourced from the Aircraft Types master catalog
// (active records only). Value = model (matches the format already stored
// in flight_operations.aircraft_type, e.g. "A330-300"). ──
async function fdLoadAircraftTypes() {
    try {
        const res = await apiFetch('/catalog/catalog_api.php?table=aircraft_types&show_inactive=0');
        const sel = document.getElementById('fdAcType');
        if (!sel || !res.success) return;
        const current = sel.value;
        sel.innerHTML = '<option value="">— Select —</option>';
        (res.data || []).forEach(function(a) {
            const opt = document.createElement('option');
            opt.value = a.model || a.full_name || a.iata_code;
            opt.textContent = (a.iata_code ? a.iata_code + ' — ' : '') + (a.model || a.full_name || '');
            opt.title = a.full_name || a.model || '';
            opt.dataset.icao = a.icao_code || '';
            opt.dataset.iata = a.iata_code || '';
            opt.dataset.manufacturer = a.manufacturer || '';
            opt.dataset.fullName = a.full_name || '';
            if (opt.value === current) opt.selected = true;
            sel.appendChild(opt);
        });
    } catch(e) { console.warn('fdLoadAircraftTypes error:', e); }
}

// When the user picks an aircraft type, surface the related catalog info
// (Aircraft Code, Model, manufacturer) next to the flight summary line.
// NOTE: Payload Capacity and cargo-position/bodega configuration are not
// yet columns on aircraft_types — pending schema addition (see memory).
// Until then, Payload Capacity keeps using the flight's base capacity_kg
// (from the Flights catalog) shown in fdFlightInfo, and PAYLOAD (KG) stays
// user-entered per flight/date.
function fdOnAcTypeChange() {
    fdMarkDirty();
    const sel = document.getElementById('fdAcType');
    const opt = sel.options[sel.selectedIndex];
    if (!opt || !opt.value) return;
    const info = document.getElementById('fdFlightInfo');
    if (info) {
        info.dataset.acLabel = (opt.dataset.iata ? 'Aircraft Code: <b>' + opt.dataset.iata + '</b> &nbsp;|&nbsp; ' : '')
            + 'Aircraft: <b>' + (opt.dataset.fullName || opt.value) + '</b>';
        fdRenderFlightInfo();
    }
}

function fdOnStationChange(el) {
    fdGuardedFieldChange(el, () => { fdLoadFlights(); });
}

async function fdLoadFlights() {
    const station = document.getElementById('fdStation')?.value || 'LIM';
    try {
        const res = await apiFetch('/flights/flights_api.php?station=' + encodeURIComponent(station));
        const flights = res.success ? res.data : [];
        window.fdFlightsCache = flights;
        const sel = document.getElementById('fdFlight');
        if (!sel) return;
        sel.innerHTML = '<option value="">— Select —</option>';
        flights.forEach(f => {
            const o = document.createElement('option');
            o.value = f.flt_number;
            o.textContent = f.flt_number + '  ' + f.flt_orig + '→' + f.flt_dest;
            o.dataset.orig     = f.flt_orig || '';
            o.dataset.dest     = f.flt_dest || '';
            o.dataset.aircraft = f.aircraft || '';
            o.dataset.capacity = f.capacity_kg || '';
            sel.appendChild(o);
        });
        sel.dataset.fdPrev = '';
        document.getElementById('fdFormWrap').style.display = 'none';
        document.getElementById('fdFlightInfo').textContent = '';
    } catch(e) { console.warn('fdLoadFlights error:', e); }
}

function fdOnFlightChange(el) {
    fdGuardedFieldChange(el, () => {
        fdRenderFlightInfo();
        fdMaybeAutoLoad();
    });
}

function fdRenderFlightInfo() {
    const sel  = document.getElementById('fdFlight');
    const opt  = sel.options[sel.selectedIndex];
    const info = document.getElementById('fdFlightInfo');
    if (!opt || !opt.value) { info.textContent = ''; info.dataset.acLabel = ''; return; }
    info.innerHTML = (opt.dataset.orig || '—') + ' &rarr; ' + (opt.dataset.dest || '—')
        + ' &nbsp;|&nbsp; Aircraft (catalog): <b>' + (opt.dataset.aircraft || '—') + '</b>'
        + ' &nbsp;|&nbsp; Base Payload Capacity: <b>' + (opt.dataset.capacity ? fmtNum(opt.dataset.capacity) + ' kg' : '—') + '</b>'
        + (info.dataset.acLabel ? ' &nbsp;|&nbsp; ' + info.dataset.acLabel : '');
}

function fdOnDateChange(el) {
    fdGuardedFieldChange(el, () => { fdMaybeAutoLoad(); });
}

// ── Auto-load: fires as soon as Station + Flight + Flight Date all have
// valid values, regardless of which of the three was the last one changed. ──
function fdMaybeAutoLoad() {
    const station = document.getElementById('fdStation')?.value || '';
    const flight  = document.getElementById('fdFlight')?.value  || '';
    const date    = document.getElementById('fdDate')?.value    || '';
    if (station && flight && date) fdLoad();
}

// Public entry point (bound to the "Refresh" button and to auto-load).
// Guards against discarding unsaved changes before switching flights.
function fdLoad() {
    if (fdDirty) {
        fdConfirmUnsavedThen(() => fdLoadInner());
        return;
    }
    fdLoadInner();
}

async function fdLoadInner() {
    const flt     = document.getElementById('fdFlight')?.value  || '';
    const date    = document.getElementById('fdDate')?.value    || '';
    const station = document.getElementById('fdStation')?.value || 'LIM';
    const status  = document.getElementById('fdStatus');
    const wrap    = document.getElementById('fdFormWrap');

    if (!flt || !date) {
        status.style.color = 'var(--delta-red)';
        status.textContent = 'Select station, flight and flight date.';
        return;
    }

    status.style.color = 'var(--text-mid)';
    status.textContent = 'Loading flight data...';
    wrap.style.display = 'none';

    ['fdShipNumber','fdAcType','fdPayload','fdPaxOb','fdNrPax','fdBagsBulk','fdFuel','fdDepTime','fdArrTime'].forEach(id => {
        const el = document.getElementById(id);
        if (el) el.value = '';
    });
    document.getElementById('fdMeta').textContent = '';

    try {
        const res = await apiFetch('/flights/flight_operations_api.php?action=get_ops'
            + '&flt_number=' + encodeURIComponent(flt)
            + '&flt_date='   + encodeURIComponent(date)
            + '&station='    + encodeURIComponent(station));

        if (!res.success) {
            status.style.color = 'var(--delta-red)';
            status.textContent = res.message || 'Error loading flight.';
            return;
        }

        const sel = document.getElementById('fdFlight');
        const opt = sel.options[sel.selectedIndex];
        const d   = res.data;

        if (d) {
            document.getElementById('fdShipNumber').value = d.ship_number   || '';
            document.getElementById('fdAcType').value      = d.aircraft_type || (opt?.dataset.aircraft || '');
            document.getElementById('fdPayload').value     = d.payload_kg    ?? '';
            document.getElementById('fdPaxOb').value        = d.pax_ob        ?? '';
            document.getElementById('fdNrPax').value        = d.nr_pax_ob     ?? '';
            document.getElementById('fdBagsBulk').value     = d.bags_in_bulk  ?? '';
            document.getElementById('fdFuel').value         = d.fuel_lbs      ?? '';
            document.getElementById('fdDepTime').value      = d.dep_time ? String(d.dep_time).substring(0,5) : '';
            document.getElementById('fdArrTime').value      = d.arr_time ? String(d.arr_time).substring(0,5) : '';
            if (d.updated_by && d.updated_at) {
                document.getElementById('fdMeta').textContent = 'Last updated: ' + d.updated_by + ' — ' + d.updated_at;
            }
        } else if (opt) {
            // No record yet: pre-fill aircraft type with the catalog's default for this flight
            document.getElementById('fdAcType').value = opt.dataset.aircraft || '';
        }
        fdOnAcTypeChange(); // refresh the Aircraft Code/Model summary line for the loaded value
        fdRenderFlightInfo();

        wrap.style.display = 'block';
        status.textContent = '';
        fdDirty = false; // freshly loaded data is, by definition, not dirty
    } catch(e) {
        status.style.color = 'var(--delta-red)';
        status.textContent = 'Error loading flight data.';
    }
}

// Persists Flight Operational Data to flight_operations (single source of
// truth also read by Booking, Pre-Manifest, ULD Builder, Post-Flight
// Reports, Flight Summary and Departure Notice via the same endpoint).
// Returns true on success, false otherwise, so callers (e.g. the unsaved-
// changes dialog) know whether it's safe to proceed.
async function fdSave() {
    const flt     = document.getElementById('fdFlight')?.value  || '';
    const date    = document.getElementById('fdDate')?.value    || '';
    const station = document.getElementById('fdStation')?.value || 'LIM';
    const status  = document.getElementById('fdStatus');
    if (!flt || !date) return false;

    const val = id => (document.getElementById(id)?.value || '').trim();
    const body = {
        flt_number:    flt,
        flt_date:      date,
        station:       station,
        ship_number:   val('fdShipNumber') || null,
        aircraft_type: val('fdAcType')     || null,
        payload_kg:    val('fdPayload')    || null,
        pax_ob:        val('fdPaxOb')      || null,
        nr_pax_ob:     val('fdNrPax')      || null,
        bags_in_bulk:  val('fdBagsBulk')   || null,
        fuel_lbs:      val('fdFuel')       || null,
        dep_time:      val('fdDepTime')    || null,
        arr_time:      val('fdArrTime')    || null,
        user: currentUser?.email || 'agent',
    };

    const btn = document.getElementById('fdSaveBtn');
    if (btn) btn.disabled = true;
    status.style.color = 'var(--text-mid)';
    status.textContent = 'Saving...';

    try {
        const res = await apiFetch('/flights/flight_operations_api.php', {
            method: 'POST', body: JSON.stringify(body)
        });
        if (res.success) {
            status.style.color = '#1E7E34';
            status.textContent = 'Saved.';
            if (res.data && res.data.updated_by && res.data.updated_at) {
                document.getElementById('fdMeta').textContent = 'Last updated: ' + res.data.updated_by + ' — ' + res.data.updated_at;
            }
            fdDirty = false;
            return true;
        } else {
            status.style.color = 'var(--delta-red)';
            status.textContent = res.message || 'Error saving.';
            return false;
        }
    } catch(e) {
        status.style.color = 'var(--delta-red)';
        status.textContent = 'Error saving flight operational data.';
        return false;
    } finally {
        if (btn) btn.disabled = false;
    }
}

function postFlightInit() {
    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2L15 22l-4-9-9-4 20-7z"/></svg>
            <div class="module-title">Post-Flight Reports</div>
        </div>

        <!-- ── Flight selector bar + KPI indicators ── -->
        <div style="background:#fff;border:1px solid var(--border);border-radius:8px;
             padding:12px 16px;margin-bottom:12px;display:flex;align-items:flex-end;
             gap:12px;flex-wrap:wrap;box-shadow:0 1px 4px rgba(0,0,0,.06);">
            <div class="awb-field" style="margin:0;">
                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">ESTACIÓN</label>
                <select id="pfStation" class="awb-input" style="width:80px;" onchange="pfLoadFlights()"></select>
            </div>
            <div class="awb-field" style="margin:0;">
                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">VUELO</label>
                <select id="pfFlight" class="awb-input" style="width:180px;" onchange="pfMaybeAutoLoad()">
                    <option value="">— Seleccionar —</option>
                </select>
            </div>
            <div class="awb-field" style="margin:0;">
                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">FECHA</label>
                <input type="date" id="pfDate" class="awb-input" style="width:150px;" onchange="pfMaybeAutoLoad()">
            </div>
            <div class="awb-field" style="margin:0;">
                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">Nº MANIFIESTO</label>
                <input type="text" id="pfManifest" class="awb-input" placeholder="2026-######"
                    style="width:140px;font-weight:700;text-align:center;" readonly>
            </div>
            <!-- DEP / ARR times -->
            <div class="awb-field" style="margin:0;">
                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">DEP TIME</label>
                <input type="time" id="pfDepTime" class="awb-input"
                    style="width:100px;background:#FFF9E6;border-color:#F59E0B;"
                    onchange="pfSaveFlightTimes()">
            </div>
            <div class="awb-field" style="margin:0;">
                <label style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);">ARR DEST (LOCAL)</label>
                <div style="display:flex;align-items:center;gap:4px;">
                    <input type="time" id="pfArrTime" class="awb-input"
                        style="width:100px;background:#FFF9E6;border-color:#F59E0B;"
                        onchange="pfSaveFlightTimes()">
                    <span id="pfArrDay" style="font-size:10px;color:#F59E0B;font-weight:700;white-space:nowrap;"></span>
                </div>
            </div>
            <!-- KPI indicators pushed to the right -->
            <div style="margin-left:auto;display:flex;gap:2px;align-items:stretch;" id="pfKpiBar" style="display:none;">
                <div style="text-align:center;padding:6px 14px;border:1px solid var(--border);border-radius:6px 0 0 6px;background:#F8FAFC;min-width:100px;">
                    <div style="font-size:9px;font-weight:700;text-transform:uppercase;color:var(--text-mid);margin-bottom:2px;">AWBS EMBARCADAS</div>
                    <div id="pfBadgeLoaded" style="font-size:20px;font-weight:900;color:var(--delta-navy);">—</div>
                </div>
                <div style="text-align:center;padding:6px 14px;border:1px solid var(--border);border-top:1px solid var(--border);background:#F8FAFC;min-width:100px;">
                    <div style="font-size:9px;font-weight:700;text-transform:uppercase;color:var(--text-mid);margin-bottom:2px;">NO EMBARCADAS</div>
                    <div id="pfBadgeOffloaded" style="font-size:20px;font-weight:900;color:var(--delta-red);">—</div>
                </div>
                <div style="text-align:center;padding:6px 18px;border:1px solid var(--border);border-radius:0 6px 6px 0;background:#002244;min-width:120px;">
                    <div style="font-size:9px;font-weight:700;text-transform:uppercase;color:#94A3B8;margin-bottom:2px;">TOTAL KG</div>
                    <div id="pfBadgeKg" style="font-size:20px;font-weight:900;color:#fff;">—</div>
                </div>
            </div>
        </div>

        <!-- ── Datos operativos del vuelo (editable — se guardan en la base
             de datos y se sincronizan con los campos correspondientes del
             TAB Flight Summary) ── -->
        <div id="pfOpsBar" style="display:none;background:#F8FAFC;border:1px solid var(--border);
             border-radius:8px;padding:10px 16px;margin-bottom:12px;">
            <div style="font-size:11px;font-weight:700;text-transform:uppercase;color:var(--text-mid);
                 letter-spacing:.06em;margin-bottom:8px;display:flex;align-items:center;gap:6px;">
                DATOS OPERATIVOS DEL VUELO
            </div>
            <div style="display:flex;gap:10px;flex-wrap:wrap;align-items:flex-end;">
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:11px;">SHIP #</label>
                    <input type="text" id="pfShipNumber" class="awb-input" placeholder="—"
                        style="width:80px;text-transform:uppercase;" onchange="pfSaveOpsBarField('ship_number', this.value)">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:11px;">TIPO A/C</label>
                    <input type="text" id="pfAcTypeFs" class="awb-input" placeholder="—" style="width:110px;"
                        onchange="pfSaveOpsBarField('aircraft_type', this.value)">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:11px;">PAYLOAD (KG)</label>
                    <input type="number" id="pfPayloadKg" class="awb-input" placeholder="—" style="width:100px;"
                        onchange="pfSaveOpsBarField('payload_kg', this.value)">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:11px;">PAX O/B</label>
                    <input type="number" id="pfPaxOb" class="awb-input" placeholder="—" style="width:75px;"
                        onchange="pfSaveOpsBarField('pax_ob', this.value)">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:11px;">NR PAX O/B</label>
                    <input type="number" id="pfNrPax" class="awb-input" placeholder="—" style="width:75px;"
                        onchange="pfSaveOpsBarField('nr_pax_ob', this.value)">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:11px;">BAGS IN BULK</label>
                    <input type="number" id="pfBagsInBulk" class="awb-input" placeholder="—" style="width:75px;"
                        onchange="pfSaveOpsBarField('bags_in_bulk', this.value)">
                </div>
                <div class="awb-field" style="margin:0;">
                    <label style="font-size:11px;">FUEL (LBS)</label>
                    <input type="number" id="pfFuel" class="awb-input" placeholder="—" style="width:85px;"
                        onchange="pfSaveOpsBarField('fuel_lbs', this.value)">
                </div>
            </div>
        </div>

        <div id="pfStatus" style="min-height:16px;font-size:13px;margin-bottom:8px;"></div>

        <!-- ── Tabs ── -->
        <div id="pfTabs" style="display:none;">
            <div style="display:flex;gap:0;margin-bottom:0;border-bottom:2px solid var(--delta-navy);">
                <button id="pfTabDep" onclick="pfSwitchTab('departure')"
                    style="padding:8px 24px;font-size:12px;font-weight:700;border:1px solid var(--delta-navy);
                    border-bottom:none;border-radius:6px 6px 0 0;cursor:pointer;background:var(--delta-navy);
                    color:#fff;">Departure Notice</button>
                <button id="pfTabSum" onclick="pfSwitchTab('summary')"
                    style="padding:8px 24px;font-size:12px;font-weight:700;border:1px solid var(--border);
                    border-bottom:2px solid var(--delta-navy);border-radius:6px 6px 0 0;cursor:pointer;
                    background:#F8FAFC;color:var(--text-mid);">Flight Summary</button>
            </div>

            <!-- ── DEPARTURE NOTICE TAB ── -->
            <div id="pfTabContentDep" style="border:1px solid var(--border);border-top:none;
                 border-radius:0 0 8px 8px;padding:16px;background:#fff;">
                <div style="font-size:12px;color:var(--text-mid);margin-bottom:14px;">
                    Email a clientes informando que su carga fue embarcada. Se envía una notificación por agencia de carga.
                </div>
                <!-- Departure Notice table - populated by pfRenderDepartureTab() -->
                <div id="pfDepTableWrap"></div>
            </div>

            <!-- ── FLIGHT SUMMARY TAB ── -->
            <div id="pfTabContentSum" style="display:none;border:1px solid var(--border);border-top:none;
                 border-radius:0 0 8px 8px;padding:16px;background:#F8FAFC;">

                <!-- ── Flight title ── -->
                <div id="pfFsTitle" style="text-align:center;font-size:18px;font-weight:700;
                     color:var(--delta-navy);margin-bottom:14px;letter-spacing:.02em;"></div>

                <!-- ── Toolbar ── -->
                <div style="display:flex;gap:8px;align-items:center;margin-bottom:14px;flex-wrap:wrap;">
                    <button class="btn-secondary" onclick="pfSendSummary()" style="font-size:12px;">&#9993; Send</button>
                    <div id="pfMsgFs" style="margin-left:8px;font-size:12px;font-weight:600;align-self:center;"></div>
                </div>

                <!-- ═══════════════════════════════════════════════════
                     ULD SUMMARY · BULK SUMMARY · PAYLOAD/VOLUME/POSITIONS
                     (diseño de referencia — Rediseño Flight Summary)
                     ═══════════════════════════════════════════════════ -->
                <div id="pfFsDashboardRow" style="display:grid;grid-template-columns:1fr 1fr 1.4fr;gap:12px;margin-bottom:12px;align-items:start;">

                    <!-- ULD SUMMARY -->
                    <div class="fs-panel">
                        <div class="fs-panel-header">
                            <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>
                            ULD SUMMARY
                        </div>
                        <table class="fs-summary-table">
                            <thead><tr><th>DESCRIPTION</th><th style="text-align:center;">ULD DETAIL</th><th style="text-align:right;">QTY / WEIGHT (KG)</th></tr></thead>
                            <tbody id="pfUldSummaryTable"><tr><td colspan="3" style="color:var(--text-light);padding:14px;">&#8212;</td></tr></tbody>
                        </table>
                    </div>

                    <!-- BULK SUMMARY -->
                    <div class="fs-panel">
                        <div class="fs-panel-header">
                            <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="7" width="18" height="13" rx="1"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
                            BULK SUMMARY
                        </div>
                        <table class="fs-summary-table">
                            <thead><tr><th>DESCRIPTION</th><th style="text-align:center;">DETAIL</th><th style="text-align:right;">QTY / WEIGHT (KG)</th></tr></thead>
                            <tbody id="pfBulkSummaryTable"><tr><td colspan="3" style="color:var(--text-light);padding:14px;">&#8212;</td></tr></tbody>
                        </table>
                    </div>

                    <!-- Right column: Payload/Volume/Positions + PAX/Revenue -->
                    <div style="display:flex;flex-direction:column;gap:12px;">

                        <!-- PAYLOAD / VOLUME / POSICIONES -->
                        <div class="fs-panel" style="padding:14px;">
                            <div style="display:grid;grid-template-columns:88px 1fr 1fr 1.7fr;gap:8px 10px;align-items:center;">
                                <div></div>
                                <div class="fs-col-lbl">Payload (KG)</div>
                                <div class="fs-col-lbl">Cargo Wgt</div>
                                <div></div>

                                <div class="fs-row-lbl">PAYLOAD</div>
                                <input type="text" id="pfFsPayload" class="awb-input fs-panel-input"
                                    onchange="pfSaveFsField('payload_kg',this.value);pfRecalcAllKpis();const opsEl=document.getElementById('pfPayloadKg');if(opsEl)opsEl.value=this.value;">
                                <input type="text" id="pfFsCargoWgt" class="awb-input fs-panel-input" readonly style="background:#F5F5F5;">
                                <div class="fs-pct-bar">
                                    <span id="pfBarPayloadPct" class="fs-pct-num">&#8212;</span>
                                    <div class="fs-bar-track"><div id="pfBarPayloadFill" class="fs-bar-fill" style="background:#C8102E;"></div></div>
                                </div>

                                <div class="fs-row-lbl">VOLUME M3</div>
                                <input type="text" id="pfFsVolumeTotal" class="awb-input fs-panel-input" placeholder="Total m&sup3;"
                                    onchange="pfSaveFsField('volume_available_m3',this.value);pfRecalcAllKpis();">
                                <input type="text" id="pfFsVolumeUsed" class="awb-input fs-panel-input" placeholder="Used m&sup3;"
                                    onchange="pfSaveFsField('volume_used_m3',this.value);pfRecalcAllKpis();">
                                <div class="fs-pct-bar">
                                    <span id="pfBarVolumePct" class="fs-pct-num">&#8212;</span>
                                    <div class="fs-bar-track"><div id="pfBarVolumeFill" class="fs-bar-fill" style="background:#D6336C;"></div></div>
                                </div>

                                <div class="fs-row-lbl">POSICIONES</div>
                                <input type="number" id="pfFsTtlPos" class="awb-input fs-panel-input" placeholder="Available"
                                    onchange="pfSaveTtlPos(this.value);pfRecalcAllKpis();">
                                <input type="text" id="pfFsPosUsed" class="awb-input fs-panel-input" readonly style="background:#F5F5F5;">
                                <div class="fs-pct-bar">
                                    <span id="pfBarPosPct" class="fs-pct-num">&#8212;</span>
                                    <div class="fs-bar-track"><div id="pfBarPosFill" class="fs-bar-fill" style="background:#D6336C;"></div></div>
                                </div>
                            </div>
                        </div>

                        <!-- PAX O/B + REVENUE -->
                        <div class="fs-panel" style="padding:14px;display:flex;align-items:center;gap:18px;flex-wrap:wrap;">
                            <div class="fs-row-lbl" style="flex-shrink:0;">PAX O/B</div>
                            <div>
                                <div class="fs-col-lbl" style="margin-bottom:3px;">PAX</div>
                                <input type="text" id="pfFsPaxOb" class="awb-input fs-panel-input" readonly style="background:#F5F5F5;width:80px;">
                            </div>
                            <div>
                                <div class="fs-col-lbl" style="margin-bottom:3px;">NR PAX</div>
                                <input type="text" id="pfFsNrPax" class="awb-input fs-panel-input" readonly style="background:#F5F5F5;width:80px;">
                            </div>
                            <div style="margin-left:auto;border:1.5px solid var(--delta-navy);border-radius:6px;padding:8px 14px;min-width:180px;">
                                <div class="fs-col-lbl" style="margin-bottom:3px;">REVENUE</div>
                                <div style="display:flex;align-items:center;gap:4px;">
                                    <input type="text" id="pfFsRevenue" class="awb-input fs-panel-input" style="flex:1;min-width:0;color:#0F6E56;font-weight:700;border:none;padding:0;height:auto;font-size:15px;"
                                        onchange="pfSaveRevenue(this.value, true)">
                                    <button type="button" id="pfRevenueRecalcBtn" onclick="pfRecalcRevenue()" title="Recalculate from loaded AWBs"
                                        style="flex-shrink:0;width:22px;height:22px;border:1px solid var(--border);border-radius:4px;
                                        background:#fff;color:var(--text-mid);cursor:pointer;font-size:12px;line-height:1;padding:0;">&#8635;</button>
                                </div>
                                <div id="pfRevenueHint" style="font-size:9px;color:var(--text-light);"></div>
                                <div style="font-size:9px;color:var(--text-mid);margin-top:2px;">Lost: <span id="pfKpiRevLost" style="color:var(--delta-red);font-weight:700;">&#8212;</span></div>
                            </div>
                        </div>
                    </div>
                </div>

                <!-- ═══════════════════════════════════════════════════
                     4. LOAD PLAN IMAGE — a todo el ancho, protagonismo,
                        click para Lightbox/Zoom (Reingeniería FS #4)
                     ═══════════════════════════════════════════════════ -->
                <div style="background:#fff;border:1px solid var(--border);border-radius:8px;overflow:hidden;">
                    <div style="background:var(--delta-navy);color:#fff;padding:8px 14px;
                         display:flex;align-items:center;justify-content:space-between;">
                        <span style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;">LOAD PLAN IMAGE</span>
                        <span id="pfLoadPlanStatusBadge" style="font-size:10px;font-weight:700;padding:2px 8px;border-radius:10px;background:#FEF9E7;color:#7C5A00;white-space:nowrap;">&#128993; Under Development</span>
                    </div>
                    <div style="padding:14px;">
                        <div id="pfLoadPlanDropzone" tabindex="0" onclick="pfLoadPlanImageClick(event)"
                            style="border:2px dashed var(--border);border-radius:6px;min-height:140px;
                            display:flex;align-items:center;justify-content:center;transition:border-color .15s;outline:none;">
                        </div>
                        <div id="pfLoadPlanActions" style="display:none;gap:8px;margin-top:10px;">
                            <button id="pfLoadPlanAnalyzeBtn" class="btn-secondary" onclick="pfAnalyzeLoadPlan()" style="font-size:12px;">
                                &#10024; Analyze Image
                            </button>
                            <button class="btn-secondary" onclick="pfRemoveLoadPlanImage()" style="font-size:12px;color:var(--delta-red);border-color:var(--delta-red);">
                                &#128465; Remove
                            </button>
                        </div>
                        <div id="pfLoadPlanMsg" style="font-size:11px;font-weight:600;margin-top:6px;min-height:14px;"></div>
                    </div>
                </div>

            </div><!-- /pfTabContentSum -->
        </div>
    `;

    const t = new Date();
    const today = t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0');
    document.getElementById('pfDate').value = today;
    populateStationSelect('pfStation').then(() => pfLoadFlights());
    pfCurrentTab = 'departure';
}

function pfSwitchTab(tab) {
    pfCurrentTab = tab;
    const isDep = tab === 'departure';
    document.getElementById('pfTabContentDep').style.display = isDep ? 'block' : 'none';
    document.getElementById('pfTabContentSum').style.display = isDep ? 'none' : 'block';
    document.getElementById('pfTabDep').style.background    = isDep ? 'var(--delta-navy)' : '#F8FAFC';
    document.getElementById('pfTabDep').style.color         = isDep ? '#fff' : 'var(--text-mid)';
    document.getElementById('pfTabDep').style.borderBottom  = isDep ? 'none' : '2px solid var(--delta-navy)';
    document.getElementById('pfTabSum').style.background    = isDep ? '#F8FAFC' : 'var(--delta-navy)';
    document.getElementById('pfTabSum').style.color         = isDep ? 'var(--text-mid)' : '#fff';
    document.getElementById('pfTabSum').style.borderBottom  = isDep ? '2px solid var(--delta-navy)' : 'none';
    if (!isDep) {
        pfPopulateSummaryTab();
    }
}

let pfCurrentTab = 'departure';

function pfAddNoRideRow() {
    const tbody = document.getElementById('pfNoRideBody');
    if (!tbody) return;
    const inp = 'border:1px solid var(--border);width:100%;padding:3px 4px;font-size:10px;border-radius:3px;';
    const tr = document.createElement('tr');
    tr.innerHTML = ['AWB','ULD','PRODUCT','KG NET','GROSS WGT','REV LOST','REASON'].map(function(f, i) {
        return '<td style="border:1px solid #ddd;padding:2px;"><input type="text" style="' + inp + '" placeholder="' + f + '..."></td>';
    }).join('');
    tbody.appendChild(tr);
}

// ── Delta Cargo Tracking: URL de seguimiento por AWB específica.
// El sistema guarda el AWB como ###-######## (con guión); la URL de
// Delta Cargo espera el número sin guión, ej. "006-45199582" → "00645199582". ──
function pfTrackingUrl(awb) {
    const digits = String(awb || '').replace(/[^0-9]/g, '');
    return 'https://www.deltacargo.com/Cargo/trackShipment?awbNumber=' + digits + '&timeZoneOffset=300';
}

function pfRenderDepartureTab(data) {
    const wrap = document.getElementById('pfDepTableWrap');
    if (!wrap) return;

    // API returns data in res.loaded array
    const loaded = data.loaded || data.shipments || [];
    const fltDest = data.flt_dest || (loaded.length ? (loaded[0].flt_dest || loaded[0].routing_dest || '') : '');

    if (!loaded.length) {
        wrap.innerHTML = '<div style="padding:20px;text-align:center;color:var(--text-light);">No shipments loaded for this flight.</div>';
        return;
    }

    // Group by agent
    const byAgent = {};
    loaded.forEach(function(s) {
        const key = s.agent_name || '\u2014';
        if (!byAgent[key]) byAgent[key] = [];
        byAgent[key].push(s);
    });

    const TH = 'padding:8px 10px;background:var(--delta-navy);color:#fff;font-size:11px;text-transform:uppercase;font-weight:700;border:1px solid #001422;white-space:nowrap;letter-spacing:.02em;';
    const TD = 'padding:9px 10px;border-bottom:1px solid #E5E7EB;font-size:13px;vertical-align:middle;';

    // ── Rediseño Departure Notice (referencia visual adjunta): agrupado
    // por Freight Forwarder, una fila por AWB. El nombre del agente + el
    // ícono de envío solo aparecen en la primera fila del grupo (#5); el
    // botón Track queda junto al número de guía (#4); el badge de estado
    // se actualiza por agente, no por fila individual (#3). ──
    let rows = '';
    Object.keys(byAgent).sort().forEach(function(agent, gi) {
        const items = byAgent[agent];
        const already = window._pfDnSentAgents && window._pfDnSentAgents.has(agent);
        const bg = gi % 2 === 0 ? '#fff' : '#F8FAFC';

        items.forEach(function(s, si) {
            const awb   = esc(s.awb || s.shipment_number);
            const pcs   = s.pieces || s.actual_pieces || 0;
            const gross = s.gross_weight || 0;
            const sDest = esc(s.flt_dest || fltDest);
            const isFirst = si === 0;

            rows += `<tr style="background:${bg};">
                <td style="${TD}white-space:nowrap;">
                    ${isFirst ? `<button onclick="pfSendAgent('${esc(agent).replace(/'/g,"\\'")}')"
                        title="Send Departure Notice to ${esc(agent)}"
                        style="background:none;border:none;cursor:pointer;font-size:15px;margin-right:8px;vertical-align:middle;padding:0;">&#128231;</button>
                        <strong style="color:var(--delta-navy);">${esc(agent)}</strong>` : ''}
                </td>
                <td style="${TD}white-space:nowrap;">
                    <span style="font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.02em;font-weight:700;color:var(--delta-navy);margin-right:8px;">${awb}</span>
                    <a href="${pfTrackingUrl(awb)}" target="_blank" rel="noopener" title="View on Delta Cargo Tracking"
                        style="display:inline-flex;align-items:center;gap:4px;background:#EFF6FF;border:1px solid #BFDBFE;
                        border-radius:4px;padding:2px 9px;font-size:11px;color:var(--delta-navy);text-decoration:none;white-space:nowrap;">&#128269; Track</a>
                </td>
                <td style="${TD}text-align:right;">${fmtWgt0(pcs)}</td>
                <td style="${TD}text-align:right;">${fmtWgt0(gross)}</td>
                <td style="${TD}text-align:center;">${sDest}</td>
                <td style="${TD}text-align:center;">
                    <span class="pf-dn-status" data-agent="${esc(agent)}" style="display:inline-block;padding:3px 10px;border-radius:10px;font-size:11px;font-weight:700;white-space:nowrap;
                        ${already ? 'background:#ECFDF5;color:#065F46;border:1px solid #6EE7B7;' : 'background:#FEF9E7;color:#92650A;border:1px solid #FDE68A;'}">
                        ${already ? '\u{1F7E2} Notification Sent' : '\u{1F7E1} Pending'}
                    </span>
                </td>
            </tr>`;
        });
    });

    wrap.innerHTML = `<table style="width:100%;border-collapse:collapse;">
        <thead><tr>
            <th style="${TH}">FREIGHT FORWARDER</th>
            <th style="${TH}">AWB</th>
            <th style="${TH}text-align:right;">PCS</th>
            <th style="${TH}text-align:right;">GROSS WEIGHT</th>
            <th style="${TH}">DESTINATION</th>
            <th style="${TH}">STATUS</th>
        </tr></thead>
        <tbody>${rows}</tbody>
    </table>`;
}

function pfPopulateSummaryTab() {
    pfRenderLoadPlanPreview();
    const data      = window._pfData || {};
    const loaded    = data.loaded    || [];
    const offloaded = data.offloaded || [];
    const flt       = document.getElementById('pfFlight')?.value.split(' ')[0] || '';
    const date      = document.getElementById('pfDate')?.value    || '';
    const sta       = document.getElementById('pfStation')?.value || 'LIM';
    const dest      = data.flt_dest || '';

    // ── Title ────────────────────────────────────────────────────
    const dateLabel = date ? (function(){
        const d = new Date(date+'T12:00:00');
        const m = ['ene','feb','mar','abr','may','jun','jul','ago','sep','oct','nov','dic'];
        return m[d.getMonth()] + ', ' + d.getDate();
    })() : '';
    const titleEl = document.getElementById('pfFsTitle');
    if (titleEl) titleEl.textContent = 'Flight Summary  ' + flt + ' | ' + sta + '->' + dest + ' | ' + dateLabel;

    // ── CAPACITY ─────────────────────────────────────────────────
    const kgOnboard  = loaded.reduce(function(s,r){ return s+(parseFloat(r.gross_weight)||0); }, 0);
    const payloadVal = document.getElementById('pfPayloadKg')?.value || '';

    const fsPayloadEl = document.getElementById('pfFsPayload');
    if (fsPayloadEl && !fsPayloadEl.value) fsPayloadEl.value = payloadVal;

    // ── ULD INFORMATION: live per-type breakdown (mismo criterio que Pre-Manifest) ──
    pfLoadUldTypeSummary(flt, date, sta);

    // ── Restore saved ops fields (ttl_positions, volumen) + Revenue override ──
    pfRestoreFsFields(flt, date, sta);

    // ── Revenue perdida (No Ride): se sigue calculando en segundo plano
    // para alimentar el KPI de Revenue, pero la tabla editable ya no se
    // muestra en Flight Summary (Reingeniería FS: se elimina del tablero
    // ejecutivo; el detalle puede consultarse en otro módulo/reporte). ──
    pfComputeRevenueLostTotal(flt, date, sta);

    // ── Revenue: auto-sum de Agent Freight de las AWBs embarcadas,
    //    salvo que el usuario ya haya fijado un valor manual (dirty).
    //    Se resetea aquí y pfRestoreFsFields la vuelve a marcar 'dirty'
    //    si había un override guardado para este vuelo/fecha. ──
    window._pfLoadedShipments    = loaded;
    window._pfOffloadedShipments = offloaded;
    window._pfRevenueDirty = false;
    pfComputeRevenue(loaded);

    // ── KPIs ─────────────────────────────────────────────────────
    pfUpdateKpis(kgOnboard, payloadVal, 0, offloaded, loaded);
}

// ── ULD's Information: agrupa por tipo (mismo concepto que uldRenderSummary
//    del módulo Pre-Manifest), separando CARGO / EMPTY / DAMAGED. ──
async function pfLoadUldTypeSummary(flt, date, sta) {
    const wrap = document.getElementById('pfUldSummaryTable');
    if (!wrap) return;
    if (!flt || !date) { wrap.innerHTML = '<tr><td colspan="3" style="color:var(--text-light);padding:14px;">\u2014</td></tr>'; return; }
    try {
        const params = new URLSearchParams({ flt_number: flt, flt_date: date, station: sta });
        const res = await apiFetch('/shipments/flight_ulds_api.php?' + params.toString());
        pfRenderUldTypeSummary((res && res.success && res.ulds) ? res.ulds : []);
    } catch(e) {
        wrap.innerHTML = '<tr><td colspan="3" style="color:var(--text-light);padding:14px;">\u2014</td></tr>';
    }
}

function pfRenderUldTypeSummary(ulds) {
    const wrap = document.getElementById('pfUldSummaryTable');
    if (!wrap) return;
    if (!ulds || !ulds.length) {
        wrap.innerHTML = '<tr><td colspan="3" style="color:var(--text-light);padding:14px;">No ULDs registered.</td></tr>';
        pfRenderBulkSummaryTable(0, 0, ulds);
        return;
    }
    const cargoTypes = {}, emptyTypes = {}, damagedTypes = {};
    let wgtCargo = 0, wgtEmpty = 0, wgtDamaged = 0, wgtBulk = 0, pcsBulk = 0;
    ulds.forEach(function(u) {
        const wgt = parseFloat(u.weight_kg) || 0;
        if (u.is_bulk) { wgtBulk += wgt; pcsBulk += parseInt(u.pieces) || 0; return; }
        const t = (u.uld_type || 'ULD').toUpperCase().substring(0, 3);
        if (u.uld_condition === 'EMPTY')        { emptyTypes[t]   = (emptyTypes[t]   || 0) + 1; wgtEmpty   += wgt; }
        else if (u.uld_condition === 'DAMAGED') { damagedTypes[t] = (damagedTypes[t] || 0) + 1; wgtDamaged += wgt; }
        else                                    { cargoTypes[t]   = (cargoTypes[t]   || 0) + 1; wgtCargo   += wgt; }
    });

    // "2 PAG + 1 PMC = 3" — mismo criterio de concatenación por tipo
    function sumOf(obj) { return Object.values(obj).reduce(function(a, b) { return a + b; }, 0); }
    function breakdown(obj) {
        const total = sumOf(obj);
        if (!total) return '';
        return Object.keys(obj).sort().map(function(t) { return obj[t] + ' ' + t; }).join(' + ') + ' = ' + total;
    }

    const totalQty = sumOf(cargoTypes) + sumOf(emptyTypes) + sumOf(damagedTypes);
    const totalWgt = wgtCargo + wgtEmpty + wgtDamaged;

    const ICON_ULD = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>';

    function fsRow(label, sub, qty, wgt, bg, color) {
        return '<tr>'
            + '<td><div style="display:flex;align-items:center;gap:8px;">'
            + '<span class="fs-summary-icon" style="background:' + bg + ';color:' + color + ';">' + ICON_ULD + '</span>'
            + '<div class="fs-summary-label">' + label + '</div></div></td>'
            + '<td style="text-align:center;">' + (sub ? '<span class="fs-summary-detail" style="color:' + color + ';">' + esc(sub) + '</span>' : '<span class="fs-summary-detail-empty">&#8212;</span>') + '</td>'
            + '<td style="text-align:right;">'
            + '<div style="font-weight:800;font-size:15px;color:' + color + ';line-height:1.2;">' + qty + '</div>'
            + '<div style="font-size:11px;font-weight:700;color:' + color + ';opacity:.8;">' + Math.round(wgt).toLocaleString('en-US') + ' Kg</div>'
            + '</td>'
            + '</tr>';
    }

    wrap.innerHTML =
        fsRow('Total ULD',  '',                       totalQty,            totalWgt,   '#EFF6FF', '#002244')
        + fsRow('With Cargo', breakdown(cargoTypes),   sumOf(cargoTypes),   wgtCargo,   '#EFF6FF', '#002244')
        + fsRow('Empty',      breakdown(emptyTypes),   sumOf(emptyTypes),   wgtEmpty,   '#F4F6F9', '#5B6472')
        + fsRow('Damaged',    breakdown(damagedTypes), sumOf(damagedTypes), wgtDamaged, '#FFF0F0', '#C8102E');

    pfRenderBulkSummaryTable(pcsBulk, wgtBulk, ulds);
}

// ── BULK SUMMARY: Bulk Cargo (piezas/peso en posiciones bulk) + Bulk
// Bags (equipaje en bulk, ya disponible en la barra operativa del vuelo).
// Sin datos disponibles → "—", nunca un 0 engañoso. ──
function pfRenderBulkSummaryTable(pcsBulk, wgtBulk, ulds) {
    const wrap = document.getElementById('pfBulkSummaryTable');
    if (!wrap) return;
    const bagsInBulkRaw = document.getElementById('pfBagsInBulk')?.value || '';
    const hasBulkCargo = (pcsBulk > 0) || (wgtBulk > 0);
    const hasBags = bagsInBulkRaw !== '' && bagsInBulkRaw !== '0';

    const ICON_BOX = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="3" y="7" width="18" height="13" rx="1"/><path d="M8 7V5a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>';
    const ICON_BAG = '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><rect x="4" y="8" width="16" height="13" rx="2"/><path d="M9 8V6a3 3 0 0 1 6 0v2"/></svg>';

    function fsRow(icon, label, qty, wgtLabel) {
        return '<tr>'
            + '<td><div style="display:flex;align-items:center;gap:8px;">'
            + '<span class="fs-summary-icon" style="background:#EFF6FF;color:#002244;">' + icon + '</span>'
            + '<div class="fs-summary-label">' + label + '</div></div></td>'
            + '<td style="text-align:center;"><span class="fs-summary-detail-empty">&#8212;</span></td>'
            + '<td style="text-align:right;">'
            + '<div style="font-weight:800;font-size:15px;color:#1A1F29;line-height:1.2;">' + qty + '</div>'
            + '<div style="font-size:11px;font-weight:700;color:#1A1F29;opacity:.8;">' + wgtLabel + '</div>'
            + '</td>'
            + '</tr>';
    }

    wrap.innerHTML =
        fsRow(ICON_BOX, 'Bulk Cargo', hasBulkCargo ? pcsBulk : '\u2014', hasBulkCargo ? Math.round(wgtBulk).toLocaleString('en-US') + ' Kg' : '\u2014')
        + fsRow(ICON_BAG, 'Bulk Bags', hasBags ? bagsInBulkRaw : '\u2014', '\u2014');
}

async function pfRestoreFsFields(flt, date, sta) {
    try {
        const res = await apiFetch(
            '/flights/flight_operations_api.php?action=get_ops&flt_number=' + encodeURIComponent(flt) +
            '&flt_date=' + encodeURIComponent(date) + '&station=' + encodeURIComponent(sta)
        );
        if (res.success && res.data) {
            const d = res.data;
            // TTL Positions
            const posEl = document.getElementById('pfFsTtlPos');
            if (posEl && d.ttl_positions) {
                posEl.value = d.ttl_positions;
                posEl.style.background   = '#EFF6FF';
                posEl.style.borderColor  = 'var(--delta-sky)';
            }
            // Volume (Reingeniería Flight Summary — capacidad volumétrica)
            const volUsedEl  = document.getElementById('pfFsVolumeUsed');
            const volTotalEl = document.getElementById('pfFsVolumeTotal');
            if (volUsedEl  && d.volume_used_m3      != null && d.volume_used_m3      !== '') volUsedEl.value  = d.volume_used_m3;
            if (volTotalEl && d.volume_available_m3 != null && d.volume_available_m3 !== '') volTotalEl.value = d.volume_available_m3;
            // Revenue: si el usuario ya fijó un valor manual, respetarlo y no recalcular
            window._pfRevenueDirty = !!(d.revenue_manual == 1 || d.revenue_manual === true);
            if (window._pfRevenueDirty && d.revenue_override != null && d.revenue_override !== '') {
                const revEl = document.getElementById('pfFsRevenue');
                if (revEl) revEl.value = 'US$ ' + Math.round(parseFloat(d.revenue_override)).toLocaleString('en-US');
                pfSetRevenueHint(true);
            } else {
                window._pfRevenueDirty = false;
                pfComputeRevenue(window._pfLoadedShipments || []);
            }
            pfRecalcAllKpis();
        }
    } catch(e) {}
}

// ── Revenue perdida (No Ride) — cálculo en segundo plano, sin UI de
// tabla (Reingeniería Flight Summary: se elimina "NO RIDE INFORMATION"
// del tablero ejecutivo; este número sigue alimentando el KPI Revenue →
// Lost). pfAddNoRideRow/pfAddNoRideRowData quedan disponibles por si se
// implementa el detalle en otro módulo/reporte más adelante. ──
async function pfComputeRevenueLostTotal(flt, date, sta) {
    const lostEl = document.getElementById('pfKpiRevLost');
    if (!lostEl) return;
    try {
        const res = await apiFetch(
            '/shipments/no_ride_api.php?flt_number=' + encodeURIComponent(flt) +
            '&flt_date=' + encodeURIComponent(date) + '&station=' + encodeURIComponent(sta)
        );
        const saved = (res && res.success && Array.isArray(res.data)) ? res.data : [];
        const offloaded = (window._pfData && window._pfData.offloaded) || [];
        const savedAwbs = saved.map(function(r){ return r.awb; });

        let total = saved.reduce(function(s, r){ return s + (parseFloat(r.revenue_lost) || 0); }, 0);
        offloaded.forEach(function(s) {
            const awb = s.awb || s.shipment_number;
            if (savedAwbs.indexOf(awb) === -1) total += parseFloat(s.freight_revenue) || 0;
        });

        lostEl.textContent = 'US$ ' + Math.round(total).toLocaleString('en-US');
    } catch(e) {
        lostEl.textContent = 'US$ 0';
    }
}

// ── Revenue (CAPACITY panel): suma automática del Agent Freight
// (freight_revenue) de todas las AWBs embarcadas. Se recalcula cada vez
// que cambian los datos del vuelo, salvo que el usuario haya editado el
// valor manualmente (window._pfRevenueDirty) — ver pfSaveRevenue. ──
function pfComputeRevenue(loaded) {
    const total = (loaded || []).reduce(function(s, r) {
        return s + (parseFloat(r.freight_revenue) || 0);
    }, 0);
    const revEl = document.getElementById('pfFsRevenue');
    if (revEl) revEl.value = 'US$ ' + Math.round(total).toLocaleString('en-US');
    const kpiEl = document.getElementById('pfKpiRevenue');
    if (kpiEl) kpiEl.textContent = 'US$ ' + Math.round(total).toLocaleString('en-US');
    pfSetRevenueHint(false);
    return total;
}

function pfSetRevenueHint(isManual) {
    const hint = document.getElementById('pfRevenueHint');
    if (hint) hint.textContent = isManual ? 'Editado manualmente' : '';
}

// Guarda el valor de Revenue editado a mano. Queda "sticky": no se
// vuelve a sobrescribir automáticamente hasta que el usuario presione
// el botón de recalcular (pfRecalcRevenue).
async function pfSaveRevenue(rawValue, manual) {
    const flt  = document.getElementById('pfFlight')?.value.split(' ')[0] || '';
    const date = document.getElementById('pfDate')?.value    || '';
    const sta  = document.getElementById('pfStation')?.value || 'LIM';
    if (!flt || !date) return;

    const numeric = parseFloat(String(rawValue).replace(/[^0-9.-]/g, '')) || 0;
    window._pfRevenueDirty = !!manual;

    const revEl = document.getElementById('pfFsRevenue');
    if (revEl) revEl.value = 'US$ ' + Math.round(numeric).toLocaleString('en-US');
    const kpiEl = document.getElementById('pfKpiRevenue');
    if (kpiEl) kpiEl.textContent = 'US$ ' + Math.round(numeric).toLocaleString('en-US');
    pfSetRevenueHint(!!manual);

    try {
        await apiFetch('/flights/flight_operations_api.php', {
            method: 'POST',
            body: JSON.stringify({
                flt_number: flt, flt_date: date, station: sta,
                revenue_override: numeric,
                revenue_manual: manual ? 1 : 0,
                user: currentUser?.email || 'agent'
            })
        });
    } catch(e) {}
}

// Botón "↻" — descarta el override manual y vuelve a sumar Agent Freight
// de las AWBs embarcadas actuales.
function pfRecalcRevenue() {
    window._pfRevenueDirty = false;
    const total = pfComputeRevenue(window._pfLoadedShipments || []);
    pfSaveRevenue(total, false);
}

// ── Silueta de ocupación de carga (KPI DE CAPACIDAD DE CARGA): dibuja un
// fuselaje redondeado en el <canvas>, relleno de izquierda a derecha según
// el % de payload ocupado, y actualiza los textos superpuestos (%
// ocupado / % disponible). Se llama tras cada carga/actualización de KPIs
// y también al reabrir el TAB Flight Summary (para redibujar tras el
// resize de layout). ──
function pfDrawAircraftCanvas(pct) {
    const canvas = document.getElementById('pfAircraftCanvas');
    if (!canvas) return;
    const rect = canvas.getBoundingClientRect();
    const w = rect.width  || canvas.parentElement?.clientWidth || 300;
    const h = rect.height || 90;
    if (!w || !h) return;

    const dpr = window.devicePixelRatio || 1;
    canvas.width  = w * dpr;
    canvas.height = h * dpr;
    const ctx = canvas.getContext('2d');
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    ctx.scale(dpr, dpr);
    ctx.clearRect(0, 0, w, h);

    const p = Math.max(0, Math.min(100, parseFloat(pct) || 0)) / 100;

    // Fuselaje: cápsula redondeada (rectángulo con extremos semicirculares)
    const marginX = 14, marginY = 16;
    const fx = marginX, fy = marginY, fw = w - marginX * 2, fh = h - marginY * 2;
    const r = fh / 2;

    function fuselagePath() {
        ctx.beginPath();
        ctx.moveTo(fx + r, fy);
        ctx.lineTo(fx + fw - r, fy);
        ctx.arc(fx + fw - r, fy + r, r, -Math.PI / 2, Math.PI / 2);
        ctx.lineTo(fx + r, fy + fh);
        ctx.arc(fx + r, fy + r, r, Math.PI / 2, Math.PI * 1.5);
        ctx.closePath();
    }

    // Fondo (disponible)
    fuselagePath();
    ctx.fillStyle = '#E5E7EB';
    ctx.fill();

    // Relleno ocupado, recortado a la forma del fuselaje
    ctx.save();
    fuselagePath();
    ctx.clip();
    ctx.fillStyle = 'rgba(30,80,140,.75)';
    ctx.fillRect(fx, fy, fw * p, fh);
    ctx.restore();

    // Contorno
    fuselagePath();
    ctx.lineWidth = 1.5;
    ctx.strokeStyle = '#9AA3AF';
    ctx.stroke();

    const loadedEl = document.getElementById('pfAcPctLoaded');
    const freeEl   = document.getElementById('pfAcPctFree');
    if (loadedEl) loadedEl.textContent = Math.round(p * 100) + '%';
    if (freeEl)   freeEl.textContent   = Math.round((1 - p) * 100) + '%';
}

function pfUpdateKpis(kgOnboard, payloadVal, revenue, offloaded, loaded) {
    function pctColorFor(pct) { return pct >= 90 ? '#2ECC71' : pct >= 70 ? '#F59E0B' : '#C8102E'; }

    // ── PAYLOAD ──────────────────────────────────────────────────
    // Cargo Wgt se completa automáticamente (peso real embarcado) — el
    // usuario nunca lo tiene que ingresar a mano (Rediseño FS #2).
    const payload = parseFloat(payloadVal) || 0;
    const pctPayload = payload > 0 ? Math.min(100, Math.round((kgOnboard / payload) * 100)) : 0;
    window._pfLastPct = pctPayload;

    const cargoWgtEl = document.getElementById('pfFsCargoWgt');
    if (cargoWgtEl) cargoWgtEl.value = Math.round(kgOnboard).toLocaleString('en-US');
    pfSetBar('pfBarPayloadFill', 'pfBarPayloadPct', pctPayload, pctColorFor(pctPayload));

    // ── VOLUME (capacidad volumétrica) — sin fuente automática todavía:
    // ambos campos quedan editables y se guardan/recuperan por vuelo. ──
    const volUsed  = parseFloat(document.getElementById('pfFsVolumeUsed')?.value)  || 0;
    const volTotal = parseFloat(document.getElementById('pfFsVolumeTotal')?.value) || 0;
    const pctVolume = volTotal > 0 ? Math.min(100, Math.round((volUsed / volTotal) * 100)) : 0;
    pfSetBar('pfBarVolumeFill', 'pfBarVolumePct', pctVolume, '#D6336C');

    // ── POSICIONES: usadas = automático (AWBs/ULDs embarcados); disponibles
    // = editable (sin fuente automática confirmada todavía). ──
    const posAvail = parseFloat(document.getElementById('pfFsTtlPos')?.value) || 0;
    const posUsed  = loaded.length; // 1 posición ~ 1 AWB/ULD embarcado como aproximación operativa
    const pctPos   = posAvail > 0 ? Math.min(100, Math.round((posUsed / posAvail) * 100)) : 0;

    const posUsedEl = document.getElementById('pfFsPosUsed');
    if (posUsedEl) posUsedEl.value = posUsed;
    pfSetBar('pfBarPosFill', 'pfBarPosPct', pctPos, '#D6336C');

    // ── PAX O/B: copia de solo lectura del dato ya disponible en la barra
    // operativa del vuelo (pfPaxOb/pfNrPax) — nada que pedir al usuario. ──
    const paxObEl = document.getElementById('pfFsPaxOb');
    const nrPaxEl = document.getElementById('pfFsNrPax');
    if (paxObEl) paxObEl.value = document.getElementById('pfPaxOb')?.value || '\u2014';
    if (nrPaxEl) nrPaxEl.value = document.getElementById('pfNrPax')?.value || '\u2014';

    // Revenue (pfKpiRevenue) se fija en pfComputeRevenue/pfSaveRevenue;
    // Lost (pfKpiRevLost) se fija en pfComputeRevenueLostTotal — ambos se
    // calculan por separado y no deben resetearse aquí.
}

function pfSetBar(fillId, pctId, pct, color) {
    const fillEl = document.getElementById(fillId);
    const pctEl  = document.getElementById(pctId);
    if (fillEl) { fillEl.style.width = pct + '%'; fillEl.style.background = color; }
    if (pctEl)  pctEl.textContent = pct + '%';
}

// Recalcula los 3 KPI de capacidad (Payload/Volume/Positions) a partir de
// los valores actuales del DOM, sin necesidad de recargar el vuelo — se
// usa cuando el usuario edita Max Payload, Volume o Positions a mano.
function pfRecalcAllKpis() {
    const loaded    = window._pfLoadedShipments    || [];
    const offloaded = window._pfOffloadedShipments || [];
    const kgOnboard  = loaded.reduce(function(s,r){ return s+(parseFloat(r.gross_weight)||0); }, 0);
    const payloadVal = document.getElementById('pfFsPayload')?.value || document.getElementById('pfPayloadKg')?.value || '';
    pfUpdateKpis(kgOnboard, payloadVal, 0, offloaded, loaded);
}

async function pfSaveFsField(field, value) {
    const flt  = document.getElementById('pfFlight')?.value.split(' ')[0] || '';
    const date = document.getElementById('pfDate')?.value  || '';
    const sta  = document.getElementById('pfStation')?.value || 'LIM';
    if (!flt || !date) return;

    try {
        await apiFetch('/flights/flight_operations_api.php', {
            method: 'POST',
            body: JSON.stringify({
                flt_number: flt, flt_date: date, station: sta,
                [field]: value || null,
                user: currentUser?.email || 'agent'
            })
        });
    } catch(e) {}
}

// ── Datos Operativos del Vuelo (barra compartida arriba de los tabs):
// ahora son editables. Al cambiar cualquiera de estos campos se guarda
// en flight_operations (mismo mecanismo que pfSaveFsField) y, si el
// campo tiene un equivalente visible en el TAB Flight Summary, se
// sincroniza de inmediato sin necesidad de recargar el vuelo. ──
function pfSaveOpsBarField(field, value) {
    pfSaveFsField(field, value);

    if (field === 'payload_kg') {
        const fsPayloadEl = document.getElementById('pfFsPayload');
        if (fsPayloadEl) fsPayloadEl.value = value;
        if (typeof pfRecalcAllKpis === 'function') pfRecalcAllKpis();
    } else if (field === 'pax_ob') {
        const fsPaxEl = document.getElementById('pfFsPaxOb');
        if (fsPaxEl) fsPaxEl.value = value || '\u2014';
    } else if (field === 'nr_pax_ob') {
        const fsNrPaxEl = document.getElementById('pfFsNrPax');
        if (fsNrPaxEl) fsNrPaxEl.value = value || '\u2014';
    } else if (field === 'bags_in_bulk') {
        // El resumen Bulk Summary lee este mismo valor — se refresca
        // junto con el resto de la data de ULDs para mantenerlo consistente.
        const flt  = document.getElementById('pfFlight')?.value || '';
        const date = document.getElementById('pfDate')?.value   || '';
        const sta  = document.getElementById('pfStation')?.value || 'LIM';
        if (typeof pfLoadUldTypeSummary === 'function') pfLoadUldTypeSummary(flt, date, sta);
    }
}

async function pfSaveTtlPos(val) {
    const posEl = document.getElementById('pfFsTtlPos');
    if (posEl) {
        posEl.style.background  = val ? '#EFF6FF'        : '#FFF9E6';
        posEl.style.borderColor = val ? 'var(--delta-sky)': '#F59E0B';
    }
    pfSaveFsField('ttl_positions', val);
}

function pfAddNoRideRow() { pfAddNoRideRowData({saved:false}); }

function pfAddNoRideRowData(data) {
    const tbody = document.getElementById('pfNoRideBody');
    if (!tbody) return;
    const inp = 'border:1px solid var(--border);padding:3px 5px;font-size:11px;border-radius:3px;width:100%;';
    const tr  = document.createElement('tr');
    tr.dataset.nrId = data.id || '0';

    const netVal  = data.kgnet  ? parseFloat(data.kgnet ).toFixed(2) : '';
    const grsVal  = data.gross  ? parseFloat(data.gross ).toFixed(2) : '';
    const revVal  = data.rev    ? (String(data.rev).startsWith('US$') ? data.rev : 'US$ '+parseFloat(data.rev).toFixed(2)) : '';

    tr.innerHTML =
        '<td style="border:1px solid #ddd;padding:2px;min-width:130px;">'
        +'<input type="text" style="'+inp+'font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-weight:700;" value="'+esc(data.awb||'')+'"'
        +' placeholder="006-12345678" maxlength="14"'
        +' oninput="pfFmtAwbNoRide(this)" onchange="pfAutoFillNoRide(this)"></td>'
        +'<td style="border:1px solid #ddd;padding:2px;min-width:70px;">'
        +'<input type="text" style="'+inp+'" value="'+esc(data.uld||'')+'" placeholder="PMC..."'
        +' onchange="pfSaveNoRideRow(this.closest(\'tr\'))"></td>'
        +'<td style="border:1px solid #ddd;padding:2px;min-width:130px;">'
        +'<input type="text" style="'+inp+'" value="'+esc(data.prod||'')+'" placeholder="Producto..."'
        +' list="pfGoodsList" onchange="pfSaveNoRideRow(this.closest(\'tr\'))"></td>'
        +'<td style="border:1px solid #ddd;padding:2px;min-width:80px;">'
        +'<input type="text" style="'+inp+'text-align:right;" value="'+esc(netVal)+'" placeholder="0.00"'
        +' onchange="pfSaveNoRideRow(this.closest(\'tr\'))"></td>'
        +'<td style="border:1px solid #ddd;padding:2px;min-width:80px;">'
        +'<input type="text" style="'+inp+'text-align:right;" value="'+esc(grsVal)+'" placeholder="0.00"'
        +' onchange="pfSaveNoRideRow(this.closest(\'tr\'))"></td>'
        +'<td style="border:1px solid #ddd;padding:2px;min-width:90px;">'
        +'<input type="text" style="'+inp+'text-align:right;" value="'+esc(revVal)+'" placeholder="US$ 0.00"'
        +' onchange="pfSaveNoRideRow(this.closest(\'tr\')); pfUpdateRevenueLost();"></td>'
        +'<td style="border:1px solid #ddd;padding:2px;min-width:160px;">'
        +'<input type="text" style="'+inp+'" value="'+esc(data.reason||'')+'" placeholder="Motivo..."'
        +' onchange="pfSaveNoRideRow(this.closest(\'tr\'))"></td>';
    tbody.appendChild(tr);
    pfLoadGoodsList();
}

async function pfSaveNoRideRow(tr) {
    const inputs = tr.querySelectorAll('input');
    const awb    = inputs[0]?.value.trim() || '';
    const flt    = document.getElementById('pfFlight')?.value.split(' ')[0] || '';
    const date   = document.getElementById('pfDate')?.value  || '';
    const sta    = document.getElementById('pfStation')?.value || 'LIM';
    if (!flt || !date) return;

    const id     = tr.dataset.nrId || '0';
    const revRaw = inputs[5]?.value.replace(/[^0-9.-]/g,'') || '';

    try {
        const res = await apiFetch('/shipments/no_ride_api.php', {
            method: 'POST',
            body: JSON.stringify({
                id:          parseInt(id) || 0,
                flt_number:  flt,
                flt_date:    date,
                station:     sta,
                awb,
                uld_number:  inputs[1]?.value.trim() || '',
                product:     inputs[2]?.value.trim() || '',
                kg_net:      parseFloat(inputs[3]?.value)||null,
                gross_weight:parseFloat(inputs[4]?.value)||null,
                revenue_lost:parseFloat(revRaw)||null,
                reason:      inputs[6]?.value.trim() || '',
                user:        currentUser?.email || 'agent'
            })
        });
        if (res.success && res.id) tr.dataset.nrId = String(res.id);
    } catch(e) {}
}

function pfFmtAwbNoRide(input) {
    let v = input.value.replace(/[^0-9]/g,'');
    if (v.length > 3) v = v.slice(0,3)+'-'+v.slice(3,11);
    input.value = v;
}

let _pfGoodsLoaded = false;
function pfLoadGoodsList() {
    if (_pfGoodsLoaded) return;
    _pfGoodsLoaded = true;
    if (!document.getElementById('pfGoodsList')) {
        const dl = document.createElement('datalist');
        dl.id = 'pfGoodsList';
        document.body.appendChild(dl);
    }
    apiFetch('/catalog/catalog_api.php?table=goods_catalog&station='+(document.getElementById('pfStation')?.value||'LIM'))
    .then(function(res) {
        const dl = document.getElementById('pfGoodsList');
        if (!dl || !res.success) return;
        (res.data||[]).forEach(function(g){
            const opt = document.createElement('option');
            opt.value = (g.description||g.name||g.good_name||'').toUpperCase();
            dl.appendChild(opt);
        });
    }).catch(function(){});
}

async function pfAutoFillNoRide(input) {
    const awb = input.value.trim();
    if (!awb || awb.length < 10) return;
    const sta = document.getElementById('pfStation')?.value || 'LIM';
    try {
        const res = await apiFetch('/shipments/shipments_api.php?q='+encodeURIComponent(awb)+'&station='+sta);
        if (!res.success || !res.data?.length) return;
        const s = res.data[0];
        const tr = input.closest('tr');
        if (!tr) return;
        const inputs = tr.querySelectorAll('input');
        // Find ULD from flight_uld_awbs
        if (inputs[1] && !inputs[1].value) inputs[1].value = s.uld_type || '';
        if (inputs[2] && !inputs[2].value) inputs[2].value = (s.description||'').toUpperCase();
        if (inputs[3] && !inputs[3].value) inputs[3].value = s.gross_weight ? parseFloat(s.gross_weight).toFixed(2) : '';
        if (inputs[4] && !inputs[4].value) inputs[4].value = s.gross_weight ? parseFloat(s.gross_weight).toFixed(2) : '';
        if (inputs[5] && !inputs[5].value) inputs[5].value = s.freight_revenue ? 'US$ '+parseFloat(s.freight_revenue).toFixed(2) : '';
        pfSaveNoRideRow(tr);
    } catch(e) {}
}

function pfRestoreCnxRow(shipmentId, cnx) {
    const wrap  = document.getElementById('pfDepTableWrap');
    if (!wrap) return;
    // Cada Freight Forwarder tiene su propia tabla de detalle anidada
    // (Correcciones Post-Flight #2): hay que buscar en TODOS los tbody,
    // no solo en el primero, para encontrar la fila de esta guía.
    let tbody = null, insertAfter = null;
    wrap.querySelectorAll('tbody').forEach(function(tb) {
        const match = Array.from(tb.querySelectorAll('tr')).find(function(r) {
            return r.dataset.shipmentId == shipmentId || r.dataset.parentId == shipmentId;
        });
        if (match) { tbody = tb; insertAfter = match; }
    });
    if (!tbody) return;

    const INP = 'border:1px solid var(--delta-sky);border-radius:4px;padding:3px 6px;font-size:11px;background:#fff;';
    const fmt  = function(v){ return v||''; };

    const cnxRow = document.createElement('tr');
    cnxRow.dataset.parentId = shipmentId;
    cnxRow.dataset.cnxSeq   = cnx.seq;
    cnxRow.dataset.cnxId    = cnx.id;
    cnxRow.dataset.dest     = cnx.arr_station || '';
    cnxRow.style.background = '#FFF9E6';
    cnxRow.style.borderLeft = '3px solid #F59E0B';

    cnxRow.innerHTML = `
        <td colspan="3" style="padding:4px 8px;text-align:right;">
            <span style="font-size:9px;color:#B45309;font-weight:700;">CNX ${cnx.seq}</span>
        </td>
        <td style="padding:4px 6px;"><input type="text" value="${fmt(cnx.cnx_flight)}" maxlength="8"
            style="${INP}width:80px;text-transform:uppercase;"
            onchange="pfSaveCnxField(${shipmentId},this.closest('tr'),${cnx.seq})"
            data-field="cnx_flight"></td>
        <td style="padding:4px 6px;"><input type="text" value="${fmt(cnx.dep_station)}" maxlength="5"
            style="${INP}width:60px;text-transform:uppercase;"
            onchange="pfSaveCnxField(${shipmentId},this.closest('tr'),${cnx.seq})"
            data-field="dep_station"></td>
        <td style="padding:4px 6px;"><input type="date" value="${fmt(cnx.dep_date)}"
            style="${INP}width:130px;"
            onchange="pfSaveCnxField(${shipmentId},this.closest('tr'),${cnx.seq})"
            data-field="dep_date"></td>
        <td style="padding:4px 6px;"><input type="time" value="${fmt(cnx.dep_time)}"
            style="${INP}width:90px;"
            onchange="pfSaveCnxField(${shipmentId},this.closest('tr'),${cnx.seq})"
            data-field="dep_time"></td>
        <td style="padding:4px 6px;"><input type="text" value="${fmt(cnx.arr_station)}" maxlength="5"
            style="${INP}width:60px;text-transform:uppercase;"
            onchange="pfSaveCnxField(${shipmentId},this.closest('tr'),${cnx.seq})"
            data-field="arr_station"></td>
        <td style="padding:4px 6px;"><input type="date" value="${fmt(cnx.arr_date)}"
            style="${INP}width:130px;"
            onchange="pfSaveCnxField(${shipmentId},this.closest('tr'),${cnx.seq})"
            data-field="arr_date"></td>
        <td style="padding:4px 6px;"><input type="time" value="${fmt(cnx.arr_time)}"
            style="${INP}width:90px;"
            onchange="pfSaveCnxField(${shipmentId},this.closest('tr'),${cnx.seq})"
            data-field="arr_time"></td>
        <td></td>
        <td style="padding:4px 6px;text-align:center;">
            <button onclick="pfDeleteCnx(this.closest('tr'),${shipmentId})"
                title="Eliminar conexión"
                style="background:none;border:none;color:var(--delta-red);cursor:pointer;font-size:16px;padding:0;">&#x2715;</button>
        </td>`;

    if (insertAfter?.nextSibling) {
        tbody.insertBefore(cnxRow, insertAfter.nextSibling);
    } else {
        tbody.appendChild(cnxRow);
    }
}

async function pfAddCnx(shipmentId, awb) {
    const wrap = document.getElementById('pfDepTableWrap');
    if (!wrap) return;
    // Cada Freight Forwarder tiene su propia tabla de detalle anidada —
    // hay que ubicar en cuál de los tbody vive esta guía.
    let tbody = null, allRows = [];
    wrap.querySelectorAll('tbody').forEach(function(tb) {
        const hasIt = Array.from(tb.rows).some(function(r) {
            return (r.dataset.shipmentId || r.dataset.parentId || '') == shipmentId;
        });
        if (hasIt) { tbody = tb; allRows = Array.from(tb.rows); }
    });
    if (!tbody) return;

    // Find the LAST row belonging to this shipment (main row OR any of its CNX rows)
    let insertAfterRow = null;
    allRows.forEach(function(r) {
        const rid = r.dataset.shipmentId || r.dataset.parentId || '';
        if (rid == shipmentId) insertAfterRow = r;
    });

    // Count existing CNX for this shipment to get seq number
    const existingCnx = allRows.filter(function(r){ return r.dataset.parentId == shipmentId; }).length;
    const seq = existingCnx + 1;

    // Get dep_station from last row's dest
    const prevDest = insertAfterRow ? (insertAfterRow.dataset.dest || '') : '';

    const INP = 'border:1px solid var(--delta-sky);border-radius:4px;padding:3px 6px;font-size:11px;background:#fff;';

    const cnxRow = document.createElement('tr');
    cnxRow.dataset.parentId = shipmentId;
    cnxRow.dataset.cnxSeq   = seq;
    cnxRow.dataset.cnxId    = '0';
    cnxRow.dataset.dest     = '';
    cnxRow.style.cssText    = 'background:#FFF9E6;border-left:3px solid #F59E0B;';

    cnxRow.innerHTML =
        '<td colspan="3" style="padding:4px 8px;text-align:right;">'
        + '<span style="font-size:9px;color:#B45309;font-weight:700;white-space:nowrap;">&#8627; CNX ' + seq + '</span>'
        + '</td>'
        // CNX flight
        + '<td style="padding:4px 6px;"><input type="text" placeholder="AA123" maxlength="8"'
        + ' style="' + INP + 'width:80px;text-transform:uppercase;"'
        + ' onchange="pfSaveCnxField(' + shipmentId + ',this.closest(\'tr\'),' + seq + ')"'
        + ' data-field="cnx_flight"></td>'
        // DEP station
        + '<td style="padding:4px 6px;"><input type="text" placeholder="' + esc(prevDest||'LIM') + '" maxlength="5"'
        + ' style="' + INP + 'width:55px;text-transform:uppercase;" value="' + esc(prevDest) + '"'
        + ' onchange="pfSaveCnxField(' + shipmentId + ',this.closest(\'tr\'),' + seq + ')"'
        + ' data-field="dep_station"></td>'
        // DEP date
        + '<td style="padding:4px 6px;"><input type="date"'
        + ' style="' + INP + 'width:130px;"'
        + ' onchange="pfSaveCnxField(' + shipmentId + ',this.closest(\'tr\'),' + seq + ')"'
        + ' data-field="dep_date"></td>'
        // DEP time
        + '<td style="padding:4px 6px;"><input type="time"'
        + ' style="' + INP + 'width:88px;"'
        + ' onchange="pfSaveCnxField(' + shipmentId + ',this.closest(\'tr\'),' + seq + ')"'
        + ' data-field="dep_time"></td>'
        // ARR station
        + '<td style="padding:4px 6px;"><input type="text" placeholder="ATL" maxlength="5"'
        + ' style="' + INP + 'width:55px;text-transform:uppercase;"'
        + ' onchange="pfSaveCnxField(' + shipmentId + ',this.closest(\'tr\'),' + seq + ')"'
        + ' data-field="arr_station"></td>'
        // ARR date
        + '<td style="padding:4px 6px;"><input type="date"'
        + ' style="' + INP + 'width:130px;"'
        + ' onchange="pfSaveCnxField(' + shipmentId + ',this.closest(\'tr\'),' + seq + ')"'
        + ' data-field="arr_date"></td>'
        // ARR time
        + '<td style="padding:4px 6px;"><input type="time"'
        + ' style="' + INP + 'width:88px;"'
        + ' onchange="pfSaveCnxField(' + shipmentId + ',this.closest(\'tr\'),' + seq + ')"'
        + ' data-field="arr_time"></td>'
        // No Send
        + '<td></td>'
        // Delete
        + '<td style="padding:4px 6px;text-align:center;">'
        + '<button onclick="pfDeleteCnx(this.closest(\'tr\'),' + shipmentId + ')"'
        + ' title="Eliminar conexión"'
        + ' style="background:none;border:none;color:var(--delta-red);cursor:pointer;font-size:16px;padding:0;">&#x2715;</button>'
        + '</td>';

    // Insert immediately after the last row belonging to this shipment
    if (insertAfterRow && insertAfterRow.nextSibling) {
        tbody.insertBefore(cnxRow, insertAfterRow.nextSibling);
    } else {
        tbody.appendChild(cnxRow);
    }

    cnxRow.querySelector('[data-field="cnx_flight"]')?.focus();
}

async function pfSaveCnxField(shipmentId, row, seq) {
    const getVal = function(field) {
        const el = row.querySelector('[data-field="' + field + '"]');
        return el ? el.value.trim() : '';
    };
    const cnx_flight  = getVal('cnx_flight').toUpperCase();
    if (!cnx_flight) return;  // Don't save until flight is entered

    const dep_date    = getVal('dep_date')    || null;
    const dep_time    = getVal('dep_time')    || null;
    const dep_station = getVal('dep_station').toUpperCase() || null;
    const arr_date    = getVal('arr_date')    || null;
    const arr_time    = getVal('arr_time')    || null;
    const arr_station = getVal('arr_station').toUpperCase() || null;
    const existingId  = parseInt(row.dataset.cnxId || '0');
    const user        = currentUser?.email || 'agent';

    if (!shipmentId || shipmentId === 0) {
        console.warn('pfSaveCnxField: shipmentId is 0, skipping save');
        return;
    }

    try {
        const res = await apiFetch('/shipments/shipment_connections_api.php', {
            method: 'POST',
            body: JSON.stringify({
                action:      'save',
                id:          existingId,
                shipment_id: shipmentId,
                seq,
                cnx_flight, dep_date, dep_time, dep_station,
                arr_date, arr_time, arr_station, user
            })
        });
        if (res.success) {
            if (res.data?.id) row.dataset.cnxId = String(res.data.id);
            if (arr_station)  row.dataset.dest  = arr_station;
            // Green flash
            const prev = row.style.background;
            row.style.background = '#DCFCE7';
            setTimeout(function(){ row.style.background = '#FFF9E6'; }, 700);
        } else {
            console.warn('CNX save failed:', res.message);
        }
    } catch(e) { console.warn('CNX save error:', e); }
}

async function pfDeleteCnx(row, shipmentId) {
    const cnxId = parseInt(row.dataset.cnxId || '0');
    if (cnxId) {
        try {
            await apiFetch('/shipments/shipment_connections_api.php', {
                method: 'POST',
                body: JSON.stringify({ action:'delete', id:cnxId, shipment_id:shipmentId })
            });
        } catch(e) {}
    }
    row.remove();
    // Renumber remaining CNX rows for this shipment
    const wrap = document.getElementById('pfDepTableWrap');
    if (!wrap) return;
    let seq = 1;
    wrap.querySelectorAll('tr[data-parent-id="' + shipmentId + '"]').forEach(function(r) {
        const badge = r.querySelector('span');
        if (badge) badge.textContent = 'CNX ' + seq++;
        r.dataset.cnxSeq = seq - 1;
    });
}

function pfSendAgent(agent) {
    // Find all shipments for this agent (Correcciones Post-Flight #2: el
    // envío ahora se dispara por Freight Forwarder, no por guía individual)
    const data   = window._pfData || {};
    const loaded = data.loaded || [];
    const agentShipments = loaded.filter(function(x){ return (x.agent_name||'\u2014') === agent; });
    const s      = agentShipments[0] || {};
    const flt    = document.getElementById('pfFlight')?.value   || '';
    const date   = document.getElementById('pfDate')?.value     || '';
    const mn     = document.getElementById('pfManifest')?.value || '';
    const dep    = document.getElementById('pfDepTime')?.value  || '';
    const arr    = document.getElementById('pfArrTime')?.value  || '';
    const sta    = document.getElementById('pfStation')?.value  || 'LIM';
    const dest   = s.flt_dest || data.flt_dest || '';
    const logoUrl= window.location.pathname.split('/').slice(0,2).join('/') + '/assets/img/delta-cargo-logo.png';

    const shortDate    = date ? (function(){ const d=new Date(date+'T12:00:00'); return d.getDate()+'/'+String(d.getMonth()+1).padStart(2,'0')+'/'+d.getFullYear(); })() : '';
    const nextDate     = date ? (function(){ const d=new Date(date+'T12:00:00');d.setDate(d.getDate()+1); return d.getDate()+'/'+String(d.getMonth()+1).padStart(2,'0')+'/'+d.getFullYear(); })() : '';
    const subjectDate  = date ? (function(){ const d=new Date(date+'T12:00:00'); const m=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; return d.getDate()+'-'+m[d.getMonth()]+'-'+d.getFullYear(); })() : '';

    const subject = 'Departure Notice | Flight ' + flt.split(' ')[0] + ' | ' + sta + ' \u2192 ' + dest + ' | ' + subjectDate;

    const buildHtml = function() {
        // ── Tarjeta de tramo de vuelo (mismo lenguaje visual para el tramo
        // principal en "Flight Information" y cada fila de "Shipment Route"):
        // triángulo de aerolínea, número de vuelo, ícono de avión entre los
        // códigos de aeropuerto (subrayados, con ícono de enlace externo) y
        // las fechas/horas reales de salida y llegada en negrita debajo. ──
        const legCardHtml = function(flightNo, orig, destAp, depLbl, depVal, arrLbl, arrVal) {
            return `<table role="presentation" style="width:100%;border-collapse:collapse;">
                <tr>
                    <td style="width:30px;vertical-align:top;padding-top:1px;">
                        <span style="color:#C8102E;font-size:15px;line-height:1;">&#9650;</span>
                    </td>
                    <td style="width:120px;vertical-align:top;padding-right:10px;">
                        <div style="font-size:17px;font-weight:800;color:#002244;letter-spacing:.01em;">${flightNo}</div>
                    </td>
                    <td style="width:34px;vertical-align:top;text-align:center;padding-top:2px;">
                        <span style="font-size:17px;color:#3B5B8C;">&#9992;</span>
                    </td>
                    <td style="vertical-align:top;padding-right:16px;">
                        <div style="font-size:15px;font-weight:800;color:#002244;text-decoration:underline;text-underline-offset:2px;">${orig} <span style="font-size:10px;text-decoration:none;">&#8599;</span></div>
                        <div style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8A94A6;margin-top:8px;">${depLbl}</div>
                        <div style="font-size:12px;font-weight:700;color:#1A1F29;margin-top:1px;">${depVal}</div>
                    </td>
                    <td style="vertical-align:top;text-align:right;">
                        <div style="font-size:15px;font-weight:800;color:#002244;text-decoration:underline;text-underline-offset:2px;"><span style="font-size:10px;text-decoration:none;">&#8599;</span> ${destAp}</div>
                        <div style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8A94A6;margin-top:8px;">${arrLbl}</div>
                        <div style="font-size:12px;font-weight:700;color:#1A1F29;margin-top:1px;">${arrVal}</div>
                    </td>
                </tr>
            </table>`;
        };

        // ── Shipment Route: vuelo principal + cualquier conexión (+CNX)
        // registrada para las guías de este agente. Se deduplica por
        // vuelo+fecha+hora, ya que varias AWBs suelen compartir la misma
        // conexión aguas abajo. ──
        const routeLegs = [{
            flight: esc(flt.split(' ')[0]),
            orig:   esc(sta),
            dest:   esc(dest),
            dep:    shortDate + ' ' + (dep || '\u2014'),
            arr:    nextDate + ' ' + (arr || '\u2014'),
        }];
        const seenLegs = new Set();

        let awbRows = '';
        agentShipments.forEach(function(sh) {
            const shAwb      = esc(sh.awb || sh.shipment_number);
            const shPcs      = sh.pieces || sh.actual_pieces || '';
            const shWgt      = sh.gross_weight ? parseFloat(sh.gross_weight).toLocaleString('en-US') : '';
            const shOrig     = esc(sh.flt_orig || sta);
            const shDest     = esc(sh.flt_dest || dest);
            const shSid      = sh.id || sh.shipment_id || 0;
            const shTrackUrl = pfTrackingUrl(sh.awb || sh.shipment_number);
            awbRows += `<table role="presentation" style="width:100%;border-collapse:collapse;border-bottom:1px solid #EEF1F5;">
                <tr>
                    <td style="width:30px;padding:14px 6px 14px 0;vertical-align:top;">
                        <span style="font-size:16px;">&#128230;</span>
                    </td>
                    <td style="padding:14px 10px;vertical-align:top;">
                        <div style="font-family:'Courier New',monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-weight:800;font-size:14px;color:#002244;">${shAwb}</div>
                        <div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:#8A94A6;margin-top:4px;">${shPcs} PCS &nbsp;&middot;&nbsp; ${shWgt} Kg</div>
                    </td>
                    <td style="padding:14px 10px;vertical-align:middle;text-align:center;white-space:nowrap;">
                        <span style="font-size:13px;font-weight:800;color:#1A1F29;text-decoration:underline;text-underline-offset:2px;">${shOrig}</span>
                        <span style="margin:0 8px;font-size:14px;color:#3B5B8C;">&#9992;</span>
                        <span style="font-size:13px;font-weight:800;color:#002244;text-decoration:underline;text-underline-offset:2px;">${shDest}</span>
                    </td>
                    <td style="padding:14px 0 14px 10px;vertical-align:middle;text-align:right;">
                        <a href="${shTrackUrl}" style="display:inline-block;background:#002244;color:#fff;font-size:10px;font-weight:700;
                            text-transform:uppercase;letter-spacing:.03em;text-decoration:none;padding:8px 14px;border-radius:20px;white-space:nowrap;">Track Shipment &#8599;</a>
                    </td>
                </tr>
            </table>`;
            if (shSid) {
                const wrap = document.getElementById('pfDepTableWrap');
                if (wrap) {
                    wrap.querySelectorAll('tr[data-parent-id="' + shSid + '"]').forEach(function(cnxR) {
                        const gv = function(f){ const el=cnxR.querySelector('[data-field="'+f+'"]'); return el?el.value.trim():''; };
                        const cf=gv('cnx_flight'), ds=gv('dep_station'), dd=gv('dep_date'), dt=gv('dep_time'), as_=gv('arr_station'), ad=gv('arr_date'), at_=gv('arr_time');
                        const fmtD=function(d){ if(!d) return '\u2014'; const x=new Date(d+'T12:00:00'); return x.getDate()+'/'+String(x.getMonth()+1).padStart(2,'0')+'/'+x.getFullYear(); };
                        if (!cf) return;
                        const legKey = cf + '|' + dd + '|' + dt;
                        if (seenLegs.has(legKey)) return;
                        seenLegs.add(legKey);
                        routeLegs.push({
                            flight: esc(cf),
                            orig:   esc(ds),
                            dest:   esc(as_),
                            dep:    fmtD(dd) + ' ' + (dt || '\u2014'),
                            arr:    fmtD(ad) + ' ' + (at_ || '\u2014'),
                        });
                    });
                }
            }
        });

        const routeCards = routeLegs.map(function(leg, i) {
            const divider = i > 0 ? '<hr style="border:none;border-top:1px solid #EEF1F5;margin:16px 0;">' : '';
            return divider + legCardHtml(leg.flight, leg.orig, leg.dest, 'ACTUAL DEPARTURE', leg.dep, 'ACTUAL ARRIVAL', leg.arr);
        }).join('');

        return `<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Departure Notice</title>
<style>
body{font-family:Arial,Helvetica,sans-serif;font-size:13px;color:#1A1F29;margin:0;padding:0;background:#F0F2F5;}
.wrap{max-width:640px;margin:0 auto;background:#fff;}
.section-lbl{font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.08em;color:#002244;margin:0 0 14px;}
.card{border:1px solid #E5E7EB;border-radius:10px;padding:18px 20px;background:#fff;}
@media print{@page{size:A4 portrait;margin:15mm;}body{padding:0;background:#fff;}}
</style></head><body>
<div class="wrap">
  <div style="background:#002244;padding:20px 28px;">
    <img src="${logoUrl}" alt="Delta Cargo" style="height:44px;width:auto;display:block;">
  </div>
  <div style="height:3px;background:#C8102E;"></div>
  <div style="padding:28px;">

    <div style="font-size:20px;font-weight:800;color:#002244;margin-bottom:2px;">Departure Notice</div>
    <div style="font-size:12px;font-weight:700;color:#8A94A6;text-transform:uppercase;letter-spacing:.04em;margin-bottom:22px;">${esc(sta)} &#9992; ${esc(dest)}</div>

    <p style="margin:0 0 24px;line-height:1.65;font-size:13px;">
    Estimado cliente,<br><br>
    Nos complace informarle que la(s) guía(s) aérea(s) detallada(s) a continuación fueron embarcadas exitosamente en el vuelo indicado y actualmente se encuentran en tránsito hacia su destino.<br>
    A continuación encontrará el detalle del vuelo y de las guías correspondientes a su empresa.
    </p>

    <div class="section-lbl">&#9992; Flight Information</div>
    <div class="card" style="margin-bottom:26px;">
      ${legCardHtml(esc(flt.split(' ')[0]), esc(sta), esc(dest), 'ACTUAL DEPARTURE', shortDate + ' ' + (dep || '\u2014'), 'ACTUAL ARRIVAL', nextDate + ' ' + (arr || '\u2014'))}
      <div style="border-top:1px solid #EEF1F5;margin:16px 0 0;padding-top:12px;">
        <span style="font-size:9px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#8A94A6;">&#128196; Export Manifest</span>
        <span style="font-size:13px;font-weight:800;color:#002244;margin-left:8px;">${esc(mn||'\u2014')}</span>
      </div>
    </div>

    <div class="section-lbl">&#128230; Shipment Details</div>
    <div class="card" style="padding:4px 20px;margin-bottom:26px;">
      ${awbRows}
    </div>

    <div class="section-lbl">&#128506; Shipment Route</div>
    <div class="card" style="margin-bottom:26px;">
      ${routeCards}
    </div>

    <p style="margin:8px 0 6px;line-height:1.65;font-size:13px;">
    Puede realizar el seguimiento del estado de cada embarque utilizando los enlaces indicados anteriormente.<br>
    Si requiere información adicional o asistencia sobre este envío, nuestro equipo de Servicio al Cliente estará disponible para atender su consulta.<br><br>
    Agradecemos la confianza depositada en nuestros servicios.
    </p>
    <p style="margin-top:18px;font-size:13px;">
    Atentamente,<br>
    <strong style="color:#002244;">Delta Cargo GHA</strong><br>
    Cargo Operations<br>
    Lima &ndash; Per&uacute;
    </p>
  </div>
</div>
</body></html>`;
    };

    // ── Modal unificado de reportes (reemplaza el modal propio de
    // Departure Notice — ver "Unificación de los modales de envío") ──
    const agentId  = s.agent_id || 0;
    const awbList  = agentShipments.map(function(x){ return x.awb || x.shipment_number; });

    reportModalOpen({
        reportType:  'DEPARTURE_NOTICE',
        title:       'Departure Notice',
        icon:        '✈',
        contextLine: agent + ' • ' + awbList.length + ' AWB(s)',
        subject:     subject,
        station:     sta,
        recipientMode: 'freight_forwarder',
        agentId:     agentId,
        agentName:   agent,
        filenamePrefix: 'Departure_Notice_' + agent.replace(/[^A-Z0-9]/gi, '_'),
        buildHtml:   buildHtml,
        onSend: function(payload) {
            return apiFetch('/shipments/postflight_api.php', {
                method: 'POST',
                body: JSON.stringify({
                    action:          'send_departure_notice',
                    awb:             awbList.join(', '),
                    awbs:            awbList,
                    agent,
                    flt_number:      flt.split(' ')[0],
                    flt_date:        date,
                    station:         sta,
                    manifest_number: mn,
                    to_email:        payload.to.join(', '),
                    cc_email:        '',
                    bcc_email:       '',
                    subject:         payload.subject,
                    body_html:       payload.html,
                    email_html:      payload.html,
                    user:            currentUser?.email || 'agent',
                })
            });
        },
        onSent: function() { pfMarkDnSent(agent); },
    });
}

function pfMarkDnSent(agent) {
    // Recuerda qué agentes ya recibieron su Departure Notice en esta sesión
    // (Correcciones Post-Flight #3) y refresca el badge de estado de TODAS
    // las filas de ese agente (una fila por AWB en el nuevo diseño).
    if (!window._pfDnSentAgents) window._pfDnSentAgents = new Set();
    window._pfDnSentAgents.add(agent);

    document.querySelectorAll('#pfDepTableWrap .pf-dn-status[data-agent="' + CSS.escape(agent) + '"]').forEach(function(badge) {
        badge.textContent = '\u{1F7E2} Notification Sent';
        badge.style.background = '#ECFDF5';
        badge.style.color = '#065F46';
        badge.style.border = '1px solid #6EE7B7';
    });
}


// ── Vuelos disponibles ────────────────────────────────────────────
async function pfSaveFlightTimes() {
    const fltRaw = document.getElementById('pfFlight')?.value  || '';
    const flt    = fltRaw.split(' ')[0];
    const date   = document.getElementById('pfDate')?.value    || '';
    const dep    = document.getElementById('pfDepTime')?.value || '';
    const arr    = document.getElementById('pfArrTime')?.value || '';
    const sta    = document.getElementById('pfStation')?.value || 'LIM';
    if (!flt || !date) return;

    // +1 day indicator
    const dayEl = document.getElementById('pfArrDay');
    if (dayEl) dayEl.textContent = (dep && arr && arr < dep) ? '+1' : '';

    const flash = function(id, color) {
        const el = document.getElementById(id);
        if (el && el.value) {
            el.style.background = color;
            setTimeout(function(){ el.style.background = '#FFF9E6'; }, 800);
        }
    };

    try {
        // Save arr_time via simple dedicated endpoint
        if (arr) {
            await apiFetch('/flights/flight_operations_api.php?action=save_arr_time'
                + '&flt_number=' + encodeURIComponent(flt)
                + '&flt_date='   + encodeURIComponent(date)
                + '&station='    + encodeURIComponent(sta)
                + '&arr_time='   + encodeURIComponent(arr));
            flash('pfArrTime', '#D1FAE5');
        }

        // Save dep time (etd) to manifest
        if (dep) {
            const mn = document.getElementById('pfManifest')?.value || '';
            await apiFetch('/shipments/postflight_api.php', {
                method: 'POST',
                body: JSON.stringify({
                    action:          'save_times',
                    flt_number:      flt,
                    flt_date:        date,
                    station:         sta,
                    manifest_number: mn,
                    etd:             dep,
                    arr_time:        arr || null,
                    email_html:      ' ',
                })
            });
            flash('pfDepTime', '#D1FAE5');
        }
    } catch(e) { console.warn('pfSaveFlightTimes error:', e); }
}

async function pfLoadFlights() {
    const station = document.getElementById('pfStation')?.value || 'LIM';
    try {
        const res = await apiFetch('/flights/flights_api.php?station=' + station);
        const flights = res.success ? res.data : [];
        window.bkFlightsCache = flights;  // cache for capacity bar
        const sel = document.getElementById('pfFlight');
        sel.innerHTML = '<option value="">— Seleccionar —</option>';
        flights.forEach(f => {
            const o = document.createElement('option');
            o.value = f.flt_number;
            o.textContent = f.flt_number + '  ' + f.flt_orig + '→' + f.flt_dest;
            sel.appendChild(o);
        });
    } catch(e) {}
}

// ── Auto-carga: misma lógica que Flight Profile (fdMaybeAutoLoad) — se
// dispara apenas Station + Flight + Date tienen los tres un valor válido,
// sin importar cuál de los tres se cambió último. Reemplaza al botón
// "Cargar vuelo", que ya no es necesario. ──
function pfMaybeAutoLoad() {
    const station = document.getElementById('pfStation')?.value || '';
    const flight  = document.getElementById('pfFlight')?.value  || '';
    const date    = document.getElementById('pfDate')?.value    || '';
    if (station && flight && date) pfLoad();
}

// ── Cargar datos del vuelo ────────────────────────────────────────
let pfData = null;

async function pfLoad() {
    const flt     = document.getElementById('pfFlight').value;
    const date    = document.getElementById('pfDate').value;
    const station = document.getElementById('pfStation')?.value || 'LIM';
    const status  = document.getElementById('pfStatus');

    if (!flt || !date) {
        status.style.color = 'var(--delta-red)';
        status.textContent = 'Seleccioná vuelo y fecha.';
        return;
    }

    status.style.color = 'var(--text-mid)';
    status.textContent = 'Cargando datos del vuelo...';

    // Load Plan Image: se recupera automáticamente si ya existe una guardada
    // para este vuelo/fecha/estación (Ajustes adicionales Flight Summary #3).
    // Si no hay ninguna (o el servicio aún no está disponible), queda vacía
    // y lista para que el usuario cargue una nueva — sin mensajes técnicos.
    pfLoadSavedLoadPlanImage(flt.split(' ')[0], date, station);

    try {
        const params = new URLSearchParams({ flt_number: flt, flt_date: date, station });
        const res = await apiFetch('/shipments/postflight_api.php?' + params.toString());

        if (!res.success) {
            status.style.color = 'var(--delta-red)';
            status.textContent = res.message || 'Error al cargar vuelo.';
            return;
        }

        pfData = res;
        window._pfData = res;

        // Manifest number
        document.getElementById('pfManifest').value = res.manifest_number || '';

        // KPI indicators
        document.getElementById('pfBadgeLoaded').textContent    = res.loaded_count ?? (res.loaded?.length ?? 0);
        document.getElementById('pfBadgeOffloaded').textContent = res.offloaded_count ?? (res.offloaded?.length ?? 0);
        document.getElementById('pfBadgeKg').textContent        = fmtNum(res.total_kg || 0);
        document.getElementById('pfKpiBar').style.display = 'flex';

        // ETD/ARR from manifest and flight_operations
        if (res.etd) document.getElementById('pfDepTime').value = res.etd.substring(0,5);
        if (res.arr_time) {
            document.getElementById('pfArrTime').value = res.arr_time.substring(0,5);
            const dep = document.getElementById('pfDepTime')?.value || '';
            const arr = res.arr_time.substring(0,5);
            const dayEl = document.getElementById('pfArrDay');
            if (dayEl) dayEl.textContent = (dep && arr && arr < dep) ? '+1' : '';
        }

        // Operational data bar
        document.getElementById('pfOpsBar').style.display = 'block';
        pfLoadOpsData();

        // Show tabs
        document.getElementById('pfTabs').style.display = 'block';

        // Render Departure Notice tab
        pfRenderDepartureTab(res);

        status.style.color = '#2ECC71';
        status.textContent = '✅ ' + (res.loaded_count || 0) + ' guías embarcadas · ' +
            (res.offloaded_count || 0) + ' no embarcadas.';

    } catch(e) {
        status.style.color = 'var(--delta-red)';
        status.textContent = 'Error de conexión.';
    }
}


// ── Helper: construir HTML Notificación de Salida ─────────────────
function pfBuildDepartureHtml(data, destCity, depTime, arrTime) {
    const flt    = document.getElementById('pfFlight').value;
    const date   = document.getElementById('pfDate').value;
    const mn     = document.getElementById('pfManifest').value;
    const dest   = data.flt_dest || 'ATL';

    let rows = '';
    (data.loaded || []).forEach((g, i) => {
        const bg   = i%2===0 ? '#fff' : '#F4F6F9';
        const awb  = g.awb || g.shipment_number;
        rows += '<tr style="background:' + bg + ';">' +
            '<td style="padding:6px 10px;border:1px solid #ddd;font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-weight:700;color:#002244;">' + esc(awb) + '</td>' +
            '<td style="padding:6px 10px;border:1px solid #ddd;">' + esc(g.agent_name||'—') + '</td>' +
            '<td style="padding:6px 10px;border:1px solid #ddd;text-align:right;">' + (g.pieces||0) + '</td>' +
            '<td style="padding:6px 10px;border:1px solid #ddd;text-align:right;">' + parseFloat(g.gross_weight||0).toFixed(2) + '</td>' +
            '<td style="padding:6px 10px;border:1px solid #ddd;">' + esc(g.description||'—') + '</td>' +
            '<td style="padding:6px 10px;border:1px solid #ddd;text-align:center;font-weight:700;">' + esc(g.routing_dest||dest) + '</td>' +
            '</tr>';
    });

    const dateLabel = date ? new Date(date + 'T12:00:00').toLocaleDateString('es-PE', { day:'2-digit', month:'short', year:'numeric' }) : date;

    return '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Departure Notice</title>' +
        '<style>body{font-family:Arial,sans-serif;font-size:13px;margin:24px;color:#1A1F29;}' +
        'h2{color:#002244;border-bottom:2px solid #C8102E;padding-bottom:6px;}' +
        'table{width:100%;border-collapse:collapse;font-size:12px;margin-top:12px;}' +
        'th{background:#002244;color:#fff;padding:7px 10px;text-align:left;border:1px solid #002244;}' +
        '@media print{@page{size:A4;margin:12mm}}' +
        '</style></head><body>' +
        '<div style="background:#002244;color:#fff;padding:12px 20px;margin-bottom:18px;border-radius:4px;">' +
        '<strong style="font-size:16px;">SIGHA <span style="color:#FFC72C;">v2</span></strong>' +
        '<span style="font-size:11px;margin-left:12px;opacity:.75;">Delta Air Lines · GSA LIM</span></div>' +
        '<h2>Departure Notice — (LIM/' + esc(dest) + ')</h2>' +
        '<p style="margin:10px 0;line-height:1.6;">Estimado Cliente: Les informamos que los embarques detallados abajo salieron en nuestro vuelo <strong>' + esc(flt) + '</strong> con destino la ciudad de <strong>' + esc(destCity||dest) + '</strong>. ' +
        'Los invitamos a seguir sus embarques vía Internet ingresando a <a href="http://www.deltacargo.com">www.deltacargo.com</a></p>' +
        '<table style="margin-bottom:14px;border-collapse:collapse;font-size:12px;">' +
        '<tr><td style="padding:5px 10px;border:1px solid #ddd;font-weight:700;background:#F4F6F9;">EXPORT MANIFEST Nº</td>' +
        '<td style="padding:5px 10px;border:1px solid #ddd;">' + esc(mn) + '</td>' +
        '<td style="padding:5px 10px;border:1px solid #ddd;font-weight:700;background:#F4F6F9;">VLO</td>' +
        '<td style="padding:5px 10px;border:1px solid #ddd;">' + esc(flt) + '</td>' +
        '<td style="padding:5px 10px;border:1px solid #ddd;font-weight:700;background:#F4F6F9;">DEP</td>' +
        '<td style="padding:5px 10px;border:1px solid #ddd;">' + dateLabel + (depTime ? ' ' + depTime : '') + '</td>' +
        '<td style="padding:5px 10px;border:1px solid #ddd;font-weight:700;background:#F4F6F9;">ARR</td>' +
        '<td style="padding:5px 10px;border:1px solid #ddd;">' + (arrTime || '—') + '</td></tr>' +
        '</table>' +
        '<table><thead><tr><th>AWB</th><th>Agente</th><th>Piezas</th><th>Kg Bruto</th><th>Descripción</th><th>Dst</th></tr></thead>' +
        '<tbody>' + rows + '</tbody></table>' +
        '<p style="margin-top:18px;line-height:1.6;">Los invitamos a seguir sus embarques vía Internet ingresando a <a href="http://www.deltacargo.com">www.deltacargo.com</a><br>' +
        'Atentamente,<br><strong>Delta Cargo LIM-Peru.</strong></p>' +
        '<p style="font-size:10px;color:#999;border-top:1px solid #eee;padding-top:8px;margin-top:20px;">' +
        'Generado por SIGHA v2 · ' + new Date().toLocaleString('es-PE') + '</p>' +
        '</body></html>';
}

// ── Notificación de Salida — Vista previa ─────────────────────────
function pfPreviewDeparture() {
    if (!pfData) { alert('Cargá el vuelo primero.'); return; }
    const html = pfBuildDepartureHtml(
        pfData,
        document.getElementById('pfDestCity').value,
        document.getElementById('pfDepTime').value,
        document.getElementById('pfArrTime').value
    );
    const win = window.open('', '_blank');
    win.document.write(html);
    win.document.close();
    setTimeout(() => win.print(), 500);
}

// ── Notificación de Salida — Enviar ──────────────────────────────
async function pfSendDeparture() {
    if (!pfData) { alert('Cargá el vuelo primero.'); return; }
    const msg = document.getElementById('pfMsgDep');

    if (!pfData.loaded || !pfData.loaded.length) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'No hay guías embarcadas (LOADED) para este vuelo.';
        return;
    }

    if (!confirm('¿Enviar Notificación de Salida por email a los destinatarios configurados?')) return;

    msg.style.color = 'var(--text-mid)';
    msg.textContent = 'Enviando...';

    const html = pfBuildDepartureHtml(
        pfData,
        document.getElementById('pfDestCity').value,
        document.getElementById('pfDepTime').value,
        document.getElementById('pfArrTime').value
    );

    try {
        const res = await apiFetch('/shipments/postflight_api.php', {
            method: 'POST',
            body: JSON.stringify({
                action:          'departure_notice',
                flt_number:      document.getElementById('pfFlight').value,
                flt_date:        document.getElementById('pfDate').value,
                manifest_number: document.getElementById('pfManifest').value,
                station:         document.getElementById('pfStation')?.value || 'LIM',
                dest_city:       document.getElementById('pfDestCity').value,
                dep_time:        document.getElementById('pfDepTime').value,
                arr_time:        document.getElementById('pfArrTime').value,
                email_html:      html,
                user_id:         currentUser ? currentUser.id : null,
            })
        });
        msg.style.color = res.success ? '#2ECC71' : 'var(--delta-red)';
        msg.textContent = res.success ? ('✅ ' + res.message) : (res.message || 'Error al enviar.');
    } catch(e) {
        msg.style.color = 'var(--delta-red)';
        msg.textContent = 'Error de conexión.';
    }
}

// NOTA: pfSaveOpsData() se eliminó — la sección "Datos Operativos del Vuelo"
// de Post-Flight es de solo lectura (ver postFlightInit). Toda edición de
// esos campos (Ship #, Aircraft Type, Payload, PAX O/B, NR PAX O/B, Bags in
// Bulk, Fuel) se hace exclusivamente desde el módulo Flight Profile (fdSave),
// que escribe en la misma tabla flight_operations que lee pfLoadOpsData().

// ── Cargar datos operativos guardados ────────────────────────────
async function pfLoadOpsData() {
    const flt  = document.getElementById('pfFlight')?.value || '';
    const date = document.getElementById('pfDate')?.value   || '';
    if (!flt || !date) return;
    try {
        const res = await apiFetch('/flights/flight_operations_api.php?flt_number=' + encodeURIComponent(flt) + '&flt_date=' + date);
        if (res.success && res.data) {
            const d = res.data;
            const fill = (id, v) => { const el=document.getElementById(id); if(el && v) el.value=v; };
            fill('pfShipNumber',  d.ship_number);
            fill('pfAcTypeFs',    d.aircraft_type);
            fill('pfPayloadKg',   d.payload_kg);
            fill('pfPaxOb',       d.pax_ob);
            fill('pfNrPax',       d.nr_pax_ob);
            fill('pfBagsInBulk',  d.bags_in_bulk);
            fill('pfFuel',        d.fuel_lbs);
        }
    } catch(e) {}
}

// ── Helper: construir HTML Flight Summary ─────────────────────────
function pfBuildSummaryHtml() {
    // ── Reingeniería: el reporte ahora es un clon fiel del dashboard en
    // pantalla (ULD Summary, Bulk Summary, Payload/Volume/Positions,
    // PAX O/B + Revenue) en vez de una plantilla independiente — así se
    // evita cualquier diferencia entre lo que el usuario ve y lo que
    // recibe por correo/imprime/descarga. ──
    const flt     = document.getElementById('pfFlight')?.value || '';
    const date    = document.getElementById('pfDate')?.value   || '';
    const station = document.getElementById('pfStation')?.value || 'LIM';
    const dest    = (window._pfData && window._pfData.flt_dest) || '';
    const etd     = document.getElementById('pfDepTime')?.value || '';
    const eta     = document.getElementById('pfArrTime')?.value || '';
    const mn      = document.getElementById('pfManifest')?.value || '';

    const agentName = (currentUser && (currentUser.name || currentUser.full_name || currentUser.display_name))
        || (currentUser?.email || '');

    const now = new Date();
    const generatedOn = now.toLocaleString('en-US', { dateStyle: 'medium', timeStyle: 'short' });
    const dateLabel = date ? (function() {
        const d = new Date(date + 'T12:00:00');
        const m = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
        return d.getDate() + ' ' + m[d.getMonth()] + ' ' + d.getFullYear();
    })() : '';

    const dashboardHtml = pfCloneDashboardForReport();

    const loadPlanBlock = pfLoadPlanImageData
        ? '<div class="fs-panel" style="margin-top:14px;page-break-inside:avoid;">'
          + '<div class="fs-panel-header">LOAD PLAN IMAGE</div>'
          + '<div style="padding:14px;"><img src="' + pfLoadPlanImageData + '" style="width:100%;border-radius:4px;display:block;"></div>'
          + '</div>'
        : '';

    return '<!DOCTYPE html><html><head><meta charset="UTF-8">'
        + '<title>Flight Summary Report — ' + esc(flt) + ' ' + dateLabel + '</title>'
        + '<style>'
        + '* { box-sizing:border-box; }'
        + 'body { font-family:Arial,sans-serif; color:#1A1F29; margin:0; padding:22px 26px; background:#fff; font-size:12px; }'
        + '.report-header { display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:14px; border-bottom:3px solid #C8102E; padding-bottom:10px; }'
        + '.report-title { font-size:22px; font-weight:900; color:#002244; letter-spacing:.02em; }'
        + '.report-sub { font-size:11px; color:#5B6472; margin-top:2px; }'
        + '.report-meta { font-size:11px; color:#5B6472; text-align:right; line-height:1.6; }'
        + '.flight-cards { display:flex; gap:10px; margin-bottom:16px; flex-wrap:wrap; }'
        + '.flight-card { background:#F8FAFC; border:1px solid #E2E6EB; border-radius:8px; padding:8px 16px; }'
        + '.flight-card-lbl { font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.05em; color:#5B6472; }'
        + '.flight-card-val { font-size:15px; font-weight:800; color:#002244; }'
        // Estilos del dashboard clonado (mismas reglas que la hoja de estilos viva)
        + '.fs-panel { background:#fff; border:1px solid #E2E6EB; border-radius:8px; overflow:hidden; }'
        + '.fs-panel-header { background:#002244; color:#fff; padding:8px 12px; font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.05em; display:flex; align-items:center; gap:7px; }'
        + '.fs-summary-table { width:100%; border-collapse:collapse; font-size:12px; }'
        + '.fs-summary-table thead th { background:#F4F6F9; color:#5B6472; font-size:9.5px; font-weight:700; text-transform:uppercase; letter-spacing:.04em; padding:6px 12px; text-align:left; border-bottom:1px solid #E2E6EB; }'
        + '.fs-summary-table tbody td { padding:8px 12px; border-bottom:1px solid #F0F2F5; vertical-align:middle; }'
        + '.fs-summary-table tbody tr:last-child td { border-bottom:none; }'
        + '.fs-summary-icon { display:inline-flex; align-items:center; justify-content:center; width:22px; height:22px; border-radius:5px; flex-shrink:0; }'
        + '.fs-summary-label { font-weight:700; color:#1A1F29; }'
        + '.fs-summary-detail { font-size:13.5px; font-weight:700; }'
        + '.fs-summary-detail-empty { font-size:13px; color:#9AA3AF; }'
        + '.fs-col-lbl { font-size:9.5px; font-weight:700; text-transform:uppercase; color:#5B6472; text-align:center; }'
        + '.fs-row-lbl { font-size:11px; font-weight:800; color:#1A1F29; }'
        + '.fs-panel-input { height:auto; font-size:13px; width:100%; text-align:center; border:none !important; background:transparent !important; font-weight:700; color:#1A1F29; padding:2px; }'
        + '.fs-pct-bar { display:flex; align-items:center; gap:8px; }'
        + '.fs-pct-num { font-size:12px; font-weight:800; color:#1A1F29; width:34px; text-align:right; flex-shrink:0; }'
        + '.fs-bar-track { background:#E5E7EB; border-radius:5px; height:10px; overflow:hidden; flex:1; -webkit-print-color-adjust:exact; print-color-adjust:exact; }'
        + '.fs-bar-fill { height:10px; border-radius:5px; -webkit-print-color-adjust:exact; print-color-adjust:exact; }'
        + '@media print {'
        + '  @page { size:A4 landscape; margin:10mm; }'
        + '  body { padding:0; }'
        + '  .fs-panel, .flight-card { -webkit-print-color-adjust:exact !important; print-color-adjust:exact !important; }'
        + '}'
        + '</style></head><body>'

        + '<div class="report-header">'
        + '<div>'
        + '<div class="report-title">FLIGHT SUMMARY REPORT</div>'
        + '<div class="report-sub">Delta Air Lines Cargo — Executive Flight Report</div>'
        + '</div>'
        + '<div class="report-meta">'
        + 'Generated on ' + generatedOn + '<br>'
        + 'By ' + esc(agentName || '—')
        + '</div>'
        + '</div>'

        + '<div class="flight-cards">'
        + '<div class="flight-card"><div class="flight-card-lbl">Airline</div><div class="flight-card-val">Delta Air Lines (DL)</div></div>'
        + '<div class="flight-card"><div class="flight-card-lbl">Flight No.</div><div class="flight-card-val">' + esc(flt) + '</div></div>'
        + '<div class="flight-card"><div class="flight-card-lbl">Date</div><div class="flight-card-val">' + dateLabel + '</div></div>'
        + '<div class="flight-card"><div class="flight-card-lbl">Route</div><div class="flight-card-val">' + esc(station) + ' → ' + esc(dest) + '</div></div>'
        + '<div class="flight-card"><div class="flight-card-lbl">Scheduled / Actual</div><div class="flight-card-val">' + esc(etd || '—') + ' / ' + esc(eta || '—') + '</div></div>'
        + '<div class="flight-card"><div class="flight-card-lbl">Manifest No.</div><div class="flight-card-val">' + esc(mn || '—') + '</div></div>'
        + '</div>'

        + dashboardHtml

        + loadPlanBlock

        + '<div style="margin-top:18px;font-size:10px;color:#9AA3AF;text-align:right;">'
        + 'Report generated on ' + generatedOn + ' by ' + esc(agentName || '—')
        + '</div>'

        + '</body></html>';
}

// ── Clona el bloque visual del dashboard (ULD Summary / Bulk Summary /
// Payload-Volume-Positions / PAX-Revenue) tal como se ve en pantalla, para
// garantizar que el reporte sea una representación fiel. Los <input> se
// congelan a su valor actual (clonar solo copia el HTML original, no el
// valor en vivo del DOM) y se dejan de solo lectura, con apariencia de
// texto plano en vez de campos editables — no tiene sentido editar un
// documento ya generado. ──
function pfCloneDashboardForReport() {
    const original = document.getElementById('pfFsDashboardRow');
    if (!original) return '';
    const clone = original.cloneNode(true);

    const origInputs  = original.querySelectorAll('input, select');
    const cloneInputs = clone.querySelectorAll('input, select');
    origInputs.forEach(function(o, i) {
        const c = cloneInputs[i];
        if (!c) return;
        if (o.tagName === 'SELECT') {
            const opt = o.options[o.selectedIndex];
            const span = document.createElement('span');
            span.textContent = opt ? opt.textContent : '';
            span.style.cssText = 'font-weight:700;font-size:13px;';
            c.replaceWith(span);
        } else {
            c.setAttribute('value', o.value || '');
            c.setAttribute('readonly', 'readonly');
            c.removeAttribute('onchange');
            c.removeAttribute('placeholder');
        }
    });
    // Botones (ej. recalcular Revenue) no aplican en un documento estático
    clone.querySelectorAll('button').forEach(function(b) { b.remove(); });

    return clone.outerHTML;
}
// ══════════════════════════════════════════════════════════════
//  LOAD PLAN IMAGE — Nuevo flujo Flight Summary: incorporación por
//  Ctrl+V / Drag & Drop / Upload Image, con análisis automático
//  opcional para autocompletar los campos del formulario.
// ══════════════════════════════════════════════════════════════
let pfLoadPlanImageData = null; // data URL de la imagen incorporada
let _pfLoadPlanAnalysis  = null; // último resultado de análisis (para historial)

function pfRenderLoadPlanPreview() {
    pfUpdateLoadPlanStatusBadge();
    const zone    = document.getElementById('pfLoadPlanDropzone');
    const actions = document.getElementById('pfLoadPlanActions');
    if (!zone) return;
    if (pfLoadPlanImageData) {
        zone.style.cursor = 'zoom-in';
        zone.innerHTML = `<div style="position:relative;width:100%;">
            <img src="${pfLoadPlanImageData}" style="width:100%;max-height:520px;object-fit:contain;display:block;border-radius:4px;background:#F4F6F9;">
            <span style="position:absolute;top:8px;right:8px;background:rgba(0,0,0,.55);color:#fff;font-size:10px;
                padding:3px 8px;border-radius:10px;pointer-events:none;">&#128269; Click to zoom</span>
        </div>`;
        if (actions) actions.style.display = 'flex';
    } else {
        zone.style.cursor = 'default';
        zone.innerHTML = `<div style="text-align:center;color:var(--text-light);font-size:12px;padding:24px;line-height:1.8;">
            &#128203; Paste (Ctrl+V), drag &amp; drop, or
            <label style="color:var(--delta-sky);cursor:pointer;text-decoration:underline;" onclick="event.stopPropagation();">upload
                <input type="file" accept="image/png,image/jpeg,image/jpg" style="display:none;" onchange="pfOnLoadPlanFileInput(this)">
            </label>
            the Load Plan image
        </div>`;
        if (actions) actions.style.display = 'none';
    }
}

// Abre la imagen del Load Plan en una vista Lightbox/Zoom a pantalla
// completa (Reingeniería Flight Summary #4). No interfiere con el click
// para "upload" cuando aún no hay imagen (ese label detiene la propagación).
function pfLoadPlanImageClick(event) {
    if (!pfLoadPlanImageData) return;
    if (event && event.target && event.target.tagName === 'INPUT') return;

    const overlay = document.createElement('div');
    overlay.id = 'pfLoadPlanLightbox';
    overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.85);z-index:9999;'
        + 'display:flex;align-items:center;justify-content:center;cursor:zoom-out;padding:30px;';
    overlay.innerHTML = `<img src="${pfLoadPlanImageData}" style="max-width:100%;max-height:100%;object-fit:contain;
            box-shadow:0 8px 40px rgba(0,0,0,.5);border-radius:4px;">
        <button onclick="document.getElementById('pfLoadPlanLightbox').remove()"
            style="position:absolute;top:20px;right:26px;background:none;border:none;color:#fff;
            font-size:28px;cursor:pointer;line-height:1;">&#x2715;</button>`;
    overlay.addEventListener('click', function(e) { if (e.target === overlay) overlay.remove(); });
    document.body.appendChild(overlay);
}

// ── Persistencia del Load Plan Image (Ajustes adicionales Flight Summary
// #3): se asocia como mínimo a Aerolínea + Flight Number + Flight Date
// (+ Station), de forma que cada vuelo tenga su propio Load Plan. Se
// guarda automáticamente al cargar/reemplazar una imagen y se recupera
// automáticamente al volver a seleccionar ese mismo vuelo/fecha — sin
// que el usuario tenga que volver a subirla. Toda falla (por ejemplo si
// el backend todavía no implementa estas acciones) se maneja en
// silencio: el usuario simplemente ve el área vacía, lista para cargar
// una imagen nueva, sin mensajes técnicos. ──
async function pfSaveLoadPlanImage() {
    const flt  = document.getElementById('pfFlight')?.value.split(' ')[0] || '';
    const date = document.getElementById('pfDate')?.value    || '';
    const sta  = document.getElementById('pfStation')?.value || 'LIM';
    if (!flt || !date || !pfLoadPlanImageData) return;
    try {
        await apiFetch('/shipments/postflight_api.php', {
            method: 'POST',
            body: JSON.stringify({
                action:       'save_load_plan_image',
                airline:      'DL',
                flt_number:   flt,
                flt_date:     date,
                station:      sta,
                image_base64: pfLoadPlanImageData,
            })
        });
    } catch(e) { /* almacenamiento best-effort — nunca bloquea al usuario */ }
}

async function pfClearSavedLoadPlanImage() {
    const flt  = document.getElementById('pfFlight')?.value.split(' ')[0] || '';
    const date = document.getElementById('pfDate')?.value    || '';
    const sta  = document.getElementById('pfStation')?.value || 'LIM';
    if (!flt || !date) return;
    try {
        await apiFetch('/shipments/postflight_api.php', {
            method: 'POST',
            body: JSON.stringify({
                action:       'save_load_plan_image',
                airline:      'DL',
                flt_number:   flt,
                flt_date:     date,
                station:      sta,
                image_base64: null, // reemplaza sin dejar la anterior
            })
        });
    } catch(e) {}
}

async function pfLoadSavedLoadPlanImage(flt, date, sta) {
    if (!flt || !date) { pfLoadPlanImageData = null; _pfLoadPlanAnalysis = null; pfRenderLoadPlanPreview(); return; }
    try {
        const params = new URLSearchParams({ action: 'get_load_plan_image', airline: 'DL', flt_number: flt, flt_date: date, station: sta });
        const res = await apiFetch('/shipments/postflight_api.php?' + params.toString());
        if (res && res.success && res.data && res.data.image_base64) {
            pfLoadPlanImageData = res.data.image_base64;
            _pfLoadPlanAnalysis  = res.data.analysis
                ? (typeof res.data.analysis === 'string' ? JSON.parse(res.data.analysis) : res.data.analysis)
                : null;
        } else {
            pfLoadPlanImageData = null;
            _pfLoadPlanAnalysis  = null;
        }
    } catch(e) {
        pfLoadPlanImageData = null;
        _pfLoadPlanAnalysis  = null;
    }
    pfRenderLoadPlanPreview();
}

function pfHandleLoadPlanFile(file) {
    if (!file || !file.type || file.type.indexOf('image') === -1) return;
    const reader = new FileReader();
    reader.onload = function(e) {
        pfLoadPlanImageData = e.target.result;
        _pfLoadPlanAnalysis  = null;
        pfRenderLoadPlanPreview();
        const msg = document.getElementById('pfLoadPlanMsg');
        if (msg) { msg.style.color = 'var(--text-mid)'; msg.textContent = 'Image loaded. Click "Analyze Image" or fill the fields manually.'; }
        // Reemplaza automáticamente cualquier Load Plan previamente
        // guardado para este mismo vuelo/fecha (no se acumulan versiones).
        pfSaveLoadPlanImage();
    };
    reader.readAsDataURL(file);
}

function pfOnLoadPlanFileInput(input) {
    const file = input.files && input.files[0];
    if (file) pfHandleLoadPlanFile(file);
}

function pfRemoveLoadPlanImage() {
    pfLoadPlanImageData = null;
    _pfLoadPlanAnalysis  = null;
    pfRenderLoadPlanPreview();
    const msg = document.getElementById('pfLoadPlanMsg');
    if (msg) msg.textContent = '';
    pfClearSavedLoadPlanImage();
}

// Pegar (Ctrl+V) y Drag&Drop se enganchan una sola vez a nivel de
// documento — solo actúan mientras el TAB Flight Summary está visible.
if (!window._pfLoadPlanListenersAttached) {
    window._pfLoadPlanListenersAttached = true;

    document.addEventListener('paste', function(e) {
        const zone = document.getElementById('pfLoadPlanDropzone');
        if (!zone || document.getElementById('pfTabContentSum')?.style.display === 'none') return;
        const items = (e.clipboardData || window.clipboardData)?.items || [];
        for (const item of items) {
            if (item.type && item.type.indexOf('image') !== -1) {
                pfHandleLoadPlanFile(item.getAsFile());
                e.preventDefault();
                break;
            }
        }
    });

    document.addEventListener('dragover', function(e) {
        const zone = document.getElementById('pfLoadPlanDropzone');
        if (zone && zone.contains(e.target)) {
            e.preventDefault();
            zone.style.borderColor = 'var(--delta-sky)';
        }
    });
    document.addEventListener('dragleave', function(e) {
        const zone = document.getElementById('pfLoadPlanDropzone');
        if (zone && zone.contains(e.target)) zone.style.borderColor = 'var(--border)';
    });
    document.addEventListener('drop', function(e) {
        const zone = document.getElementById('pfLoadPlanDropzone');
        if (zone && zone.contains(e.target)) {
            e.preventDefault();
            zone.style.borderColor = 'var(--border)';
            const file = e.dataTransfer?.files?.[0];
            if (file) pfHandleLoadPlanFile(file);
        }
    });
}

// ── Análisis automático del Load Plan (Nuevo flujo Flight Summary #3/#4) ──
// NOTA IMPORTANTE: esta función depende de un endpoint de backend
// (action=analyze_load_plan) que debe implementarse del lado del servidor,
// invocando un modelo con visión (ej. Claude) con una API key propia del
// backend. Ese endpoint no puede construirse desde este archivo frontend
// (index.php) porque requiere credenciales secretas que no deben vivir en
// el navegador. La función ya queda lista para consumir su respuesta.
// Estado de disponibilidad del análisis automático (Correcciones Flight
// Summary — depuración #5/#6): 'unknown' hasta el primer intento,
// 'available' si el backend alguna vez respondió con datos válidos,
// 'unavailable' si falló — en cuyo caso no se vuelve a llamar al backend
// en esta sesión (se evitan llamadas repetitivas) y el formulario sigue
// totalmente utilizable de forma manual.
let _pfLoadPlanAnalysisState = 'unknown';

function pfUpdateLoadPlanStatusBadge() {
    const badge = document.getElementById('pfLoadPlanStatusBadge');
    if (!badge) return;
    if (_pfLoadPlanAnalysisState === 'available') {
        badge.textContent = '\u{1F7E2} Available';
        badge.style.background = '#ECFDF5';
        badge.style.color = '#065F46';
    } else if (_pfLoadPlanAnalysisState === 'unavailable') {
        badge.textContent = '\u{1F534} Service Unavailable';
        badge.style.background = '#FDECEC';
        badge.style.color = '#991B1B';
    } else {
        badge.textContent = '\u{1F7E1} Under Development';
        badge.style.background = '#FEF9E7';
        badge.style.color = '#7C5A00';
    }
}

async function pfAnalyzeLoadPlan() {
    if (!pfLoadPlanImageData) return;
    const msg = document.getElementById('pfLoadPlanMsg');

    // Ya sabemos que el servicio no está disponible en esta sesión: no
    // repetir la llamada, solo informar y dejar seguir trabajando manualmente.
    if (_pfLoadPlanAnalysisState === 'unavailable') {
        msg.style.color = 'var(--text-mid)';
        msg.textContent = 'Automatic image analysis is currently unavailable. You may continue by completing the information manually.';
        return;
    }

    msg.style.color = 'var(--text-mid)';
    msg.textContent = 'Analyzing image...';

    try {
        const timeoutMs = 20000;
        const res = await Promise.race([
            apiFetch('/shipments/postflight_api.php', {
                method: 'POST',
                body: JSON.stringify({
                    action:       'analyze_load_plan',
                    image_base64: pfLoadPlanImageData,
                    flt_number:   document.getElementById('pfFlight')?.value || '',
                    flt_date:     document.getElementById('pfDate')?.value   || '',
                })
            }),
            new Promise(function(_, reject) { setTimeout(function(){ reject(new Error('timeout')); }, timeoutMs); }),
        ]);

        // Respuesta vacía / con forma inesperada — se trata igual que
        // "servicio no disponible", sin exponer detalles técnicos.
        const hasValidData = res && res.success && res.data && typeof res.data === 'object';
        if (hasValidData) {
            _pfLoadPlanAnalysisState = 'available';
            pfUpdateLoadPlanStatusBadge();
            _pfLoadPlanAnalysis = res.data;
            pfApplyLoadPlanAnalysis(res.data);
            msg.style.color = '#0F6E56';
            msg.textContent = '\u2713 Data recognized — please review the fields before sending.';
        } else {
            _pfLoadPlanAnalysisState = 'unavailable';
            pfUpdateLoadPlanStatusBadge();
            msg.style.color = 'var(--text-mid)';
            msg.textContent = 'Automatic image analysis is currently unavailable. You may continue by completing the information manually.';
        }
    } catch(e) {
        _pfLoadPlanAnalysisState = 'unavailable';
        pfUpdateLoadPlanStatusBadge();
        msg.style.color = 'var(--text-mid)';
        msg.textContent = 'Automatic image analysis is currently unavailable. You may continue by completing the information manually.';
    }
}

// Autocompleta SOLO los campos que estén vacíos — nunca pisa una edición
// manual que el usuario ya haya hecho (#5: el usuario siempre puede
// corregir/reemplazar antes de enviar).
function pfApplyLoadPlanAnalysis(d) {
    const fill = function(id, v) {
        const el = document.getElementById(id);
        if (el && !el.value && v != null && v !== '') el.value = v;
    };
    fill('pfAcTypeFs',   d.aircraft_type);
    fill('pfShipNumber', d.ship_number);
    fill('pfPayloadKg',  d.payload_kg);
    fill('pfFsFuel',     d.fuel_lbs);
    fill('pfPaxOb',      d.pax_ob);
    fill('pfNrPax',      d.nr_pax_ob);
    const revEl = document.getElementById('pfFsRevenue');
    if (revEl && !revEl.value && d.revenue) revEl.value = 'US$ ' + Math.round(parseFloat(d.revenue)).toLocaleString('en-US');
}

// ── Flight Summary — Enviar (Modal Unificado de Envío de Reportes:
// Send / Preview / Print / PDF desde un único documento — Generación del
// Reporte Flight Summary). El botón "Preview/Print" independiente se
// eliminó: esas mismas acciones ya viven dentro de este modal. ─────────
async function pfSendSummary() {
    if (!pfData) { alert('Load the flight first.'); return; }

    const flt     = document.getElementById('pfFlight').value;
    const date    = document.getElementById('pfDate').value;
    const mn      = document.getElementById('pfManifest').value;
    const station = document.getElementById('pfStation')?.value || 'LIM';
    const acType  = document.getElementById('pfAcTypeFs')?.value || '';
    const paxOb   = document.getElementById('pfPaxOb')?.value || '';
    const nrPax   = document.getElementById('pfNrPax')?.value || '';
    const fuel    = document.getElementById('pfFuel')?.value || '';

    // Agent on Duty = usuario firmado en el sistema (Generación del
    // Reporte Flight Summary): ya no es un campo manual.
    const agent = (currentUser && (currentUser.name || currentUser.full_name || currentUser.display_name))
        || (currentUser?.email || '');

    const dateLabel = date ? (function(){
        const d = new Date(date + 'T12:00:00');
        const m = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
        return d.getDate() + '-' + m[d.getMonth()] + '-' + d.getFullYear();
    })() : '';

    await pfLoadOpsData();

    reportModalOpen({
        reportType:  'FLIGHT_SUMMARY',
        title:       'Flight Summary Report',
        icon:        '📊',
        contextLine: flt + ' • ' + station + ' → ' + (pfData.flt_dest || '') + ' • ' + dateLabel,
        subject:     'FLIGHT SUMMARY // ' + station + '-' + (pfData.flt_dest || '') + ' // ' + flt + '-' + dateLabel.replace(/-/g, '').toUpperCase(),
        station:     station,
        recipientMode: 'notification_config',
        filenamePrefix: 'Flight_Summary_' + flt.replace(/[^A-Z0-9]/gi, '_'),
        buildHtml: function() {
            return pfBuildSummaryHtml();
        },
        onSend: function(payload) {
            return apiFetch('/shipments/postflight_api.php', {
                method: 'POST',
                body: JSON.stringify({
                    action:                'flight_summary',
                    flt_number:            flt,
                    flt_date:              date,
                    manifest_number:       mn,
                    station:               station,
                    agent_name:            agent,
                    ac_type:               acType,
                    pax_ob:                parseInt(paxOb) || 0,
                    nr_pax:                parseInt(nrPax) || 0,
                    fuel:                  fuel,
                    to_email:              payload.to.join(', '),
                    subject:               payload.subject,
                    email_html:            payload.html,
                    // Historial y trazabilidad (Nuevo flujo Flight Summary #8) —
                    // requiere que el backend persista estos campos junto al
                    // envío para poder auditar/reenviar sin recapturar la imagen.
                    load_plan_image_base64: pfLoadPlanImageData || null,
                    load_plan_analysis:     _pfLoadPlanAnalysis ? JSON.stringify(_pfLoadPlanAnalysis) : null,
                    user_id:               currentUser ? currentUser.id : null,
                })
            });
        },
    });
}

// ── Módulo AWB Cancelled (standalone) ────────────────────────────
function cancelledInit() {
    document.getElementById('mainContent').innerHTML = `
        <div class="module-header">
            <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="color:var(--delta-red)"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
            <div class="module-title">AWB Cancelled</div>
        </div>
        <div class="toolbar" style="align-items:flex-end;flex-wrap:wrap;gap:10px;">
            <div class="awb-field"><label>Estación</label>
                <select id="cxStation" class="awb-input" style="width:80px;" onchange="cxLoad()"></select>
            </div>
            <div class="awb-field"><label>Agente / FF</label>
                <input type="text" id="cxAgent" class="awb-input" placeholder="Todos..." style="width:150px;" oninput="cxLoad()">
            </div>
            <div class="awb-field"><label>Vuelo</label>
                <input type="text" id="cxFlt" class="awb-input" placeholder="DL150..." style="width:90px;" oninput="cxLoad()">
            </div>
            <div class="awb-field"><label>Fecha desde</label>
                <input type="date" id="cxFrom" class="awb-input" style="width:140px;" onchange="cxLoad()">
            </div>
            <div class="awb-field"><label>Fecha hasta</label>
                <input type="date" id="cxTo" class="awb-input" style="width:140px;" onchange="cxLoad()">
            </div>
            <button class="btn-secondary" onclick="cxExport()" style="align-self:flex-end;">
                <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
                Exportar Excel
            </button>
        </div>
        <div class="card" style="padding:0;overflow:hidden;">
            <div id="cxTable" style="padding:32px;text-align:center;color:var(--text-mid);">Selecciona un rango de fechas.</div>
        </div>`;

    // Init station select and default dates
    const today = new Date();
    const firstDay = new Date(today.getFullYear(), today.getMonth(), 1).toISOString().slice(0,10);
    const lastDay  = new Date(today.getFullYear(), today.getMonth()+1, 0).toISOString().slice(0,10);
    document.getElementById('cxFrom').value = firstDay;
    document.getElementById('cxTo').value   = lastDay;
    populateStationSelect('cxStation', [['','Todas']]).then(() => cxLoad());
}

async function cxLoad() {
    const station = document.getElementById('cxStation')?.value || '';
    const agent   = document.getElementById('cxAgent')?.value   || '';
    const flt     = document.getElementById('cxFlt')?.value     || '';
    const from    = document.getElementById('cxFrom')?.value    || '';
    const to      = document.getElementById('cxTo')?.value      || '';
    const tbl     = document.getElementById('cxTable');
    if (!from || !to) return;
    tbl.innerHTML = '<div style="padding:32px;text-align:center;color:var(--text-mid);">Cargando...</div>';
    const params = new URLSearchParams({ status: 'CANCELLED' });
    if (station) params.set('station', station);
    if (flt)     params.set('flt_number', flt);
    if (from)    params.set('date_from', from);
    if (to)      params.set('date_to', to);
    const res = await apiFetch('/shipments/postflight_api.php?' + params.toString());
    const rows = (res.cancelled || res.data || []).filter(function(r) {
        if (agent && !(r.agent_name||'').toLowerCase().includes(agent.toLowerCase())) return false;
        return true;
    });
    window._cxRows = rows;
    if (!rows.length) {
        tbl.innerHTML = '<div style="padding:32px;text-align:center;color:var(--text-mid);">No hay guías canceladas en el período seleccionado.</div>';
        return;
    }
    let html = '<table style="width:100%;border-collapse:collapse;font-size:12px;">'
        + '<thead><tr style="background:var(--delta-navy);color:#fff;">'
        + '<th style="padding:9px 10px;text-align:left;">AWB</th>'
        + '<th style="padding:9px 10px;">Fecha</th>'
        + '<th style="padding:9px 10px;">Vuelo</th>'
        + '<th style="padding:9px 10px;text-align:left;">Agente / FF</th>'
        + '<th style="padding:9px 10px;text-align:left;">Descripción</th>'
        + '<th style="padding:9px 10px;text-align:right;">Pzas</th>'
        + '<th style="padding:9px 10px;text-align:right;">Gross WGT</th>'
        + '<th style="padding:9px 10px;text-align:right;">CHG WGT</th>'
        + '<th style="padding:9px 10px;text-align:right;">Revenue</th>'
        + '</tr></thead><tbody>';
    let totPcs=0, totGross=0, totChg=0, totRev=0;
    rows.forEach(function(r, i) {
        const bg = i%2===0 ? '#fff' : '#F8FAFC';
        const pcs   = parseInt(r.pieces||0);
        const gross = parseFloat(r.gross_weight||0);
        const chg   = parseFloat(r.chg_weight||0);
        const rev   = parseFloat(r.total_revenue||0);
        totPcs+=pcs; totGross+=gross; totChg+=chg; totRev+=rev;
        html += '<tr style="background:'+bg+';border-bottom:1px solid #F0F2F5;">'
            + '<td style="padding:7px 10px;font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;font-weight:700;color:var(--delta-navy);">' + esc(r.awb||r.shipment_number||'') + '</td>'
            + '<td style="padding:7px 10px;text-align:center;">' + (r.flt_date||'') + '</td>'
            + '<td style="padding:7px 10px;text-align:center;font-weight:700;">' + esc(r.flt_number||'') + '</td>'
            + '<td style="padding:7px 10px;">' + esc(r.agent_name||'') + '</td>'
            + '<td style="padding:7px 10px;">' + esc(r.description||'') + '</td>'
            + '<td style="padding:7px 10px;text-align:right;">' + pcs + '</td>'
            + '<td style="padding:7px 10px;text-align:right;">' + gross.toFixed(2) + '</td>'
            + '<td style="padding:7px 10px;text-align:right;">' + chg.toFixed(2) + '</td>'
            + '<td style="padding:7px 10px;text-align:right;">$' + rev.toFixed(2) + '</td>'
            + '</tr>';
    });
    html += '<tr style="background:#EEF3F8;font-weight:700;border-top:2px solid var(--delta-navy);">'
        + '<td colspan="5" style="padding:8px 10px;">TOTAL — ' + rows.length + ' guía(s)</td>'
        + '<td style="padding:8px 10px;text-align:right;">' + totPcs + '</td>'
        + '<td style="padding:8px 10px;text-align:right;">' + totGross.toFixed(2) + '</td>'
        + '<td style="padding:8px 10px;text-align:right;">' + totChg.toFixed(2) + '</td>'
        + '<td style="padding:8px 10px;text-align:right;color:var(--delta-red);">$' + totRev.toFixed(2) + '</td>'
        + '</tr>';
    html += '</tbody></table>';
    tbl.innerHTML = html;
}

function cxExport() {
    const rows = window._cxRows || [];
    if (!rows.length) { alert('No hay datos para exportar.'); return; }
    const data = [['AWB','Fecha','Vuelo','Agente/FF','Descripción','Pzas','Gross WGT','CHG WGT','Revenue']];
    rows.forEach(function(r) {
        data.push([r.awb||r.shipment_number||'', r.flt_date||'', r.flt_number||'',
            r.agent_name||'', r.description||'', r.pieces||0,
            r.gross_weight||0, r.chg_weight||0, r.total_revenue||0]);
    });
    const ws = XLSX.utils.aoa_to_sheet(data);
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'AWB Cancelled');
    XLSX.writeFile(wb, 'AWB_Cancelled_' + new Date().toISOString().slice(0,10) + '.xlsx');
}

// ── Guías Canceladas ──────────────────────────────────────────────
let pfCancelRows = [];

function pfInitCancelDatePicker() {
    const el = document.getElementById('pfCancelDateRange');
    if (!el || typeof flatpickr === 'undefined') return;
    flatpickr(el, {
        mode: 'range', locale: 'es', dateFormat: 'Y-m-d',
        altInput: true, altFormat: 'd/m/Y',
        onChange: () => pfLoadCancelled()
    });
}

async function pfLoadCancelled() {
    const station  = document.getElementById('pfStation')?.value || 'LIM';
    const agent    = document.getElementById('pfCancelAgent')?.value || '';
    const flt      = document.getElementById('pfCancelFlt')?.value || '';
    const dateEl   = document.getElementById('pfCancelDateRange');
    const dateVal  = dateEl ? dateEl.value : '';
    const wrap     = document.getElementById('pfCancelTableWrap');
    if (!wrap) return;

    wrap.innerHTML = '<div class="empty-state">Cargando...</div>';

    try {
        const params = new URLSearchParams({
            action: 'cancelled', station,
            agent_q: agent, flt_q: flt, date_range: dateVal
        });
        const res = await apiFetch('/shipments/postflight_api.php?' + params.toString());
        pfCancelRows = res.success ? (res.data || []) : [];

        if (!pfCancelRows.length) {
            wrap.innerHTML = '<div class="empty-state">No hay guías canceladas con los filtros aplicados.</div>';
            return;
        }

        let html = '<table class="data-table"><thead><tr>' +
            '<th>AWB</th><th>Fecha</th><th>Agente</th><th>Vuelo</th><th>Dest</th>' +
            '<th style="text-align:right;">Kg</th><th style="text-align:right;">Pzas</th>' +
            '<th>Descripción</th><th>Cancelado por</th></tr></thead><tbody>';

        pfCancelRows.forEach((r, i) => {
            const bg = i%2===0 ? '' : 'style="background:#F4F6F9;"';
            html += '<tr ' + bg + '>' +
                '<td><strong style="font-family:Courier New,monospace;font-variant-numeric:tabular-nums;letter-spacing:.03em;color:var(--delta-navy);">' + esc(r.awb||r.shipment_number) + '</strong></td>' +
                '<td>' + fmtDate(r.flt_date) + '</td>' +
                '<td style="text-transform:uppercase;">' + esc(r.agent_name||'—') + '</td>' +
                '<td>' + esc(r.flt_number||'—') + '</td>' +
                '<td style="text-align:center;font-weight:700;">' + esc(r.routing_dest||r.flt_dest||'—') + '</td>' +
                '<td style="text-align:right;">' + fmtNum(r.gross_weight) + '</td>' +
                '<td style="text-align:right;">' + (r.pieces||0) + '</td>' +
                '<td style="text-transform:uppercase;">' + esc(r.description||'—') + '</td>' +
                '<td style="font-size:11px;color:var(--text-mid);">' + esc(r.cancelled_by||'—') + '</td>' +
                '</tr>';
        });
        html += '</tbody></table>';
        wrap.innerHTML = html;
    } catch(e) {
        wrap.innerHTML = '<div class="empty-state">Error de conexión.</div>';
    }
}

function pfExportCancelled() {
    if (!pfCancelRows.length) { alert('No hay datos para exportar.'); return; }
    const data = pfCancelRows.map(r => ({
        AWB: r.awb || r.shipment_number,
        Fecha: r.flt_date, Agente: r.agent_name,
        Vuelo: r.flt_number, Destino: r.routing_dest || r.flt_dest,
        'Kg Bruto': r.gross_weight, Piezas: r.pieces,
        Descripción: r.description, 'Cancelado por': r.cancelled_by || '—',
    }));
    const ws = XLSX.utils.json_to_sheet(data);
    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'Canceladas');
    XLSX.writeFile(wb, 'Guias_Canceladas_' + new Date().toISOString().slice(0,10) + '.xlsx');
}
</script>

</body>
</html>
